agentmail-toolkit 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mcp.cjs CHANGED
@@ -97,14 +97,22 @@ var GetAttachmentParams = import_zod.z.object({
97
97
  threadId: ThreadIdSchema,
98
98
  attachmentId: AttachmentIdSchema
99
99
  });
100
- var AttachmentSchema = import_zod.z.object({
100
+ var AttachmentBaseSchema = import_zod.z.object({
101
101
  filename: import_zod.z.string().optional().describe("Filename"),
102
102
  contentType: import_zod.z.string().optional().describe("MIME type of the attachment"),
103
103
  contentDisposition: import_zod.z.enum(["inline", "attachment"]).optional().describe("Content disposition"),
104
- contentId: import_zod.z.string().optional().describe("Content ID for inline attachments"),
105
- content: import_zod.z.string().optional().describe("Base64 encoded content. Exactly one of content or url must be provided"),
106
- url: import_zod.z.url().optional().describe("Publicly accessible URL to fetch the attachment from. Exactly one of content or url must be provided")
104
+ contentId: import_zod.z.string().optional().describe("Content ID for inline attachments")
107
105
  });
106
+ var AttachmentSchema = import_zod.z.union([
107
+ import_zod.z.strictObject({
108
+ ...AttachmentBaseSchema.shape,
109
+ content: import_zod.z.string().describe("Base64 encoded content")
110
+ }),
111
+ import_zod.z.strictObject({
112
+ ...AttachmentBaseSchema.shape,
113
+ url: import_zod.z.url().describe("Publicly accessible URL to fetch the attachment from")
114
+ })
115
+ ]).describe("Attachment: provide exactly one of content (base64) or url");
108
116
  var BaseMessageParams = import_zod.z.object({
109
117
  inboxId: InboxIdSchema,
110
118
  text: import_zod.z.string().optional().describe("Plain text body"),
@@ -126,6 +134,15 @@ var ReplyToMessageParams = BaseMessageParams.extend({
126
134
  cc: import_zod.z.array(import_zod.z.string()).optional().describe("Override CC recipients. Cannot be combined with replyAll"),
127
135
  bcc: import_zod.z.array(import_zod.z.string()).optional().describe("Override BCC recipients. Cannot be combined with replyAll"),
128
136
  replyTo: import_zod.z.array(import_zod.z.string()).optional().describe("Reply-to addresses")
137
+ // Mirrors the API's ReplyMessageSchema refine (agentmail-api schemas/message.ts),
138
+ // predicate and error text both - reject the combination locally instead of
139
+ // round-tripping a request the API will reject. NOTE: the MCP SDK validates
140
+ // tools/call args against a z.object it rebuilds from the raw shape, which
141
+ // drops root-level refines - runTool in mcp.ts re-parses with this schema so
142
+ // the refine is enforced on that path too.
143
+ }).refine((data) => !(data.replyAll && (data.to || data.cc || data.bcc)), {
144
+ message: "Cannot specify to, cc, or bcc when replyAll is true",
145
+ path: ["replyAll"]
129
146
  });
130
147
  var ForwardMessageParams = SendMessageParams.extend({
131
148
  messageId: MessageIdSchema
@@ -184,20 +201,18 @@ var AuthMeParams = import_zod.z.object({});
184
201
  var import_zod2 = require("zod");
185
202
  var isoDate = () => import_zod2.z.iso.datetime().describe("ISO 8601 datetime");
186
203
  var MetadataSchema = import_zod2.z.record(import_zod2.z.string(), import_zod2.z.union([import_zod2.z.string(), import_zod2.z.number(), import_zod2.z.boolean()]));
187
- var PaginationSchema = import_zod2.z.looseObject({
204
+ var PaginationSchema = import_zod2.z.object({
188
205
  count: import_zod2.z.number().describe("Number of items returned"),
189
206
  limit: import_zod2.z.number().optional().describe("Limit of number of items returned"),
190
207
  nextPageToken: import_zod2.z.string().optional().describe("Page token for pagination")
191
208
  });
192
- var VoidResultSchema = import_zod2.z.looseObject({
209
+ var VoidResultSchema = import_zod2.z.object({
193
210
  success: import_zod2.z.literal(true)
194
211
  });
195
- var InboxSchema = import_zod2.z.looseObject({
196
- podId: import_zod2.z.string(),
212
+ var InboxSchema = import_zod2.z.object({
197
213
  inboxId: import_zod2.z.string(),
198
214
  email: import_zod2.z.string(),
199
215
  displayName: import_zod2.z.string().optional(),
200
- clientId: import_zod2.z.string().optional(),
201
216
  metadata: MetadataSchema.optional().describe("Custom metadata attached to the inbox"),
202
217
  updatedAt: isoDate(),
203
218
  createdAt: isoDate()
@@ -205,7 +220,7 @@ var InboxSchema = import_zod2.z.looseObject({
205
220
  var ListInboxesResponseSchema = PaginationSchema.extend({
206
221
  inboxes: import_zod2.z.array(InboxSchema)
207
222
  });
208
- var AttachmentMetaSchema = import_zod2.z.looseObject({
223
+ var AttachmentMetaSchema = import_zod2.z.object({
209
224
  attachmentId: import_zod2.z.string(),
210
225
  filename: import_zod2.z.string().optional(),
211
226
  size: import_zod2.z.number(),
@@ -216,10 +231,9 @@ var AttachmentMetaSchema = import_zod2.z.looseObject({
216
231
  var AttachmentResponseSchema = AttachmentMetaSchema.extend({
217
232
  downloadUrl: import_zod2.z.string().describe("URL to download the attachment"),
218
233
  expiresAt: isoDate().describe("Time at which the download URL expires"),
219
- text: import_zod2.z.string().optional().describe("Extracted text (PDF/DOCX only, toolkit-added)"),
220
- extractionError: import_zod2.z.string().optional().describe("Set when PDF/DOCX text extraction failed or was skipped (toolkit-added)")
234
+ text: import_zod2.z.string().optional().describe("Extracted text (PDF/DOCX only, toolkit-added; absent when extraction was skipped or failed - see download URL)")
221
235
  });
222
- var ThreadItemSchema = import_zod2.z.looseObject({
236
+ var ThreadItemSchema = import_zod2.z.object({
223
237
  inboxId: import_zod2.z.string(),
224
238
  threadId: import_zod2.z.string(),
225
239
  labels: import_zod2.z.array(import_zod2.z.string()),
@@ -237,7 +251,7 @@ var ThreadItemSchema = import_zod2.z.looseObject({
237
251
  updatedAt: isoDate(),
238
252
  createdAt: isoDate()
239
253
  });
240
- var MessageItemSchema = import_zod2.z.looseObject({
254
+ var MessageItemSchema = import_zod2.z.object({
241
255
  inboxId: import_zod2.z.string(),
242
256
  threadId: import_zod2.z.string(),
243
257
  messageId: import_zod2.z.string(),
@@ -252,7 +266,9 @@ var MessageItemSchema = import_zod2.z.looseObject({
252
266
  attachments: import_zod2.z.array(AttachmentMetaSchema).optional(),
253
267
  inReplyTo: import_zod2.z.string().optional(),
254
268
  references: import_zod2.z.array(import_zod2.z.string()).optional(),
255
- headers: import_zod2.z.record(import_zod2.z.string(), import_zod2.z.string()).optional(),
269
+ // Raw RFC-822 `headers` deliberately excluded: they carry personal identifiers
270
+ // (Received-chain IPs, Return-Path) a model never needs — flagged by OpenAI app
271
+ // review. Threading works via inReplyTo/references.
256
272
  size: import_zod2.z.number(),
257
273
  updatedAt: isoDate(),
258
274
  createdAt: isoDate()
@@ -270,25 +286,43 @@ var ThreadSchema = ThreadItemSchema.extend({
270
286
  var ListThreadsResponseSchema = PaginationSchema.extend({
271
287
  threads: import_zod2.z.array(ThreadItemSchema)
272
288
  });
273
- var SearchThreadsResponseSchema = ListThreadsResponseSchema;
289
+ var HighlightsSchema = import_zod2.z.object({
290
+ from: import_zod2.z.array(import_zod2.z.string()).optional().describe("Matched fragments from the sender address"),
291
+ recipients: import_zod2.z.array(import_zod2.z.string()).optional().describe("Matched fragments from recipient addresses"),
292
+ subject: import_zod2.z.array(import_zod2.z.string()).optional().describe("Matched fragments from the subject"),
293
+ text: import_zod2.z.array(import_zod2.z.string()).optional().describe("Matched fragments from the body")
294
+ });
295
+ var SearchThreadsResponseSchema = PaginationSchema.extend({
296
+ threads: import_zod2.z.array(
297
+ ThreadItemSchema.extend({
298
+ highlights: HighlightsSchema.optional().describe("Matched fragments per field, present when the query matched an indexed field")
299
+ })
300
+ )
301
+ });
274
302
  var ListMessagesResponseSchema = PaginationSchema.extend({
275
303
  messages: import_zod2.z.array(MessageItemSchema)
276
304
  });
277
- var SearchMessagesResponseSchema = ListMessagesResponseSchema;
278
- var UpdateThreadResponseSchema = import_zod2.z.looseObject({
305
+ var SearchMessagesResponseSchema = PaginationSchema.extend({
306
+ messages: import_zod2.z.array(
307
+ MessageItemSchema.extend({
308
+ highlights: HighlightsSchema.optional().describe("Matched fragments per field, present when the query matched an indexed field")
309
+ })
310
+ )
311
+ });
312
+ var UpdateThreadResponseSchema = import_zod2.z.object({
279
313
  threadId: import_zod2.z.string(),
280
314
  labels: import_zod2.z.array(import_zod2.z.string())
281
315
  });
282
- var UpdateMessageResponseSchema = import_zod2.z.looseObject({
316
+ var UpdateMessageResponseSchema = import_zod2.z.object({
283
317
  messageId: import_zod2.z.string(),
284
318
  labels: import_zod2.z.array(import_zod2.z.string())
285
319
  });
286
- var SendMessageResponseSchema = import_zod2.z.looseObject({
320
+ var SendMessageResponseSchema = import_zod2.z.object({
287
321
  messageId: import_zod2.z.string(),
288
322
  threadId: import_zod2.z.string()
289
323
  });
290
324
  var DraftSendStatusSchema = import_zod2.z.enum(["scheduled", "sending", "failed"]);
291
- var DraftItemSchema = import_zod2.z.looseObject({
325
+ var DraftItemSchema = import_zod2.z.object({
292
326
  inboxId: import_zod2.z.string(),
293
327
  draftId: import_zod2.z.string(),
294
328
  labels: import_zod2.z.array(import_zod2.z.string()),
@@ -304,7 +338,6 @@ var DraftItemSchema = import_zod2.z.looseObject({
304
338
  updatedAt: isoDate()
305
339
  });
306
340
  var DraftSchema = DraftItemSchema.extend({
307
- clientId: import_zod2.z.string().optional(),
308
341
  replyTo: import_zod2.z.array(import_zod2.z.string()).optional(),
309
342
  text: import_zod2.z.string().optional(),
310
343
  html: import_zod2.z.string().optional(),
@@ -315,7 +348,7 @@ var ListDraftsResponseSchema = PaginationSchema.extend({
315
348
  drafts: import_zod2.z.array(DraftItemSchema)
316
349
  });
317
350
  var ScopeTypeSchema = import_zod2.z.enum(["organization", "pod", "inbox"]);
318
- var IdentitySchema = import_zod2.z.looseObject({
351
+ var IdentitySchema = import_zod2.z.object({
319
352
  scopeType: ScopeTypeSchema,
320
353
  scopeId: import_zod2.z.string(),
321
354
  organizationId: import_zod2.z.string(),
@@ -341,7 +374,11 @@ function apiErrorMessage(error) {
341
374
  }
342
375
  const base = detail ?? error.message;
343
376
  const bounded = typeof base === "string" && base.length > MAX_ERROR_BODY_LENGTH ? base.slice(0, MAX_ERROR_BODY_LENGTH) + "\u2026" : base;
344
- return error.statusCode ? `${bounded} (HTTP ${error.statusCode})` : bounded;
377
+ const withStatus = error.statusCode ? `${bounded} (HTTP ${error.statusCode})` : bounded;
378
+ if (error.statusCode === 401) return `${withStatus} \u2014 credentials are missing or invalid; reconnect or provide a valid API key`;
379
+ if (error.statusCode === 403) return `${withStatus} \u2014 the authenticated credential lacks permission for this action`;
380
+ if (error.statusCode === 429) return `${withStatus} \u2014 rate limited; wait before retrying`;
381
+ return withStatus;
345
382
  }
346
383
  function errorMessage(error) {
347
384
  if (error instanceof import_agentmail.AgentMailError) return apiErrorMessage(error);
@@ -469,11 +506,11 @@ async function getAttachment(client, args) {
469
506
  const attachment = await client.inboxes.threads.getAttachment(inboxId, threadId, attachmentId);
470
507
  if (!attachment.downloadUrl.startsWith("https://")) {
471
508
  console.error("[agentmail-toolkit] attachment download URL is not https, skipping extraction", { attachmentId });
472
- return { ...attachment, extractionError: "download URL is not https" };
509
+ return attachment;
473
510
  }
474
511
  if (attachment.size > MAX_ATTACHMENT_BYTES) {
475
512
  console.error("[agentmail-toolkit] attachment too large to extract, skipping", { attachmentId, size: attachment.size });
476
- return { ...attachment, extractionError: "attachment exceeds size cap" };
513
+ return attachment;
477
514
  }
478
515
  const response = await fetch(attachment.downloadUrl, { signal: AbortSignal.timeout(15e3), redirect: "error" });
479
516
  if (!response.ok) {
@@ -482,12 +519,12 @@ async function getAttachment(client, args) {
482
519
  const contentLength = Number(response.headers.get("content-length"));
483
520
  if (contentLength && contentLength > MAX_ATTACHMENT_BYTES) {
484
521
  console.error("[agentmail-toolkit] content-length exceeds cap, skipping", { attachmentId, contentLength });
485
- return { ...attachment, extractionError: "content-length exceeds size cap" };
522
+ return attachment;
486
523
  }
487
524
  const arrayBuffer = await response.arrayBuffer();
488
525
  if (arrayBuffer.byteLength > MAX_ATTACHMENT_BYTES) {
489
526
  console.error("[agentmail-toolkit] downloaded attachment exceeds cap, skipping", { attachmentId, size: arrayBuffer.byteLength });
490
- return { ...attachment, extractionError: "downloaded attachment exceeds size cap" };
527
+ return attachment;
491
528
  }
492
529
  const fileBytes = new Uint8Array(arrayBuffer);
493
530
  const detectedType = detectFileType(fileBytes);
@@ -502,7 +539,7 @@ async function getAttachment(client, args) {
502
539
  attachmentId,
503
540
  error: err instanceof Error ? err.message : String(err)
504
541
  });
505
- return { ...attachment, extractionError: err instanceof Error ? err.message : String(err) };
542
+ return attachment;
506
543
  }
507
544
  }
508
545
  async function listMessages(client, args) {
@@ -955,6 +992,47 @@ var ListToolkit = class extends BaseToolkit {
955
992
  };
956
993
 
957
994
  // src/mcp.ts
995
+ var toolsByName = new Map(tools.map((tool) => [tool.name, tool]));
996
+ async function runTool(tool, client, args) {
997
+ const preflight = tool.paramsSchema.safeParse(normalize(args));
998
+ if (!preflight.success) {
999
+ const message = preflight.error.issues.map((issue) => issue.message).join("; ");
1000
+ return {
1001
+ content: [{ type: "text", text: `Invalid arguments: ${message}` }],
1002
+ isError: true
1003
+ };
1004
+ }
1005
+ const { isError, result, statusCode, body } = await safeFunc(tool.func, client, preflight.data);
1006
+ if (isError) {
1007
+ console.error("[agentmail-toolkit] tool error", {
1008
+ tool: tool.name,
1009
+ statusCode,
1010
+ body: truncateForLog(body)
1011
+ });
1012
+ return {
1013
+ content: [{ type: "text", text: String(result) }],
1014
+ isError: true
1015
+ };
1016
+ }
1017
+ const parsed = import_zod3.z.object(tool.outputSchema.shape).safeParse(normalize(result));
1018
+ if (!parsed.success) {
1019
+ console.error("[agentmail-toolkit] output schema mismatch", {
1020
+ tool: tool.name,
1021
+ issues: parsed.error.issues
1022
+ });
1023
+ return {
1024
+ content: [{ type: "text", text: `Internal error: ${tool.name} result did not match its declared output schema` }],
1025
+ isError: true
1026
+ };
1027
+ }
1028
+ const structuredContent = parsed.data;
1029
+ const text = JSON.stringify(structuredContent);
1030
+ return {
1031
+ structuredContent,
1032
+ content: [{ type: "text", text }],
1033
+ isError: false
1034
+ };
1035
+ }
958
1036
  var AgentMailToolkit = class extends ListToolkit {
959
1037
  buildTool(tool) {
960
1038
  return {
@@ -963,41 +1041,36 @@ var AgentMailToolkit = class extends ListToolkit {
963
1041
  description: tool.description,
964
1042
  inputSchema: tool.paramsSchema.shape,
965
1043
  outputSchema: tool.outputSchema.shape,
966
- callback: async (args) => {
967
- const { isError, result, statusCode, body } = await safeFunc(tool.func, this.client, args);
968
- if (isError) {
969
- console.error("[agentmail-toolkit] tool error", {
970
- tool: tool.name,
971
- statusCode,
972
- body: truncateForLog(body)
973
- });
974
- return {
975
- content: [{ type: "text", text: String(result) }],
976
- isError: true
977
- };
978
- }
979
- const parsed = import_zod3.z.object(tool.outputSchema.shape).safeParse(normalize(result));
980
- if (!parsed.success) {
981
- console.error("[agentmail-toolkit] output schema mismatch", {
982
- tool: tool.name,
983
- issues: parsed.error.issues
984
- });
985
- return {
986
- content: [{ type: "text", text: `Internal error: ${tool.name} result did not match its declared output schema` }],
987
- isError: true
988
- };
989
- }
990
- const structuredContent = parsed.data;
991
- const text = JSON.stringify(structuredContent);
992
- return {
993
- structuredContent,
994
- content: [{ type: "text", text }],
995
- isError: false
996
- };
997
- },
1044
+ callback: async (args) => runTool(tool, this.client, args),
998
1045
  annotations: tool.annotations
999
1046
  };
1000
1047
  }
1048
+ /**
1049
+ * Invoke a tool by name with a per-call client, bypassing the client bound
1050
+ * at construction, and return the same MCP CallToolResult a tools/call
1051
+ * would.
1052
+ *
1053
+ * This lets a host that serves many users (e.g. a hosted MCP server) build
1054
+ * the toolkit ONCE and hand each request its own client, instead of
1055
+ * constructing a fresh toolkit per request and again per tool call - which,
1056
+ * under long-lived/streaming connections, retains a per-call object graph
1057
+ * and drives heap growth.
1058
+ *
1059
+ * An unknown tool name returns an isError result rather than throwing, so a
1060
+ * bad name can never crash the caller. `args` is assumed already validated
1061
+ * against the tool's paramsSchema (the MCP SDK validates tools/call
1062
+ * arguments before dispatch).
1063
+ */
1064
+ async invoke(name, client, args) {
1065
+ const tool = toolsByName.get(name);
1066
+ if (!tool) {
1067
+ return {
1068
+ content: [{ type: "text", text: `Unknown tool: ${name}` }],
1069
+ isError: true
1070
+ };
1071
+ }
1072
+ return runTool(tool, client, args);
1073
+ }
1001
1074
  };
1002
1075
  // Annotate the CommonJS export names for ESM import in node:
1003
1076
  0 && (module.exports = {