1import { McpAttachmentInput } from "./input.ts";
2import {
3 AttachmentCreateUploadUrlInput,
4 AttachmentCreateUploadUrlResult,
5 AttachmentDeleteInput,
6 MessageId,
7 RunId,
8 ThreadId,
9 OrchestrationV2RunStatus,
10 OrchestratorMcpFailure,
11} from "@t3tools/contracts";
12import * as Crypto from "effect/Crypto";
13import * as FileSystem from "effect/FileSystem";
14import * as Schema from "effect/Schema";
15import { Tool, Toolkit } from "effect/ai";
16import * as ServerSecretStore from "../../../auth/ServerSecretStore.ts";
17import * as ServerConfig from "../../../config.ts";
18import * as ThreadManagementService from "../../../orchestration-v2/ThreadManagementService.ts";
19import * as McpInvocationContext from "../../McpInvocationContext.ts";
20
21const shared = {
22 failure: OrchestratorMcpFailure,
23 failureMode: "return" as const,
24 dependencies: [
25 McpInvocationContext.McpInvocationContext,
26 ThreadManagementService.ThreadManagementService,
27 ServerConfig.ServerConfig,
28 ServerSecretStore.ServerSecretStore,
29 FileSystem.FileSystem,
30 Crypto.Crypto,
31 ],
32};
33const AttachmentUploadTool = Tool.make("t3_attachment_prepare_upload", {
34 ...shared,
35 description:
36 "Create the app's signed upload URL. POST the exact bytes to relativeUrl on this MCP server's HTTP origin, then pass the attachment metadata and returned ID to t3_thread_send_attachments. Upload is separate from sending; provider attachment support is decided by its adapter.",
37 parameters: Schema.Struct({ upload: AttachmentCreateUploadUrlInput }),
38 success: AttachmentCreateUploadUrlResult,
39}).annotate(Tool.Destructive, true);
40const AttachmentDiscardTool = Tool.make("t3_attachment_discard", {
41 ...shared,
42 description:
43 "Discard a pending upload. Already-delivered thread attachments are never deleted by this operation.",
44 parameters: AttachmentDeleteInput,
45 success: Schema.Struct({}),
46}).annotate(Tool.Destructive, true);
47const AttachmentSendTool = Tool.make("t3_thread_send_attachments", {
48 ...shared,
49 description:
50 "Send uploaded attachments to this thread or any other thread in this environment. Each call is a new message, without a retry key. Acceptance does not mean the provider can consume the attachment or has finished the turn. The target cannot have broader permission modes than the caller; failures retain claimed files when dispatch outcome is uncertain.",
51 parameters: Schema.Struct({
52 threadId: Schema.optional(ThreadId),
53 message: Schema.optional(Schema.String.check(Schema.isMaxLength(120000))),
54 attachments: Schema.Array(McpAttachmentInput).check(
55 Schema.isMinLength(1),
56 Schema.isMaxLength(8),
57 ),
58 }),
59 success: Schema.Struct({
60 threadId: ThreadId,
61 messageId: MessageId,
62 runId: RunId,
63 status: OrchestrationV2RunStatus,
64 }),
65})
66 .annotate(Tool.Destructive, true)
67 .annotate(Tool.OpenWorld, true);
68export const AttachmentToolkit = Toolkit.make(
69 AttachmentUploadTool,
70 AttachmentDiscardTool,
71 AttachmentSendTool,
72);