shipmail-mcp 0.8.8 → 0.9.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/index.js CHANGED
@@ -19,8 +19,6 @@ Environment:
19
19
  SHIPMAIL_API_KEY when set; reduces env-trace leak surface for hosts that
20
20
  log environment variables.
21
21
  SHIPMAIL_BASE_URL Optional API base URL. Must be https. Defaults to ${DEFAULT_BASE_URL}.
22
- SHIPMAIL_ORGANIZATION_ID
23
- Optional delegated child organization ID for infrastructure tools.
24
22
  SHIPMAIL_ALLOW_INSECURE_BASE_URL=1
25
23
  Permit non-https or non-shipmail.to base URL (development only).
26
24
 
@@ -89,8 +87,7 @@ function readConfig(argv = process.argv.slice(2)) {
89
87
  const baseUrl = rawBaseUrl !== void 0 && rawBaseUrl.length > 0 ? validateBaseUrl(rawBaseUrl, allowInsecure) : void 0;
90
88
  return {
91
89
  apiKey: readApiKey(),
92
- baseUrl,
93
- organizationId: env["SHIPMAIL_ORGANIZATION_ID"] || void 0
90
+ baseUrl
94
91
  };
95
92
  }
96
93
 
@@ -266,19 +263,6 @@ var MCP_PERMISSION_GROUPS = [
266
263
  description: "Read and remove suppressed recipient addresses.",
267
264
  scopes: ["suppressions:read", "suppressions:write"],
268
265
  persistent: false
269
- },
270
- {
271
- name: "partner_admin",
272
- label: "Partner administration",
273
- description: "Manage delegated partner organizations, credential grants, and usage.",
274
- scopes: [
275
- "partner:organizations:read",
276
- "partner:organizations:write",
277
- "partner:organizations:access",
278
- "partner:mailbox_credentials:issue",
279
- "partner:usage:read"
280
- ],
281
- persistent: true
282
266
  }
283
267
  ];
284
268
  var CAPABILITY_ROWS = [
@@ -435,50 +419,7 @@ var CAPABILITY_ROWS = [
435
419
  ["shipmail_create_booking_page", "createBookingPage", "booking_pages:write"],
436
420
  ["shipmail_get_booking_page", "getBookingPage", "booking_pages:read"],
437
421
  ["shipmail_update_booking_page", "updateBookingPage", "booking_pages:write"],
438
- ["shipmail_delete_booking_page", "deleteBookingPage", "booking_pages:write"],
439
- ["shipmail_list_partner_organizations", "listPartnerOrganizations", "partner:organizations:read"],
440
- [
441
- "shipmail_create_partner_organization",
442
- "createPartnerOrganization",
443
- "partner:organizations:write"
444
- ],
445
- ["shipmail_get_partner_organization", "getPartnerOrganization", "partner:organizations:read"],
446
- [
447
- "shipmail_update_partner_organization",
448
- "updatePartnerOrganization",
449
- "partner:organizations:write"
450
- ],
451
- [
452
- "shipmail_resend_partner_ownership_invitation",
453
- "resendPartnerOwnershipInvitation",
454
- "partner:organizations:write"
455
- ],
456
- [
457
- "shipmail_suspend_partner_organization",
458
- "suspendPartnerOrganization",
459
- "partner:organizations:write"
460
- ],
461
- [
462
- "shipmail_resume_partner_organization",
463
- "resumePartnerOrganization",
464
- "partner:organizations:write"
465
- ],
466
- [
467
- "shipmail_offboard_partner_organization",
468
- "offboardPartnerOrganization",
469
- "partner:organizations:write"
470
- ],
471
- [
472
- "shipmail_list_partner_mailbox_credential_grants",
473
- "listPartnerMailboxCredentialGrants",
474
- "partner:mailbox_credentials:issue"
475
- ],
476
- [
477
- "shipmail_consume_partner_mailbox_credential_grant",
478
- "consumePartnerMailboxCredentialGrant",
479
- "partner:mailbox_credentials:issue"
480
- ],
481
- ["shipmail_get_partner_usage", "getPartnerUsage", "partner:usage:read"]
422
+ ["shipmail_delete_booking_page", "deleteBookingPage", "booking_pages:write"]
482
423
  ];
483
424
  var MEMBER_GROUPS = new Set(
484
425
  MCP_MEMBER_PERMISSION_GROUP_NAMES
@@ -487,14 +428,12 @@ var DESTRUCTIVE_PREFIXES = [
487
428
  "shipmail_delete_",
488
429
  "shipmail_remove_",
489
430
  "shipmail_revoke_",
490
- "shipmail_offboard_",
491
431
  "shipmail_cancel_"
492
432
  ];
493
433
  var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
494
434
  "shipmail_create_mailbox_app_password",
495
435
  "shipmail_create_mailbox_export",
496
436
  "shipmail_rotate_webhook_secret",
497
- "shipmail_consume_partner_mailbox_credential_grant",
498
437
  "shipmail_rotate_audience_feed",
499
438
  "shipmail_revoke_audience_feed"
500
439
  ]);
@@ -541,7 +480,6 @@ var OPEN_WORLD_READ_SCOPES = /* @__PURE__ */ new Set([
541
480
  ]);
542
481
  function permissionGroupFor(toolName, requiredScope) {
543
482
  if (requiredScope === "public") return "account_status";
544
- if (requiredScope.startsWith("partner:")) return "partner_admin";
545
483
  if (requiredScope.startsWith("domains:")) return "domain_admin";
546
484
  if (requiredScope.startsWith("mailbox_credentials:")) return "credentials";
547
485
  if (requiredScope.startsWith("mailbox_forwarding:")) return "forwarding";
@@ -582,7 +520,7 @@ function recipientControlFor(toolName) {
582
520
  }
583
521
  function durationFor(toolName) {
584
522
  if (toolName === "shipmail_run_automation") return "one_shot";
585
- if (toolName.includes("app_password") || toolName.includes("credential_grant") || toolName === "shipmail_rotate_webhook_secret") {
523
+ if (toolName.includes("app_password") || toolName === "shipmail_rotate_webhook_secret") {
586
524
  return "credential";
587
525
  }
588
526
  if (toolName.includes("forwarding") || toolName.includes("mailbox_rule") || toolName.includes("webhook") || toolName.includes("booking_page") || toolName.includes("automation")) {
@@ -595,7 +533,7 @@ function durationFor(toolName) {
595
533
  }
596
534
  function idempotencyFor(toolName, effect) {
597
535
  if (effect === "read") return "none";
598
- if (toolName === "shipmail_create_mailbox_app_password" || toolName === "shipmail_consume_partner_mailbox_credential_grant" || toolName === "shipmail_prepare_newsletter_asset_upload" || toolName === "shipmail_prepare_staged_attachment_upload") {
536
+ if (toolName === "shipmail_create_mailbox_app_password" || toolName === "shipmail_prepare_newsletter_asset_upload" || toolName === "shipmail_prepare_staged_attachment_upload") {
599
537
  return "forbidden";
600
538
  }
601
539
  if (DESTRUCTIVE_PREFIXES.some((prefix) => toolName.startsWith(prefix)) || toolName.startsWith("shipmail_suspend_") || toolName.startsWith("shipmail_resume_")) {
@@ -626,7 +564,7 @@ function defineCapability(row) {
626
564
  },
627
565
  auditAction: effect === "read" ? null : "api.mutation",
628
566
  transports: {
629
- hostedOAuth: permissionGroup !== "partner_admin",
567
+ hostedOAuth: true,
630
568
  directApiKeyHttp: true,
631
569
  stdio: toolName !== "shipmail_compose_message_with_file" && toolName !== "shipmail_upload_newsletter_asset_with_file"
632
570
  }
@@ -740,7 +678,7 @@ var MAX_STRING_LENGTH = 16384;
740
678
  var TRUNCATION_MARKER = "\n\u2026[truncated]";
741
679
  var DANGEROUS_CHARS_REGEX = (
742
680
  // eslint-disable-next-line no-control-regex
743
- /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F؜‎‏‪-‮⁦-⁩]/gu
681
+ /[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\u061C\u200B\u200E\u200F\u202A-\u202E\u2060\u2066-\u2069\uFEFF\u{E0000}-\u{E007F}]/gu
744
682
  );
745
683
  function sanitizeString(value, maxLength = MAX_STRING_LENGTH) {
746
684
  const stripped = value.replace(DANGEROUS_CHARS_REGEX, "");
@@ -751,26 +689,25 @@ function sanitizeString(value, maxLength = MAX_STRING_LENGTH) {
751
689
  function isPlainObject(value) {
752
690
  return typeof value === "object" && value !== null && !Array.isArray(value);
753
691
  }
754
- function sanitizeValue(value, maxStringLength = MAX_STRING_LENGTH) {
692
+ function childField(field, key) {
693
+ return field !== void 0 && field.path[0] === key ? { path: field.path.slice(1), maxLength: field.maxLength } : void 0;
694
+ }
695
+ function sanitizeValue(value, longField) {
755
696
  if (typeof value === "string") {
756
- return sanitizeString(value, maxStringLength);
697
+ return sanitizeString(value, longField?.path.length === 0 ? longField.maxLength : void 0);
757
698
  }
758
699
  if (Array.isArray(value)) {
759
- return value.map((item) => sanitizeValue(item, maxStringLength));
700
+ return value.map((item) => sanitizeValue(item));
760
701
  }
761
702
  if (isPlainObject(value)) {
762
- const out = {};
763
- for (const [key, item] of Object.entries(value)) {
764
- out[key] = sanitizeValue(item, maxStringLength);
765
- }
766
- return out;
703
+ return sanitizeRecord(value, longField);
767
704
  }
768
705
  return value;
769
706
  }
770
- function sanitizeRecord(value, maxStringLength = MAX_STRING_LENGTH) {
707
+ function sanitizeRecord(value, longField) {
771
708
  const out = {};
772
709
  for (const [key, item] of Object.entries(value)) {
773
- out[key] = sanitizeValue(item, maxStringLength);
710
+ out[key] = sanitizeValue(item, childField(longField, key));
774
711
  }
775
712
  return out;
776
713
  }
@@ -791,8 +728,8 @@ var GENERIC_INTERNAL_MESSAGE = "Internal MCP error. The original message is logg
791
728
  var MCP_RATE_LIMIT_MARKER = "[mcp.rate_limit]";
792
729
  var MCP_SCHEMA_VIOLATION_MARKER = "[mcp.schema_violation]";
793
730
  var SAFE_MARKERS = [MCP_RATE_LIMIT_MARKER, MCP_SCHEMA_VIOLATION_MARKER];
794
- function jsonResult(structuredContent) {
795
- const sanitized = sanitizeRecord(structuredContent);
731
+ function jsonResult(structuredContent, longField) {
732
+ const sanitized = sanitizeRecord(structuredContent, longField);
796
733
  return {
797
734
  content: [
798
735
  {
@@ -998,6 +935,7 @@ function isPublicHttpsUrl(rawUrl) {
998
935
  if (host.startsWith("[") && host.endsWith("]")) {
999
936
  host = host.slice(1, -1);
1000
937
  }
938
+ host = host.replace(/\.$/, "");
1001
939
  if (host.length === 0) return false;
1002
940
  for (const pattern of PRIVATE_HOST_NAME_PATTERNS) {
1003
941
  if (pattern.test(host)) return false;
@@ -1183,7 +1121,7 @@ var mailboxSchema = z.object({
1183
1121
  address: z.string(),
1184
1122
  display_name: z.string().nullable(),
1185
1123
  suspended_at: z.string().nullable(),
1186
- suspension_reasons: z.array(z.enum(["billing", "manual", "security"])),
1124
+ suspension_reasons: z.array(z.enum(["billing", "manual", "security", "abuse"])),
1187
1125
  auto_reply: autoReplySchema,
1188
1126
  created_at: z.string(),
1189
1127
  updated_at: z.string()
@@ -1244,7 +1182,7 @@ var mailboxAppPasswordSchema = z.object({
1244
1182
  mailbox_id: z.string(),
1245
1183
  name: z.string(),
1246
1184
  state: z.enum(["pending_create", "active", "pending_revoke", "revoked", "failed"]),
1247
- purpose: z.enum(["operator_client", "partner_embedded_webmail"]),
1185
+ purpose: z.enum(["operator_client"]),
1248
1186
  expires_at: z.string().nullable(),
1249
1187
  allowed_cidrs: z.array(z.string()),
1250
1188
  last_used_at: z.string().nullable(),
@@ -1254,23 +1192,6 @@ var mailboxAppPasswordSchema = z.object({
1254
1192
  var createdMailboxAppPasswordSchema = mailboxAppPasswordSchema.extend({
1255
1193
  secret: z.string()
1256
1194
  });
1257
- var partnerMailboxCredentialSchema = createdMailboxAppPasswordSchema.extend({
1258
- operator_notified: z.boolean()
1259
- });
1260
- var partnerMailboxCredentialGrantSchema = z.object({
1261
- object: z.literal("partner_mailbox_credential_grant"),
1262
- id: z.string(),
1263
- partner_organization_id: z.string(),
1264
- organization_id: z.string(),
1265
- organization_name: z.string(),
1266
- external_reference: z.string(),
1267
- operator_email: z.string(),
1268
- mailbox_id: z.string(),
1269
- mailbox_address: z.string(),
1270
- disclosure_version: z.string(),
1271
- expires_at: z.string(),
1272
- consented_at: z.string()
1273
- });
1274
1195
  var mailboxFolderSchema = z.object({
1275
1196
  object: z.literal("mailbox_folder"),
1276
1197
  id: z.string(),
@@ -1447,7 +1368,8 @@ var inboxFullMessageSchema = inboxMessageSchema.omit({ object: true }).extend({
1447
1368
  var inboxAttachmentContentSchema = inboxAttachmentSchema.omit({ download_path: true }).extend({
1448
1369
  object: z.literal("inbox_attachment_content"),
1449
1370
  mailbox_id: z.string(),
1450
- message_id: z.string()
1371
+ message_id: z.string(),
1372
+ text: z.string().optional()
1451
1373
  });
1452
1374
  var inboxMessagesSchema = z.object({
1453
1375
  object: z.literal("inbox_messages"),
@@ -1740,6 +1662,8 @@ var scheduledMessageSchema = z.object({
1740
1662
  object: z.literal("scheduled_message"),
1741
1663
  id: z.string(),
1742
1664
  scheduled_message_id: z.string(),
1665
+ // Optional so this package keeps working against a server that predates the field.
1666
+ status: z.enum(["scheduled", "settling"]).optional(),
1743
1667
  kind: z.enum(["scheduled", "undo"]),
1744
1668
  mailbox_id: z.string(),
1745
1669
  mailbox_address: z.string(),
@@ -1838,6 +1762,8 @@ var webhookSchema = z.object({
1838
1762
  events: z.array(z.enum(WEBHOOK_EVENT_TYPES)),
1839
1763
  active: z.boolean(),
1840
1764
  description: z.string().nullable(),
1765
+ mailbox_ids: z.array(z.string()).nullable(),
1766
+ domain_ids: z.array(z.string()).nullable(),
1841
1767
  created_at: z.string(),
1842
1768
  updated_at: z.string()
1843
1769
  });
@@ -1905,14 +1831,6 @@ var mailboxAppPasswordsOutputSchema = z.object({
1905
1831
  data: z.array(mailboxAppPasswordSchema)
1906
1832
  })
1907
1833
  });
1908
- var partnerMailboxCredentialOutputSchema = z.object({
1909
- credential: partnerMailboxCredentialSchema
1910
- });
1911
- var partnerMailboxCredentialGrantsOutputSchema = z.object({
1912
- grants: z.object({
1913
- data: z.array(partnerMailboxCredentialGrantSchema)
1914
- })
1915
- });
1916
1834
  var mailboxFolderOutputSchema = z.object({ folder: mailboxFolderSchema });
1917
1835
  var mailboxFoldersOutputSchema = z.object({ folders: mailboxFoldersSchema });
1918
1836
  var mailboxRulesOutputSchema = z.object({ rules: mailboxRulesSchema });
@@ -2634,10 +2552,15 @@ var webhookEventSchema = z.enum(WEBHOOK_EVENT_TYPES);
2634
2552
  var webhookDeliveryStatusSchema = z.enum(WEBHOOK_DELIVERY_STATUSES);
2635
2553
  var listWebhooksInputSchema = paginationInputSchema;
2636
2554
  var listMembersInputSchema = paginationInputSchema;
2555
+ var webhookScopeIdsInputSchema = z.array(idSchema).max(100).nullable();
2637
2556
  var createWebhookInputSchema = z.object({
2638
2557
  url: publicHttpsUrlSchema,
2639
2558
  events: z.array(webhookEventSchema).min(1).max(WEBHOOK_EVENT_TYPES.length),
2640
2559
  description: noControlString(500, "description").optional(),
2560
+ mailbox_ids: webhookScopeIdsInputSchema.optional().describe(
2561
+ "Only deliver events for these mailbox IDs (plus mailboxes of domain_ids). Omit for every mailbox. Organization events are always delivered."
2562
+ ),
2563
+ domain_ids: webhookScopeIdsInputSchema.optional().describe("Only deliver events for these domain IDs and their mailboxes."),
2641
2564
  idempotency_key: idempotencyKeySchema
2642
2565
  });
2643
2566
  var updateWebhookInputSchema = z.object({
@@ -2646,9 +2569,11 @@ var updateWebhookInputSchema = z.object({
2646
2569
  events: z.array(webhookEventSchema).min(1).max(WEBHOOK_EVENT_TYPES.length).optional(),
2647
2570
  description: noControlString(500, "description").nullable().optional(),
2648
2571
  active: z.boolean().optional(),
2572
+ mailbox_ids: webhookScopeIdsInputSchema.optional().describe("Replace the mailbox filter. Null or [] clears it."),
2573
+ domain_ids: webhookScopeIdsInputSchema.optional().describe("Replace the domain filter. Null or [] clears it."),
2649
2574
  idempotency_key: idempotencyKeySchema
2650
2575
  }).refine(
2651
- (value) => value.url !== void 0 || value.events !== void 0 || value.description !== void 0 || value.active !== void 0,
2576
+ (value) => value.url !== void 0 || value.events !== void 0 || value.description !== void 0 || value.active !== void 0 || value.mailbox_ids !== void 0 || value.domain_ids !== void 0,
2652
2577
  {
2653
2578
  message: "Provide at least one webhook field to update."
2654
2579
  }
@@ -3765,95 +3690,6 @@ var updateBookingPageInputSchema = z.object({
3765
3690
  active: z.boolean().optional(),
3766
3691
  idempotency_key: idempotencyKeySchema
3767
3692
  });
3768
- var partnerOrganizationSchema = z.object({
3769
- object: z.literal("partner_organization"),
3770
- id: z.string(),
3771
- organization_id: z.string(),
3772
- name: z.string(),
3773
- external_reference: z.string(),
3774
- owner_email: z.string(),
3775
- owner_user_id: z.string().nullable(),
3776
- status: z.enum(["pending_owner", "active", "suspended", "offboarding", "disconnected"]),
3777
- data_classification: z.enum(["internal_test", "customer"]),
3778
- mailbox_limit: z.number().int(),
3779
- delegated_permissions: z.array(z.string()),
3780
- owner_accepted_at: z.string().nullable(),
3781
- activated_at: z.string().nullable(),
3782
- suspended_at: z.string().nullable(),
3783
- disconnected_at: z.string().nullable(),
3784
- created_at: z.string(),
3785
- updated_at: z.string()
3786
- });
3787
- var partnerOrganizationOutputSchema = z.object({
3788
- organization: partnerOrganizationSchema
3789
- });
3790
- var createdPartnerOrganizationSchema = partnerOrganizationSchema.extend({
3791
- ownership_invitation: z.object({
3792
- object: z.literal("partner_ownership_invitation"),
3793
- owner_email: z.string(),
3794
- email_sent: z.boolean(),
3795
- expires_at: z.string()
3796
- }).nullable()
3797
- });
3798
- var createdPartnerOrganizationOutputSchema = z.object({
3799
- organization: createdPartnerOrganizationSchema
3800
- });
3801
- var partnerOrganizationsOutputSchema = z.object({
3802
- data: z.array(partnerOrganizationSchema)
3803
- });
3804
- var partnerInvitationOutputSchema = z.object({
3805
- invitation: z.object({
3806
- object: z.literal("partner_ownership_invitation"),
3807
- owner_email: z.string(),
3808
- email_sent: z.boolean(),
3809
- expires_at: z.string()
3810
- })
3811
- });
3812
- var partnerUsageOutputSchema = z.object({
3813
- usage: z.object({
3814
- object: z.literal("partner_usage"),
3815
- period_start: z.string(),
3816
- period_end: z.string(),
3817
- closed_through: z.string().nullable(),
3818
- active_children: z.number().int(),
3819
- active_mailboxes: z.number().int(),
3820
- child_active_seconds: z.number().int(),
3821
- mailbox_active_seconds: z.number().int(),
3822
- child_subtotal: z.number().int(),
3823
- mailbox_subtotal: z.number().int(),
3824
- minimum_shortfall: z.number().int(),
3825
- projected_total: z.number().int(),
3826
- currency: z.string()
3827
- })
3828
- });
3829
- var createPartnerOrganizationInputSchema = z.object({
3830
- name: noControlString(120, "name").min(1),
3831
- external_reference: noControlString(200, "external reference").min(1),
3832
- owner_email: emailSchema,
3833
- mailbox_limit: z.number().int().min(1).max(50).default(3),
3834
- data_classification: z.enum(["internal_test", "customer"]).default("customer"),
3835
- idempotency_key: idempotencyKeySchema
3836
- });
3837
- var partnerOrganizationByIdInputSchema = z.object({
3838
- id: idSchema.describe("Partner organization relationship ID.")
3839
- });
3840
- var updatePartnerOrganizationInputSchema = partnerOrganizationByIdInputSchema.extend({
3841
- name: noControlString(120, "name").min(1).optional(),
3842
- mailbox_limit: z.number().int().min(1).max(50).optional(),
3843
- idempotency_key: idempotencyKeySchema
3844
- }).refine((value) => value.name !== void 0 || value.mailbox_limit !== void 0, {
3845
- message: "Provide name or mailbox_limit."
3846
- });
3847
- var resendPartnerInvitationInputSchema = partnerOrganizationByIdInputSchema.extend({
3848
- owner_email: emailSchema.optional(),
3849
- idempotency_key: idempotencyKeySchema
3850
- });
3851
- var consumePartnerMailboxCredentialGrantInputSchema = z.object({
3852
- grant_id: idSchema.describe("Single-use mailbox credential grant ID."),
3853
- name: noControlString(100, "name").trim().min(1).optional(),
3854
- expires_at: z.iso.datetime().optional(),
3855
- allowed_cidrs: z.array(appPasswordCidrInputSchema).max(20).optional()
3856
- });
3857
3693
 
3858
3694
  // src/cross-organization-tools.ts
3859
3695
  var MAX_CONCURRENT_ORGANIZATIONS = 4;
@@ -5266,14 +5102,7 @@ var SESSION_LIMITS = {
5266
5102
  shipmail_delete_booking_page: 10,
5267
5103
  shipmail_create_automation: 20,
5268
5104
  shipmail_update_automation: 20,
5269
- shipmail_run_automation: 20,
5270
- shipmail_create_partner_organization: 20,
5271
- shipmail_update_partner_organization: 50,
5272
- shipmail_resend_partner_ownership_invitation: 20,
5273
- shipmail_suspend_partner_organization: 20,
5274
- shipmail_resume_partner_organization: 20,
5275
- shipmail_offboard_partner_organization: 10,
5276
- shipmail_consume_partner_mailbox_credential_grant: 20
5105
+ shipmail_run_automation: 20
5277
5106
  };
5278
5107
  var SESSION_TOTAL_LIMIT = 500;
5279
5108
  var DEBUG_ENABLED = process.env["SHIPMAIL_MCP_DEBUG"] === "1";
@@ -5299,11 +5128,25 @@ var OutputSchemaViolation = class extends Error {
5299
5128
  issues;
5300
5129
  };
5301
5130
  var MEDIA_TYPE_PATTERN = /^[a-z0-9][a-z0-9!#$&^_.+-]{0,126}\/[a-z0-9][a-z0-9!#$&^_.+-]{0,126}$/i;
5131
+ var CHARSET_PARAMETER_PATTERN = /(?:^|;)\s*charset\s*=\s*(?:"([a-z0-9][a-z0-9._-]{0,39})"|([a-z0-9][a-z0-9._-]{0,39}))(?:\s*;|$)/i;
5302
5132
  var MAX_MCP_ATTACHMENT_BYTES = 3 * 1024 * 1024;
5133
+ var MAX_MCP_ATTACHMENT_RESPONSE_BYTES = Math.ceil(MAX_MCP_ATTACHMENT_BYTES * 4 / 3) + 64 * 1024;
5303
5134
  function normalizeAttachmentContentType(contentType) {
5304
5135
  const mediaType = contentType.split(";", 1)[0]?.trim().toLowerCase();
5305
5136
  return mediaType && MEDIA_TYPE_PATTERN.test(mediaType) ? mediaType : "application/octet-stream";
5306
5137
  }
5138
+ function isTextAttachment(contentType) {
5139
+ return normalizeAttachmentContentType(contentType).startsWith("text/");
5140
+ }
5141
+ function decodeTextAttachment(bytes, contentType) {
5142
+ if (!isTextAttachment(contentType)) return null;
5143
+ const charset = contentType.match(CHARSET_PARAMETER_PATTERN)?.slice(1).find(Boolean) ?? "utf-8";
5144
+ try {
5145
+ return new TextDecoder(charset, { fatal: true }).decode(bytes);
5146
+ } catch {
5147
+ return null;
5148
+ }
5149
+ }
5307
5150
  function attachmentResourceUri(mailboxId, messageId, partId) {
5308
5151
  return `shipmail://mailboxes/${encodeURIComponent(mailboxId)}/inbox/messages/${encodeURIComponent(messageId)}/attachments/${encodeURIComponent(partId)}`;
5309
5152
  }
@@ -5397,7 +5240,7 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
5397
5240
  }
5398
5241
  callCounts.set(name, used + 1);
5399
5242
  }
5400
- async function runTool(name, outputSchema, body) {
5243
+ async function runTool(name, outputSchema, body, longField) {
5401
5244
  const start = performance2.now();
5402
5245
  try {
5403
5246
  checkRateLimit(name);
@@ -5410,7 +5253,7 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
5410
5253
  throw new OutputSchemaViolation(name, issues);
5411
5254
  }
5412
5255
  logToolCall(name, performance2.now() - start);
5413
- return jsonResult(parsed.data);
5256
+ return jsonResult(parsed.data, longField);
5414
5257
  } catch (error) {
5415
5258
  if (error instanceof OutputSchemaViolation) {
5416
5259
  process.stderr.write(
@@ -6328,13 +6171,14 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
6328
6171
  "shipmail_read_mailbox_inbox_attachment",
6329
6172
  {
6330
6173
  title: "Read Mailbox Inbox Attachment",
6331
- description: "Fetch one attachment from an exact JMAP inbox message as an embedded MCP resource. The input uses the attachment part ID.",
6174
+ description: "Fetch one attachment from an exact JMAP inbox message. Decodable text attachments are returned in attachment.text; other attachments are embedded resources. The input uses the attachment part ID.",
6332
6175
  inputSchema: readMailboxInboxAttachmentInputSchema,
6333
6176
  outputSchema: inboxAttachmentContentOutputSchema,
6334
6177
  annotations: { readOnlyHint: true, openWorldHint: true }
6335
6178
  },
6336
6179
  async ({ id, message_id, part_id }) => {
6337
6180
  let resource;
6181
+ let text;
6338
6182
  const result = await runTool(
6339
6183
  "shipmail_read_mailbox_inbox_attachment",
6340
6184
  inboxAttachmentContentOutputSchema,
@@ -6358,15 +6202,18 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
6358
6202
  if (bytes.byteLength > MAX_MCP_ATTACHMENT_BYTES) {
6359
6203
  throw new ValidationError3("Attachment exceeds the MCP read limit of 3 MB.");
6360
6204
  }
6361
- resource = {
6362
- type: "resource",
6363
- resource: {
6364
- uri: attachmentResourceUri(id, message.id, attachment.part_id),
6365
- mimeType: normalizeAttachmentContentType(attachment.content_type),
6366
- blob: Buffer.from(bytes).toString("base64")
6367
- },
6368
- annotations: { audience: ["assistant"], priority: 1 }
6369
- };
6205
+ text = decodeTextAttachment(bytes, attachment.content_type) ?? void 0;
6206
+ if (text === void 0) {
6207
+ resource = {
6208
+ type: "resource",
6209
+ resource: {
6210
+ uri: attachmentResourceUri(id, message.id, attachment.part_id),
6211
+ mimeType: isTextAttachment(attachment.content_type) ? "application/octet-stream" : normalizeAttachmentContentType(attachment.content_type),
6212
+ blob: Buffer.from(bytes).toString("base64")
6213
+ },
6214
+ annotations: { audience: ["assistant"], priority: 1 }
6215
+ };
6216
+ }
6370
6217
  return {
6371
6218
  attachment: {
6372
6219
  object: "inbox_attachment_content",
@@ -6376,13 +6223,23 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
6376
6223
  blob_id: attachment.blob_id,
6377
6224
  name: attachment.name,
6378
6225
  content_type: attachment.content_type,
6379
- size: bytes.byteLength
6226
+ size: bytes.byteLength,
6227
+ ...text !== void 0 ? { text } : {}
6380
6228
  }
6381
6229
  };
6382
- }
6230
+ },
6231
+ { path: ["attachment", "text"], maxLength: MAX_MCP_ATTACHMENT_BYTES }
6383
6232
  );
6384
- if (result.isError || resource === void 0) return result;
6385
- return { ...result, content: [...result.content, resource] };
6233
+ if (result.isError) return result;
6234
+ if (resource !== void 0) return { ...result, content: [...result.content, resource] };
6235
+ if (text !== void 0 && Buffer.byteLength(JSON.stringify(result), "utf8") > MAX_MCP_ATTACHMENT_RESPONSE_BYTES) {
6236
+ return errorResult(
6237
+ new ValidationError3(
6238
+ "Text attachment exceeds the MCP response budget after JSON encoding."
6239
+ )
6240
+ );
6241
+ }
6242
+ return result;
6386
6243
  }
6387
6244
  );
6388
6245
  });
@@ -6917,11 +6774,14 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
6917
6774
  client_reference: args.client_reference,
6918
6775
  cursor: args.cursor,
6919
6776
  limit: args.limit
6920
- } : {
6777
+ } : args.client_reference ? {
6921
6778
  client_reference: args.client_reference,
6922
6779
  cursor: args.cursor,
6923
6780
  limit: args.limit
6924
- };
6781
+ } : void 0;
6782
+ if (!params) {
6783
+ throw new Error("Provide mailbox_id or client_reference.");
6784
+ }
6925
6785
  return client.messages.list(params);
6926
6786
  })
6927
6787
  );
@@ -7305,7 +7165,7 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
7305
7165
  "shipmail_update_webhook",
7306
7166
  {
7307
7167
  title: "Update Webhook",
7308
- description: "Update webhook URL, subscribed events, description, or active state. A URL change redirects future deliveries.",
7168
+ description: "Update webhook URL, subscribed events, description, active state, or mailbox and domain filter. A URL change redirects future deliveries.",
7309
7169
  inputSchema: updateWebhookInputSchema,
7310
7170
  outputSchema: webhookOutputSchema,
7311
7171
  annotations: {
@@ -7321,6 +7181,8 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
7321
7181
  if (args.events !== void 0) update.events = args.events;
7322
7182
  if (args.description !== void 0) update.description = args.description;
7323
7183
  if (args.active !== void 0) update.active = args.active;
7184
+ if (args.mailbox_ids !== void 0) update.mailbox_ids = args.mailbox_ids;
7185
+ if (args.domain_ids !== void 0) update.domain_ids = args.domain_ids;
7324
7186
  return {
7325
7187
  webhook: await client.webhooks.update(args.id, update, mutationOptions(args))
7326
7188
  };
@@ -8596,204 +8458,6 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
8596
8458
  })
8597
8459
  );
8598
8460
  });
8599
- registerIfAllowed("shipmail_list_partner_organizations", () => {
8600
- server.registerTool(
8601
- "shipmail_list_partner_organizations",
8602
- {
8603
- title: "List Partner Organizations",
8604
- description: "List operator-owned organizations connected to the partner account.",
8605
- outputSchema: partnerOrganizationsOutputSchema,
8606
- annotations: { readOnlyHint: true, openWorldHint: false }
8607
- },
8608
- async () => runTool(
8609
- "shipmail_list_partner_organizations",
8610
- partnerOrganizationsOutputSchema,
8611
- () => client.partner.listOrganizations()
8612
- )
8613
- );
8614
- });
8615
- registerIfAllowed("shipmail_create_partner_organization", () => {
8616
- server.registerTool(
8617
- "shipmail_create_partner_organization",
8618
- {
8619
- title: "Create Partner Organization",
8620
- description: "Create an operator organization and email its ownership invitation. No domain or mailbox is created.",
8621
- inputSchema: createPartnerOrganizationInputSchema,
8622
- outputSchema: createdPartnerOrganizationOutputSchema,
8623
- annotations: {
8624
- readOnlyHint: false,
8625
- destructiveHint: false,
8626
- idempotentHint: true,
8627
- openWorldHint: false
8628
- }
8629
- },
8630
- async (args) => runTool(
8631
- "shipmail_create_partner_organization",
8632
- createdPartnerOrganizationOutputSchema,
8633
- async () => ({
8634
- organization: await client.partner.createOrganization(
8635
- stripIdempotencyKey(args),
8636
- mutationOptions(args)
8637
- )
8638
- })
8639
- )
8640
- );
8641
- });
8642
- registerIfAllowed("shipmail_get_partner_organization", () => {
8643
- server.registerTool(
8644
- "shipmail_get_partner_organization",
8645
- {
8646
- title: "Get Partner Organization",
8647
- description: "Get one partner child relationship and its ownership state.",
8648
- inputSchema: partnerOrganizationByIdInputSchema,
8649
- outputSchema: partnerOrganizationOutputSchema,
8650
- annotations: { readOnlyHint: true, openWorldHint: false }
8651
- },
8652
- async ({ id }) => runTool("shipmail_get_partner_organization", partnerOrganizationOutputSchema, async () => ({
8653
- organization: await client.partner.getOrganization(id)
8654
- }))
8655
- );
8656
- });
8657
- registerIfAllowed("shipmail_update_partner_organization", () => {
8658
- server.registerTool(
8659
- "shipmail_update_partner_organization",
8660
- {
8661
- title: "Update Partner Organization",
8662
- description: "Update an operator organization name or mailbox allocation.",
8663
- inputSchema: updatePartnerOrganizationInputSchema,
8664
- outputSchema: partnerOrganizationOutputSchema,
8665
- annotations: {
8666
- readOnlyHint: false,
8667
- destructiveHint: false,
8668
- idempotentHint: true,
8669
- openWorldHint: false
8670
- }
8671
- },
8672
- async (args) => runTool(
8673
- "shipmail_update_partner_organization",
8674
- partnerOrganizationOutputSchema,
8675
- async () => {
8676
- const { id, idempotency_key: _key, ...params } = args;
8677
- return {
8678
- organization: await client.partner.updateOrganization(
8679
- id,
8680
- params,
8681
- mutationOptions(args)
8682
- )
8683
- };
8684
- }
8685
- )
8686
- );
8687
- });
8688
- registerIfAllowed("shipmail_resend_partner_ownership_invitation", () => {
8689
- server.registerTool(
8690
- "shipmail_resend_partner_ownership_invitation",
8691
- {
8692
- title: "Resend Partner Ownership Invitation",
8693
- description: "Revoke the pending ownership link and email a new single-use link.",
8694
- inputSchema: resendPartnerInvitationInputSchema,
8695
- outputSchema: partnerInvitationOutputSchema,
8696
- annotations: {
8697
- readOnlyHint: false,
8698
- destructiveHint: false,
8699
- idempotentHint: true,
8700
- openWorldHint: true
8701
- }
8702
- },
8703
- async (args) => runTool(
8704
- "shipmail_resend_partner_ownership_invitation",
8705
- partnerInvitationOutputSchema,
8706
- async () => ({
8707
- invitation: await client.partner.resendOwnershipInvitation(
8708
- args.id,
8709
- { owner_email: args.owner_email },
8710
- mutationOptions(args)
8711
- )
8712
- })
8713
- )
8714
- );
8715
- });
8716
- for (const transition of ["suspend", "resume", "offboard"]) {
8717
- const toolName = `shipmail_${transition}_partner_organization`;
8718
- registerIfAllowed(toolName, () => {
8719
- server.registerTool(
8720
- toolName,
8721
- {
8722
- title: `${transition[0]?.toUpperCase() ?? ""}${transition.slice(1)} Partner Organization`,
8723
- description: transition === "suspend" ? "Suspend outbound sending for one operator organization. Inbound mail and storage continue." : transition === "resume" ? "Resume partner-managed outbound sending for one operator organization." : "Start non-destructive offboarding and immediately remove delegated access.",
8724
- inputSchema: partnerOrganizationByIdInputSchema,
8725
- outputSchema: partnerOrganizationOutputSchema,
8726
- annotations: {
8727
- readOnlyHint: false,
8728
- destructiveHint: transition !== "resume",
8729
- idempotentHint: true,
8730
- openWorldHint: false
8731
- }
8732
- },
8733
- async ({ id }) => runTool(toolName, partnerOrganizationOutputSchema, async () => ({
8734
- organization: transition === "suspend" ? await client.partner.suspendOrganization(id) : transition === "resume" ? await client.partner.resumeOrganization(id) : await client.partner.offboardOrganization(id)
8735
- }))
8736
- );
8737
- });
8738
- }
8739
- registerIfAllowed("shipmail_consume_partner_mailbox_credential_grant", () => {
8740
- server.registerTool(
8741
- "shipmail_consume_partner_mailbox_credential_grant",
8742
- {
8743
- title: "Consume Partner Mailbox Credential Grant",
8744
- description: "Consume a single-use grant and issue an embedded-webmail credential. The secret appears once and the operator receives a notification.",
8745
- inputSchema: consumePartnerMailboxCredentialGrantInputSchema,
8746
- outputSchema: partnerMailboxCredentialOutputSchema,
8747
- annotations: {
8748
- readOnlyHint: false,
8749
- destructiveHint: true,
8750
- idempotentHint: false,
8751
- openWorldHint: true
8752
- }
8753
- },
8754
- async (args) => runTool(
8755
- "shipmail_consume_partner_mailbox_credential_grant",
8756
- partnerMailboxCredentialOutputSchema,
8757
- async () => ({
8758
- credential: await client.partner.consumeMailboxCredentialGrant(args.grant_id, {
8759
- ...args.name ? { name: args.name } : {},
8760
- ...args.expires_at ? { expires_at: args.expires_at } : {},
8761
- ...args.allowed_cidrs ? { allowed_cidrs: args.allowed_cidrs } : {}
8762
- })
8763
- })
8764
- )
8765
- );
8766
- });
8767
- registerIfAllowed("shipmail_list_partner_mailbox_credential_grants", () => {
8768
- server.registerTool(
8769
- "shipmail_list_partner_mailbox_credential_grants",
8770
- {
8771
- title: "List Partner Mailbox Credential Grants",
8772
- description: "List active ten-minute mailbox approvals created by operator owners. Grant metadata contains no app-password secret.",
8773
- outputSchema: partnerMailboxCredentialGrantsOutputSchema,
8774
- annotations: { readOnlyHint: true, openWorldHint: false }
8775
- },
8776
- async () => runTool(
8777
- "shipmail_list_partner_mailbox_credential_grants",
8778
- partnerMailboxCredentialGrantsOutputSchema,
8779
- async () => ({ grants: await client.partner.listMailboxCredentialGrants() })
8780
- )
8781
- );
8782
- });
8783
- registerIfAllowed("shipmail_get_partner_usage", () => {
8784
- server.registerTool(
8785
- "shipmail_get_partner_usage",
8786
- {
8787
- title: "Get Partner Usage",
8788
- description: "Get consolidated child and mailbox usage for the current UTC month.",
8789
- outputSchema: partnerUsageOutputSchema,
8790
- annotations: { readOnlyHint: true, openWorldHint: false }
8791
- },
8792
- async () => runTool("shipmail_get_partner_usage", partnerUsageOutputSchema, async () => ({
8793
- usage: await client.partner.usage()
8794
- }))
8795
- );
8796
- });
8797
8461
  if (allowedTools) {
8798
8462
  const unknown = [...allowedTools].filter((name) => !knownTools.includes(name));
8799
8463
  if (unknown.length > 0) {
@@ -8804,7 +8468,7 @@ function registerTools(rawServer, client, allowedTools, grantedOrganizations = [
8804
8468
  }
8805
8469
 
8806
8470
  // src/version.ts
8807
- var VERSION = "0.8.8";
8471
+ var VERSION = "0.9.0";
8808
8472
 
8809
8473
  // src/server.ts
8810
8474
  var SHIPMAIL_MCP_INSTRUCTIONS = `Shipmail MCP exposes the business email and calendar tools authorized by the connection's current Shipmail permissions.
@@ -8818,7 +8482,7 @@ Operational instructions:
8818
8482
  - Do not create or change mailbox rules without explicit user intent. List rules and folders before rule changes; custom folder IDs must belong to the target mailbox. Read the latest rule before updating or deleting it, and retain expected_position to detect concurrent reordering.
8819
8483
  - Do not create or delete a custom mailbox folder without explicit user intent. List folders before creating or deleting one. Deleting a custom folder moves its remaining messages to Trash and conflicts while an inbox rule or automation references it. For inbox messages, use the exact inbox ID, list folders before a custom-folder move, and move a message to Trash before permanent deletion.
8820
8484
  - Do not change a domain catch-all or enable an auto-reply without explicit user intent. A catch-all retargets unmatched-recipient mail, and an enabled auto-reply can send replies.
8821
- - Do not reset a mailbox password unless the operator supplied the replacement password. Do not create an app password without explicit operator approval; store its one-time secret securely. Do not consume a partner mailbox credential grant without explicit partner approval.
8485
+ - Do not reset a mailbox password unless the operator supplied the replacement password. Do not create an app password without explicit operator approval; store its one-time secret securely.
8822
8486
  - Webhook signing secrets appear once in the conversation log. Treat that log as sensitive and store each secret in the user's chosen secret manager.
8823
8487
  - Confirm that a recipient should receive mail again before removing a suppression. Use the subscriber state tools for subscription changes rather than a profile update. Prefer unsubscribing to removing a subscriber when opt-out history must be preserved.
8824
8488
  - Do not create, send, schedule, resume, or otherwise mutate a newsletter without the user's explicit approval. A test send requires approval of the exact draft and test recipient. Scheduling requires approval of the content, audience, and scheduled time. Resume requires confirmation that delivery should continue. Run newsletter preflight before test sending or scheduling, and obtain sender identities and existing asset IDs from their list tools.
@@ -8846,7 +8510,6 @@ function createShipmailMcpServer(config, allowedTools, organizationGrants = [])
8846
8510
  const client = new ShipmailClient({
8847
8511
  apiKey: config.apiKey,
8848
8512
  ...config.baseUrl ? { baseUrl: config.baseUrl } : {},
8849
- ...config.organizationId ? { organizationId: config.organizationId } : {},
8850
8513
  defaultHeaders
8851
8514
  });
8852
8515
  const server = new McpServer5(
@@ -8953,7 +8616,6 @@ async function main() {
8953
8616
  const client = new ShipmailClient2({
8954
8617
  apiKey: config.apiKey,
8955
8618
  ...config.baseUrl ? { baseUrl: config.baseUrl } : {},
8956
- ...config.organizationId ? { organizationId: config.organizationId } : {},
8957
8619
  defaultHeaders: {
8958
8620
  "User-Agent": `shipmail-mcp/${VERSION}`,
8959
8621
  "X-Shipmail-Client": "mcp",