shipmail-mcp 0.5.4 → 0.5.8
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/README.md +27 -14
- package/dist/capabilities.d.ts +9 -3
- package/dist/capabilities.js +15 -2
- package/dist/index.js +671 -175
- package/dist/server.d.ts +10 -2
- package/dist/server.js +3890 -3395
- package/package.json +2 -2
- package/server.json +2 -2
- package/smithery.yaml +1 -1
package/dist/index.js
CHANGED
|
@@ -12,6 +12,7 @@ var MCP_MEMBER_PERMISSION_GROUP_NAMES = [
|
|
|
12
12
|
"mail_read",
|
|
13
13
|
"drafts",
|
|
14
14
|
"mail_organize",
|
|
15
|
+
"mailbox_rules",
|
|
15
16
|
"send_mail",
|
|
16
17
|
"calendar_read",
|
|
17
18
|
"calendar_write"
|
|
@@ -80,6 +81,13 @@ var MCP_PERMISSION_GROUPS = [
|
|
|
80
81
|
scopes: ["mailbox_forwarding:read", "mailbox_forwarding:write"],
|
|
81
82
|
persistent: true
|
|
82
83
|
},
|
|
84
|
+
{
|
|
85
|
+
name: "mailbox_rules",
|
|
86
|
+
label: "Inbox rules",
|
|
87
|
+
description: "Read and manage persistent server-side sorting, move, read, and star rules.",
|
|
88
|
+
scopes: ["mailbox_rules:read", "mailbox_rules:write"],
|
|
89
|
+
persistent: true
|
|
90
|
+
},
|
|
83
91
|
{
|
|
84
92
|
name: "webhooks",
|
|
85
93
|
label: "Webhooks",
|
|
@@ -173,6 +181,8 @@ var CAPABILITY_ROWS = [
|
|
|
173
181
|
["shipmail_update_mailbox", "updateMailbox", "mailboxes:write"],
|
|
174
182
|
["shipmail_delete_mailbox", "deleteMailbox", "mailboxes:write"],
|
|
175
183
|
["shipmail_list_mailbox_folders", "listMailboxFolders", "mailboxes:read"],
|
|
184
|
+
["shipmail_get_mailbox_rules", "getMailboxRules", "mailbox_rules:read"],
|
|
185
|
+
["shipmail_set_mailbox_rules", "updateMailboxRules", "mailbox_rules:write"],
|
|
176
186
|
["shipmail_create_mailbox_folder", "createMailboxFolder", "mailboxes:write"],
|
|
177
187
|
["shipmail_update_mailbox_folder", "updateMailboxFolder", "mailboxes:write"],
|
|
178
188
|
["shipmail_delete_mailbox_folder", "deleteMailboxFolder", "mailboxes:write"],
|
|
@@ -334,7 +344,8 @@ var DESTRUCTIVE_TOOLS = /* @__PURE__ */ new Set([
|
|
|
334
344
|
"shipmail_create_mailbox_app_password",
|
|
335
345
|
"shipmail_create_mailbox_export",
|
|
336
346
|
"shipmail_rotate_webhook_secret",
|
|
337
|
-
"shipmail_consume_partner_mailbox_credential_grant"
|
|
347
|
+
"shipmail_consume_partner_mailbox_credential_grant",
|
|
348
|
+
"shipmail_set_mailbox_rules"
|
|
338
349
|
]);
|
|
339
350
|
var EXTERNAL_WRITE_TOOLS = /* @__PURE__ */ new Set([
|
|
340
351
|
"shipmail_send_message",
|
|
@@ -382,6 +393,7 @@ function permissionGroupFor(toolName, requiredScope) {
|
|
|
382
393
|
if (requiredScope.startsWith("domains:")) return "domain_admin";
|
|
383
394
|
if (requiredScope.startsWith("mailbox_credentials:")) return "credentials";
|
|
384
395
|
if (requiredScope.startsWith("mailbox_forwarding:")) return "forwarding";
|
|
396
|
+
if (requiredScope.startsWith("mailbox_rules:")) return "mailbox_rules";
|
|
385
397
|
if (requiredScope === "mailboxes:export") return "mailbox_export";
|
|
386
398
|
if (requiredScope === "drafts:write") return "drafts";
|
|
387
399
|
if (requiredScope === "messages:send") return "send_mail";
|
|
@@ -417,7 +429,7 @@ function durationFor(toolName) {
|
|
|
417
429
|
if (toolName.includes("app_password") || toolName.includes("credential_grant") || toolName === "shipmail_rotate_webhook_secret") {
|
|
418
430
|
return "credential";
|
|
419
431
|
}
|
|
420
|
-
if (toolName.includes("forwarding") || toolName.includes("webhook") || toolName.includes("booking_page")) {
|
|
432
|
+
if (toolName.includes("forwarding") || toolName.includes("mailbox_rules") || toolName.includes("webhook") || toolName.includes("booking_page")) {
|
|
421
433
|
return "persistent";
|
|
422
434
|
}
|
|
423
435
|
if (toolName === "shipmail_send_message" || toolName.includes("schedule_") || toolName.includes("scheduled_message")) {
|
|
@@ -566,14 +578,154 @@ function readConfig(argv = process.argv.slice(2)) {
|
|
|
566
578
|
}
|
|
567
579
|
|
|
568
580
|
// src/server.ts
|
|
569
|
-
import { McpServer as
|
|
581
|
+
import { McpServer as McpServer5 } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
570
582
|
import { ShipMailClient } from "shipmail";
|
|
571
583
|
|
|
572
|
-
// src/
|
|
584
|
+
// src/cross-organization-tools.ts
|
|
585
|
+
import { performance } from "perf_hooks";
|
|
573
586
|
import "@modelcontextprotocol/sdk/server/mcp.js";
|
|
574
|
-
import {
|
|
587
|
+
import { ShipMailError as ShipMailError2 } from "shipmail";
|
|
575
588
|
import { z as z2 } from "zod/v4";
|
|
576
589
|
|
|
590
|
+
// src/result.ts
|
|
591
|
+
import { ShipMailError, ValidationError } from "shipmail";
|
|
592
|
+
|
|
593
|
+
// src/sanitize.ts
|
|
594
|
+
var MAX_STRING_LENGTH = 16384;
|
|
595
|
+
var TRUNCATION_MARKER = "\n\u2026[truncated]";
|
|
596
|
+
var DANGEROUS_CHARS_REGEX = (
|
|
597
|
+
// eslint-disable-next-line no-control-regex
|
|
598
|
+
/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F--]/gu
|
|
599
|
+
);
|
|
600
|
+
function sanitizeString(value, maxLength = MAX_STRING_LENGTH) {
|
|
601
|
+
const stripped = value.replace(DANGEROUS_CHARS_REGEX, "");
|
|
602
|
+
if (stripped.length <= maxLength) return stripped;
|
|
603
|
+
const keep = Math.max(0, maxLength - TRUNCATION_MARKER.length);
|
|
604
|
+
return stripped.slice(0, keep) + TRUNCATION_MARKER;
|
|
605
|
+
}
|
|
606
|
+
function isPlainObject(value) {
|
|
607
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
608
|
+
}
|
|
609
|
+
function sanitizeValue(value, maxStringLength = MAX_STRING_LENGTH) {
|
|
610
|
+
if (typeof value === "string") {
|
|
611
|
+
return sanitizeString(value, maxStringLength);
|
|
612
|
+
}
|
|
613
|
+
if (Array.isArray(value)) {
|
|
614
|
+
return value.map((item) => sanitizeValue(item, maxStringLength));
|
|
615
|
+
}
|
|
616
|
+
if (isPlainObject(value)) {
|
|
617
|
+
const out = {};
|
|
618
|
+
for (const [key, item] of Object.entries(value)) {
|
|
619
|
+
out[key] = sanitizeValue(item, maxStringLength);
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
return value;
|
|
624
|
+
}
|
|
625
|
+
function sanitizeRecord(value, maxStringLength = MAX_STRING_LENGTH) {
|
|
626
|
+
const out = {};
|
|
627
|
+
for (const [key, item] of Object.entries(value)) {
|
|
628
|
+
out[key] = sanitizeValue(item, maxStringLength);
|
|
629
|
+
}
|
|
630
|
+
return out;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// src/result.ts
|
|
634
|
+
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
635
|
+
var SAFE_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
636
|
+
"validation_error",
|
|
637
|
+
"not_found",
|
|
638
|
+
"conflict",
|
|
639
|
+
"rate_limit_error",
|
|
640
|
+
"authentication_error",
|
|
641
|
+
"authorization_error",
|
|
642
|
+
"quota_exceeded"
|
|
643
|
+
]);
|
|
644
|
+
var GENERIC_INTERNAL_MESSAGE = "Internal MCP error. The original message is logged on the MCP server stderr.";
|
|
645
|
+
var MCP_RATE_LIMIT_MARKER = "[mcp.rate_limit]";
|
|
646
|
+
var MCP_SCHEMA_VIOLATION_MARKER = "[mcp.schema_violation]";
|
|
647
|
+
var SAFE_MARKERS = [MCP_RATE_LIMIT_MARKER, MCP_SCHEMA_VIOLATION_MARKER];
|
|
648
|
+
function jsonResult(structuredContent) {
|
|
649
|
+
const sanitized = sanitizeRecord(structuredContent);
|
|
650
|
+
return {
|
|
651
|
+
content: [
|
|
652
|
+
{
|
|
653
|
+
type: "text",
|
|
654
|
+
text: JSON.stringify(sanitized, null, 2)
|
|
655
|
+
}
|
|
656
|
+
],
|
|
657
|
+
structuredContent: sanitized
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function formatShipMailError(error) {
|
|
661
|
+
const parts = [];
|
|
662
|
+
const isSafeMessage = error.type !== void 0 && SAFE_ERROR_TYPES.has(error.type);
|
|
663
|
+
if (isSafeMessage) {
|
|
664
|
+
parts.push(sanitizeString(error.message, MAX_ERROR_MESSAGE_LENGTH));
|
|
665
|
+
} else {
|
|
666
|
+
parts.push("ShipMail request failed. Contact ShipMail support with the request_id below.");
|
|
667
|
+
}
|
|
668
|
+
if (error.type) parts.push(`type=${error.type}`);
|
|
669
|
+
if (error.status !== void 0) parts.push(`status=${error.status}`);
|
|
670
|
+
if (error.requestId) parts.push(`request_id=${error.requestId}`);
|
|
671
|
+
if (isSafeMessage && error instanceof ValidationError && error.details?.length) {
|
|
672
|
+
const fields = error.details.map((detail) => sanitizeString(detail.field, 100)).filter((field) => field.length > 0).slice(0, 25).join(", ");
|
|
673
|
+
if (fields.length > 0) parts.push(`fields=${fields}`);
|
|
674
|
+
}
|
|
675
|
+
return parts.join(" | ");
|
|
676
|
+
}
|
|
677
|
+
function safeMarkerStrip(message) {
|
|
678
|
+
for (const marker of SAFE_MARKERS) {
|
|
679
|
+
if (message.startsWith(marker)) {
|
|
680
|
+
return message.slice(marker.length).trim();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
return null;
|
|
684
|
+
}
|
|
685
|
+
function errorResult(error) {
|
|
686
|
+
let text;
|
|
687
|
+
if (error instanceof ShipMailError) {
|
|
688
|
+
text = formatShipMailError(error);
|
|
689
|
+
} else if (error instanceof Error && safeMarkerStrip(error.message) !== null) {
|
|
690
|
+
text = sanitizeString(safeMarkerStrip(error.message) ?? "", MAX_ERROR_MESSAGE_LENGTH);
|
|
691
|
+
} else {
|
|
692
|
+
if (error instanceof Error) {
|
|
693
|
+
process.stderr.write(
|
|
694
|
+
`${JSON.stringify({
|
|
695
|
+
tool: "mcp_internal_error",
|
|
696
|
+
name: error.name,
|
|
697
|
+
message: sanitizeString(error.message, MAX_ERROR_MESSAGE_LENGTH)
|
|
698
|
+
})}
|
|
699
|
+
`
|
|
700
|
+
);
|
|
701
|
+
} else {
|
|
702
|
+
process.stderr.write(
|
|
703
|
+
`${JSON.stringify({
|
|
704
|
+
tool: "mcp_internal_error",
|
|
705
|
+
message: sanitizeString(String(error), MAX_ERROR_MESSAGE_LENGTH)
|
|
706
|
+
})}
|
|
707
|
+
`
|
|
708
|
+
);
|
|
709
|
+
}
|
|
710
|
+
text = GENERIC_INTERNAL_MESSAGE;
|
|
711
|
+
}
|
|
712
|
+
return {
|
|
713
|
+
content: [{ type: "text", text }],
|
|
714
|
+
isError: true
|
|
715
|
+
};
|
|
716
|
+
}
|
|
717
|
+
function asTextResource(uri, value) {
|
|
718
|
+
return {
|
|
719
|
+
contents: [
|
|
720
|
+
{
|
|
721
|
+
uri,
|
|
722
|
+
mimeType: "application/json",
|
|
723
|
+
text: JSON.stringify(sanitizeValue(value), null, 2)
|
|
724
|
+
}
|
|
725
|
+
]
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
|
|
577
729
|
// src/schemas.ts
|
|
578
730
|
import {
|
|
579
731
|
CONFERENCING_PROVIDER_IDS,
|
|
@@ -727,6 +879,8 @@ var SYSTEM_FOLDER_NAMES = [
|
|
|
727
879
|
"junk",
|
|
728
880
|
"trash"
|
|
729
881
|
];
|
|
882
|
+
var MAILBOX_RULE_MATCH_MODES = ["all", "any"];
|
|
883
|
+
var MAILBOX_RULE_SYSTEM_TARGET_ROLES = ["inbox", "archive", "junk", "trash"];
|
|
730
884
|
var JMAP_KEYWORDS = ["$flagged", "$seen", "$draft", "$answered", "$forwarded"];
|
|
731
885
|
var NEWSLETTER_STATUSES = [
|
|
732
886
|
"draft",
|
|
@@ -748,6 +902,7 @@ var NEWSLETTER_DOMAIN_STATUSES = [
|
|
|
748
902
|
];
|
|
749
903
|
var NEWSLETTER_DOMAIN_RECORD_STATUSES = ["pending", "verified", "failed"];
|
|
750
904
|
var NEWSLETTER_ARCHIVE_VISIBILITIES = ["private", "public"];
|
|
905
|
+
var NEWSLETTER_STYLING_MODES = ["styled", "plain"];
|
|
751
906
|
var NEWSLETTER_PREFLIGHT_STATUSES = ["not_run", "passed", "warning", "failed"];
|
|
752
907
|
var NEWSLETTER_PREFLIGHT_ITEM_STATUSES = ["pass", "warn", "fail"];
|
|
753
908
|
var NEWSLETTER_TEST_SEND_STATUSES = ["pending", "sent", "failed"];
|
|
@@ -948,6 +1103,70 @@ var mailboxFoldersSchema = z.object({
|
|
|
948
1103
|
address: z.string(),
|
|
949
1104
|
data: z.array(mailboxFolderSchema)
|
|
950
1105
|
});
|
|
1106
|
+
var mailboxRuleConditionSchema = z.lazy(
|
|
1107
|
+
() => z.union([
|
|
1108
|
+
z.object({
|
|
1109
|
+
type: z.enum([
|
|
1110
|
+
"from_is",
|
|
1111
|
+
"from_contains",
|
|
1112
|
+
"recipient_is",
|
|
1113
|
+
"plus_tag_is",
|
|
1114
|
+
"subject_contains"
|
|
1115
|
+
]),
|
|
1116
|
+
value: noControlString(256, "condition value").min(1)
|
|
1117
|
+
}),
|
|
1118
|
+
z.object({
|
|
1119
|
+
type: z.enum(["has_attachment", "list_unsubscribe_exists"])
|
|
1120
|
+
}),
|
|
1121
|
+
z.object({
|
|
1122
|
+
type: z.literal("group"),
|
|
1123
|
+
match_mode: z.enum(MAILBOX_RULE_MATCH_MODES),
|
|
1124
|
+
conditions: z.array(mailboxRuleConditionSchema).min(1).max(10)
|
|
1125
|
+
})
|
|
1126
|
+
])
|
|
1127
|
+
);
|
|
1128
|
+
var mailboxRuleActionSchema = z.union([
|
|
1129
|
+
z.object({
|
|
1130
|
+
type: z.literal("move"),
|
|
1131
|
+
target: z.union([
|
|
1132
|
+
z.object({
|
|
1133
|
+
kind: z.literal("system"),
|
|
1134
|
+
role: z.enum(MAILBOX_RULE_SYSTEM_TARGET_ROLES)
|
|
1135
|
+
}),
|
|
1136
|
+
z.object({
|
|
1137
|
+
kind: z.literal("custom"),
|
|
1138
|
+
folder_id: noControlString(256, "folder_id").min(1)
|
|
1139
|
+
})
|
|
1140
|
+
])
|
|
1141
|
+
}),
|
|
1142
|
+
z.object({ type: z.enum(["mark_read", "star"]) }),
|
|
1143
|
+
z.object({ type: z.literal("send_webhook") }).strict()
|
|
1144
|
+
]);
|
|
1145
|
+
var mailboxRuleSchema = z.object({
|
|
1146
|
+
id: z.uuid("Rule ID must be a UUID."),
|
|
1147
|
+
name: noControlString(120, "name").trim().min(1),
|
|
1148
|
+
enabled: z.boolean(),
|
|
1149
|
+
position: z.number().int().min(0),
|
|
1150
|
+
match_mode: z.enum(MAILBOX_RULE_MATCH_MODES),
|
|
1151
|
+
stop: z.boolean(),
|
|
1152
|
+
conditions: z.array(mailboxRuleConditionSchema).min(1).max(10),
|
|
1153
|
+
actions: z.array(mailboxRuleActionSchema).min(1).max(4)
|
|
1154
|
+
});
|
|
1155
|
+
var mailboxRulesSchema = z.object({
|
|
1156
|
+
object: z.literal("mailbox_rules"),
|
|
1157
|
+
mailbox_id: z.string(),
|
|
1158
|
+
address: z.string(),
|
|
1159
|
+
rules: z.array(mailboxRuleSchema),
|
|
1160
|
+
folders: z.array(
|
|
1161
|
+
z.object({
|
|
1162
|
+
id: z.string(),
|
|
1163
|
+
name: z.string(),
|
|
1164
|
+
parent_id: z.string().nullable(),
|
|
1165
|
+
role: z.string().nullable(),
|
|
1166
|
+
kind: z.enum(["custom", "system"])
|
|
1167
|
+
})
|
|
1168
|
+
)
|
|
1169
|
+
});
|
|
951
1170
|
var mailboxIdentitySchema = z.object({
|
|
952
1171
|
object: z.literal("mailbox_identity"),
|
|
953
1172
|
id: z.string(),
|
|
@@ -1230,6 +1449,10 @@ var messageSchema = z.object({
|
|
|
1230
1449
|
source: z.enum(MESSAGE_SOURCES),
|
|
1231
1450
|
mode: z.enum(["live", "test"]),
|
|
1232
1451
|
status: z.enum(MESSAGE_STATUSES),
|
|
1452
|
+
rule_disposition: z.object({
|
|
1453
|
+
matched_rule_ids: z.array(z.string()).describe("Inbox rule IDs that matched this inbound message, in evaluation order."),
|
|
1454
|
+
stop_rule_id: z.string().nullable().describe("Inbox rule ID that stopped further processing, if any.")
|
|
1455
|
+
}).nullable().describe("Inbox rule evaluation recorded for this tracked message at delivery."),
|
|
1233
1456
|
scheduled_at: z.string().nullable(),
|
|
1234
1457
|
created_at: z.string(),
|
|
1235
1458
|
updated_at: z.string()
|
|
@@ -1369,7 +1592,12 @@ var acknowledgmentSchema = z.object({
|
|
|
1369
1592
|
ok: z.literal(true),
|
|
1370
1593
|
id: z.string()
|
|
1371
1594
|
});
|
|
1372
|
-
var statusOutputSchema = z.object({
|
|
1595
|
+
var statusOutputSchema = z.object({
|
|
1596
|
+
status: statusSchema,
|
|
1597
|
+
// Present only when the connection covers more than one organization. Use an id here as
|
|
1598
|
+
// organization_id on other tools.
|
|
1599
|
+
organizations: z.array(z.object({ id: z.string(), name: z.string() })).optional()
|
|
1600
|
+
});
|
|
1373
1601
|
var domainOutputSchema = z.object({ domain: domainSchema });
|
|
1374
1602
|
var domainDnsRecordsOutputSchema = z.object({ dns_records: domainDnsRecordSetSchema });
|
|
1375
1603
|
var mailboxOutputSchema = z.object({ mailbox: mailboxSchema });
|
|
@@ -1396,6 +1624,7 @@ var partnerMailboxCredentialGrantsOutputSchema = z.object({
|
|
|
1396
1624
|
});
|
|
1397
1625
|
var mailboxFolderOutputSchema = z.object({ folder: mailboxFolderSchema });
|
|
1398
1626
|
var mailboxFoldersOutputSchema = z.object({ folders: mailboxFoldersSchema });
|
|
1627
|
+
var mailboxRulesOutputSchema = z.object({ rules: mailboxRulesSchema });
|
|
1399
1628
|
var mailboxIdentitiesOutputSchema = z.object({ identities: mailboxIdentitiesSchema });
|
|
1400
1629
|
var inboxMessagesOutputSchema = z.object({ inbox_messages: inboxMessagesSchema });
|
|
1401
1630
|
var inboxMessageSummariesOutputSchema = z.object({
|
|
@@ -1649,6 +1878,11 @@ var deleteMailboxFolderInputSchema = z.object({
|
|
|
1649
1878
|
id: idSchema,
|
|
1650
1879
|
folder_id: folderIdSchema
|
|
1651
1880
|
});
|
|
1881
|
+
var updateMailboxRulesInputSchema = z.object({
|
|
1882
|
+
id: idSchema,
|
|
1883
|
+
rules: z.array(mailboxRuleSchema).max(50),
|
|
1884
|
+
idempotency_key: idempotencyKeySchema
|
|
1885
|
+
});
|
|
1652
1886
|
var resetPasswordInputSchema = z.object({
|
|
1653
1887
|
id: idSchema,
|
|
1654
1888
|
password: z.string().min(8).max(128).refine((value) => /[a-z]/.test(value), "Password must include a lowercase letter.").refine((value) => /[A-Z]/.test(value), "Password must include an uppercase letter.").refine((value) => /[0-9]/.test(value), "Password must include a number."),
|
|
@@ -2163,6 +2397,9 @@ var newsletterSchema = z.object({
|
|
|
2163
2397
|
body_text: z.string().nullable(),
|
|
2164
2398
|
status: z.enum(NEWSLETTER_STATUSES),
|
|
2165
2399
|
archive_visibility: z.enum(NEWSLETTER_ARCHIVE_VISIBILITIES),
|
|
2400
|
+
styling_mode: z.enum(NEWSLETTER_STYLING_MODES).describe(
|
|
2401
|
+
"styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it."
|
|
2402
|
+
),
|
|
2166
2403
|
preflight_status: z.enum(NEWSLETTER_PREFLIGHT_STATUSES),
|
|
2167
2404
|
preflight_results: z.record(z.string(), z.unknown()),
|
|
2168
2405
|
send_window_hours: z.number().int(),
|
|
@@ -2400,7 +2637,10 @@ var newsletterDraftFieldsInputSchema = {
|
|
|
2400
2637
|
body_text: z.string().max(25e4).nullish(),
|
|
2401
2638
|
blocks: z.array(newsletterBlockInputSchema).min(1).max(200).optional(),
|
|
2402
2639
|
send_window_hours: z.number().int().min(1).max(24).optional(),
|
|
2403
|
-
archive_visibility: z.enum(NEWSLETTER_ARCHIVE_VISIBILITIES).optional()
|
|
2640
|
+
archive_visibility: z.enum(NEWSLETTER_ARCHIVE_VISIBILITIES).optional(),
|
|
2641
|
+
styling_mode: z.enum(NEWSLETTER_STYLING_MODES).optional().describe(
|
|
2642
|
+
"styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it."
|
|
2643
|
+
)
|
|
2404
2644
|
};
|
|
2405
2645
|
var createNewsletterInputSchema = z.object({
|
|
2406
2646
|
...newsletterDraftFieldsInputSchema,
|
|
@@ -2451,6 +2691,9 @@ var createNewsletterFromChangelogInputSchema = z.object({
|
|
|
2451
2691
|
}).optional(),
|
|
2452
2692
|
send_window_hours: z.number().int().min(1).max(24).optional(),
|
|
2453
2693
|
archive_visibility: z.enum(NEWSLETTER_ARCHIVE_VISIBILITIES).optional(),
|
|
2694
|
+
styling_mode: z.enum(NEWSLETTER_STYLING_MODES).optional().describe(
|
|
2695
|
+
"styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it."
|
|
2696
|
+
),
|
|
2454
2697
|
idempotency_key: idempotencyKeySchema
|
|
2455
2698
|
});
|
|
2456
2699
|
var updateNewsletterInputSchema = z.object({
|
|
@@ -2466,9 +2709,10 @@ var updateNewsletterInputSchema = z.object({
|
|
|
2466
2709
|
blocks: newsletterDraftFieldsInputSchema.blocks,
|
|
2467
2710
|
send_window_hours: newsletterDraftFieldsInputSchema.send_window_hours,
|
|
2468
2711
|
archive_visibility: newsletterDraftFieldsInputSchema.archive_visibility,
|
|
2712
|
+
styling_mode: newsletterDraftFieldsInputSchema.styling_mode,
|
|
2469
2713
|
idempotency_key: idempotencyKeySchema
|
|
2470
2714
|
}).refine(
|
|
2471
|
-
(value) => value.audience_id !== void 0 || value.newsletter_domain_id !== void 0 || value.name !== void 0 || value.subject !== void 0 || value.preview_text !== void 0 || value.from_name !== void 0 || value.body_html !== void 0 || value.body_text !== void 0 || value.blocks !== void 0 || value.send_window_hours !== void 0 || value.archive_visibility !== void 0,
|
|
2715
|
+
(value) => value.audience_id !== void 0 || value.newsletter_domain_id !== void 0 || value.name !== void 0 || value.subject !== void 0 || value.preview_text !== void 0 || value.from_name !== void 0 || value.body_html !== void 0 || value.body_text !== void 0 || value.blocks !== void 0 || value.send_window_hours !== void 0 || value.archive_visibility !== void 0 || value.styling_mode !== void 0,
|
|
2472
2716
|
{
|
|
2473
2717
|
message: "Provide at least one newsletter field to update."
|
|
2474
2718
|
}
|
|
@@ -2824,20 +3068,313 @@ var consumePartnerMailboxCredentialGrantInputSchema = z.object({
|
|
|
2824
3068
|
allowed_cidrs: z.array(appPasswordCidrInputSchema).max(20).optional()
|
|
2825
3069
|
});
|
|
2826
3070
|
|
|
3071
|
+
// src/cross-organization-tools.ts
|
|
3072
|
+
var MAX_CONCURRENT_ORGANIZATIONS = 4;
|
|
3073
|
+
var SAFE_ERROR_TYPES2 = /* @__PURE__ */ new Set([
|
|
3074
|
+
"validation_error",
|
|
3075
|
+
"authentication_error",
|
|
3076
|
+
"authorization_error",
|
|
3077
|
+
"quota_exceeded",
|
|
3078
|
+
"not_found",
|
|
3079
|
+
"rate_limit_error",
|
|
3080
|
+
"conflict"
|
|
3081
|
+
]);
|
|
3082
|
+
var cursorSchema = z2.string().regex(/^[A-Za-z0-9_\-=.+/]{1,512}$/, "Cursor must be 1-512 characters of [A-Za-z0-9_\\-=.+/].");
|
|
3083
|
+
var crossOrganizationListInputSchema = z2.object({
|
|
3084
|
+
limit: z2.number().int().min(1).max(100).default(25).describe("Maximum results to return from each organization."),
|
|
3085
|
+
cursor_by_organization: z2.record(z2.string().min(1).max(128), cursorSchema).optional().describe(
|
|
3086
|
+
"Independent pagination cursors keyed by organization ID. Copy each successful section's next_cursor into the matching key on the next call."
|
|
3087
|
+
)
|
|
3088
|
+
});
|
|
3089
|
+
var organizationSchema = z2.object({
|
|
3090
|
+
id: z2.string(),
|
|
3091
|
+
name: z2.string()
|
|
3092
|
+
});
|
|
3093
|
+
var organizationErrorSchema = z2.object({
|
|
3094
|
+
type: z2.string(),
|
|
3095
|
+
message: z2.string(),
|
|
3096
|
+
status: z2.number().int().nullable(),
|
|
3097
|
+
request_id: z2.string().nullable(),
|
|
3098
|
+
retryable: z2.boolean()
|
|
3099
|
+
});
|
|
3100
|
+
function crossOrganizationListOutputSchema(itemSchema) {
|
|
3101
|
+
return z2.object({
|
|
3102
|
+
organizations: z2.array(
|
|
3103
|
+
z2.discriminatedUnion("status", [
|
|
3104
|
+
z2.object({
|
|
3105
|
+
organization: organizationSchema,
|
|
3106
|
+
status: z2.literal("ok"),
|
|
3107
|
+
data: z2.array(itemSchema),
|
|
3108
|
+
pagination: paginationSchema
|
|
3109
|
+
}),
|
|
3110
|
+
z2.object({
|
|
3111
|
+
organization: organizationSchema,
|
|
3112
|
+
status: z2.literal("error"),
|
|
3113
|
+
error: organizationErrorSchema
|
|
3114
|
+
})
|
|
3115
|
+
])
|
|
3116
|
+
)
|
|
3117
|
+
});
|
|
3118
|
+
}
|
|
3119
|
+
var CROSS_ORGANIZATION_LIST_BEHAVIORS = [
|
|
3120
|
+
{
|
|
3121
|
+
toolName: "shipmail_list_domains_across_organizations",
|
|
3122
|
+
baseToolName: "shipmail_list_domains",
|
|
3123
|
+
title: "List Domains Across Organizations",
|
|
3124
|
+
description: "List domains in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3125
|
+
itemSchema: domainSchema,
|
|
3126
|
+
list: (client, params) => client.domains.list(params)
|
|
3127
|
+
},
|
|
3128
|
+
{
|
|
3129
|
+
toolName: "shipmail_list_mailboxes_across_organizations",
|
|
3130
|
+
baseToolName: "shipmail_list_mailboxes",
|
|
3131
|
+
title: "List Mailboxes Across Organizations",
|
|
3132
|
+
description: "List mailboxes in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3133
|
+
itemSchema: mailboxSchema,
|
|
3134
|
+
list: (client, params) => client.mailboxes.list(params)
|
|
3135
|
+
},
|
|
3136
|
+
{
|
|
3137
|
+
toolName: "shipmail_list_webhooks_across_organizations",
|
|
3138
|
+
baseToolName: "shipmail_list_webhooks",
|
|
3139
|
+
title: "List Webhooks Across Organizations",
|
|
3140
|
+
description: "List webhooks in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3141
|
+
itemSchema: webhookSchema,
|
|
3142
|
+
list: (client, params) => client.webhooks.list(params)
|
|
3143
|
+
},
|
|
3144
|
+
{
|
|
3145
|
+
toolName: "shipmail_list_suppressions_across_organizations",
|
|
3146
|
+
baseToolName: "shipmail_list_suppressions",
|
|
3147
|
+
title: "List Suppressions Across Organizations",
|
|
3148
|
+
description: "List suppressions in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3149
|
+
itemSchema: suppressionSchema,
|
|
3150
|
+
list: (client, params) => client.suppressions.list(params)
|
|
3151
|
+
},
|
|
3152
|
+
{
|
|
3153
|
+
toolName: "shipmail_list_newsletters_across_organizations",
|
|
3154
|
+
baseToolName: "shipmail_list_newsletters",
|
|
3155
|
+
title: "List Newsletters Across Organizations",
|
|
3156
|
+
description: "List newsletters in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3157
|
+
itemSchema: newsletterSchema,
|
|
3158
|
+
list: (client, params) => client.newsletters.list(params)
|
|
3159
|
+
},
|
|
3160
|
+
{
|
|
3161
|
+
toolName: "shipmail_list_newsletter_domains_across_organizations",
|
|
3162
|
+
baseToolName: "shipmail_list_newsletter_domains",
|
|
3163
|
+
title: "List Newsletter Domains Across Organizations",
|
|
3164
|
+
description: "List newsletter sending domains in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3165
|
+
itemSchema: newsletterDomainSchema,
|
|
3166
|
+
list: (client, params) => client.newsletters.domains.list(params)
|
|
3167
|
+
},
|
|
3168
|
+
{
|
|
3169
|
+
toolName: "shipmail_list_newsletter_assets_across_organizations",
|
|
3170
|
+
baseToolName: "shipmail_list_newsletter_assets",
|
|
3171
|
+
title: "List Newsletter Assets Across Organizations",
|
|
3172
|
+
description: "List newsletter assets in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3173
|
+
itemSchema: newsletterAssetSchema,
|
|
3174
|
+
list: (client, params) => client.newsletters.assets.list(params)
|
|
3175
|
+
},
|
|
3176
|
+
{
|
|
3177
|
+
toolName: "shipmail_list_audiences_across_organizations",
|
|
3178
|
+
baseToolName: "shipmail_list_audiences",
|
|
3179
|
+
title: "List Audiences Across Organizations",
|
|
3180
|
+
description: "List audiences in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3181
|
+
itemSchema: audienceSchema,
|
|
3182
|
+
list: (client, params) => client.audiences.list(params)
|
|
3183
|
+
},
|
|
3184
|
+
{
|
|
3185
|
+
toolName: "shipmail_list_booking_pages_across_organizations",
|
|
3186
|
+
baseToolName: "shipmail_list_booking_pages",
|
|
3187
|
+
title: "List Booking Pages Across Organizations",
|
|
3188
|
+
description: "List booking pages in every organization on this connection. Returns one independently paginated success or failure section per organization.",
|
|
3189
|
+
itemSchema: bookingPageSchema,
|
|
3190
|
+
list: (client, params) => client.bookingPages.list(params)
|
|
3191
|
+
}
|
|
3192
|
+
];
|
|
3193
|
+
var CROSS_ORGANIZATION_TOOL_NAMES = new Set(
|
|
3194
|
+
CROSS_ORGANIZATION_LIST_BEHAVIORS.map((behavior) => behavior.toolName)
|
|
3195
|
+
);
|
|
3196
|
+
function permissionError(behavior) {
|
|
3197
|
+
return {
|
|
3198
|
+
type: "authorization_error",
|
|
3199
|
+
message: `${behavior.baseToolName} is not permitted for this organization.`,
|
|
3200
|
+
status: 403,
|
|
3201
|
+
request_id: null,
|
|
3202
|
+
retryable: false
|
|
3203
|
+
};
|
|
3204
|
+
}
|
|
3205
|
+
function requestError(error) {
|
|
3206
|
+
if (error instanceof ShipMailError2) {
|
|
3207
|
+
const isSafeMessage = error.type !== void 0 && SAFE_ERROR_TYPES2.has(error.type);
|
|
3208
|
+
return {
|
|
3209
|
+
type: error.type ?? "api_error",
|
|
3210
|
+
message: isSafeMessage ? sanitizeString(error.message, 500) : "ShipMail could not list this organization. Contact support with the request_id.",
|
|
3211
|
+
status: error.status ?? null,
|
|
3212
|
+
request_id: error.requestId ?? null,
|
|
3213
|
+
retryable: error.retryable
|
|
3214
|
+
};
|
|
3215
|
+
}
|
|
3216
|
+
return {
|
|
3217
|
+
type: "internal_error",
|
|
3218
|
+
message: "The organization could not be listed because of an unexpected error.",
|
|
3219
|
+
status: null,
|
|
3220
|
+
request_id: null,
|
|
3221
|
+
retryable: false
|
|
3222
|
+
};
|
|
3223
|
+
}
|
|
3224
|
+
function upstreamSchemaError(toolName, organizationId, issues) {
|
|
3225
|
+
process.stderr.write(
|
|
3226
|
+
`${JSON.stringify({
|
|
3227
|
+
tool: toolName,
|
|
3228
|
+
organization_id: organizationId,
|
|
3229
|
+
output_schema_violation: issues
|
|
3230
|
+
})}
|
|
3231
|
+
`
|
|
3232
|
+
);
|
|
3233
|
+
}
|
|
3234
|
+
async function listOneOrganization(behavior, grant, args) {
|
|
3235
|
+
const organization = { id: grant.id, name: grant.name };
|
|
3236
|
+
if (!grant.allowedTools.has(behavior.baseToolName)) {
|
|
3237
|
+
return {
|
|
3238
|
+
organization,
|
|
3239
|
+
status: "error",
|
|
3240
|
+
error: permissionError(behavior)
|
|
3241
|
+
};
|
|
3242
|
+
}
|
|
3243
|
+
try {
|
|
3244
|
+
const cursor = args.cursor_by_organization?.[grant.id];
|
|
3245
|
+
const raw = await behavior.list(grant.client, {
|
|
3246
|
+
limit: args.limit,
|
|
3247
|
+
...cursor ? { cursor } : {}
|
|
3248
|
+
});
|
|
3249
|
+
const pageSchema = z2.object({
|
|
3250
|
+
data: z2.array(behavior.itemSchema),
|
|
3251
|
+
pagination: paginationSchema
|
|
3252
|
+
});
|
|
3253
|
+
const page = pageSchema.safeParse(raw);
|
|
3254
|
+
if (!page.success) {
|
|
3255
|
+
const issues = page.error.issues.slice(0, 5).map(
|
|
3256
|
+
(issue) => `${issue.path.length === 0 ? "(root)" : issue.path.join(".")}: ${issue.message}`
|
|
3257
|
+
).join("; ");
|
|
3258
|
+
upstreamSchemaError(behavior.toolName, grant.id, issues);
|
|
3259
|
+
return {
|
|
3260
|
+
organization,
|
|
3261
|
+
status: "error",
|
|
3262
|
+
error: {
|
|
3263
|
+
type: "internal_error",
|
|
3264
|
+
message: "ShipMail returned an unexpected response shape for this organization.",
|
|
3265
|
+
status: 500,
|
|
3266
|
+
request_id: null,
|
|
3267
|
+
retryable: false
|
|
3268
|
+
}
|
|
3269
|
+
};
|
|
3270
|
+
}
|
|
3271
|
+
return {
|
|
3272
|
+
organization,
|
|
3273
|
+
status: "ok",
|
|
3274
|
+
data: page.data.data,
|
|
3275
|
+
pagination: page.data.pagination
|
|
3276
|
+
};
|
|
3277
|
+
} catch (error) {
|
|
3278
|
+
return {
|
|
3279
|
+
organization,
|
|
3280
|
+
status: "error",
|
|
3281
|
+
error: requestError(error)
|
|
3282
|
+
};
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
3285
|
+
async function listEveryOrganization(behavior, grants, args) {
|
|
3286
|
+
const organizations = [];
|
|
3287
|
+
for (let offset = 0; offset < grants.length; offset += MAX_CONCURRENT_ORGANIZATIONS) {
|
|
3288
|
+
const batch = grants.slice(offset, offset + MAX_CONCURRENT_ORGANIZATIONS);
|
|
3289
|
+
const sections = await Promise.all(
|
|
3290
|
+
batch.map((grant) => listOneOrganization(behavior, grant, args))
|
|
3291
|
+
);
|
|
3292
|
+
organizations.push(...sections);
|
|
3293
|
+
}
|
|
3294
|
+
return { organizations };
|
|
3295
|
+
}
|
|
3296
|
+
function registerCrossOrganizationTools(server, grants) {
|
|
3297
|
+
if (grants.length < 2) return;
|
|
3298
|
+
for (const behavior of CROSS_ORGANIZATION_LIST_BEHAVIORS) {
|
|
3299
|
+
if (!grants.some((grant) => grant.allowedTools.has(behavior.baseToolName))) continue;
|
|
3300
|
+
const baseCapability = getMcpCapability(behavior.baseToolName);
|
|
3301
|
+
if (!baseCapability || baseCapability.effect !== "read") {
|
|
3302
|
+
throw new Error(
|
|
3303
|
+
`Cross-organization MCP tool ${behavior.toolName} must map to an audited read capability.`
|
|
3304
|
+
);
|
|
3305
|
+
}
|
|
3306
|
+
const outputSchema = crossOrganizationListOutputSchema(behavior.itemSchema);
|
|
3307
|
+
server.registerTool(
|
|
3308
|
+
behavior.toolName,
|
|
3309
|
+
{
|
|
3310
|
+
title: behavior.title,
|
|
3311
|
+
description: behavior.description,
|
|
3312
|
+
inputSchema: crossOrganizationListInputSchema,
|
|
3313
|
+
outputSchema,
|
|
3314
|
+
annotations: {
|
|
3315
|
+
...baseCapability.annotations,
|
|
3316
|
+
readOnlyHint: true,
|
|
3317
|
+
destructiveHint: false
|
|
3318
|
+
}
|
|
3319
|
+
},
|
|
3320
|
+
async (args) => {
|
|
3321
|
+
const startedAt = performance.now();
|
|
3322
|
+
const raw = await listEveryOrganization(behavior, grants, args);
|
|
3323
|
+
const parsed = outputSchema.safeParse(raw);
|
|
3324
|
+
if (!parsed.success) {
|
|
3325
|
+
process.stderr.write(
|
|
3326
|
+
`${JSON.stringify({
|
|
3327
|
+
tool: behavior.toolName,
|
|
3328
|
+
output_schema_violation: parsed.error.message
|
|
3329
|
+
})}
|
|
3330
|
+
`
|
|
3331
|
+
);
|
|
3332
|
+
return {
|
|
3333
|
+
isError: true,
|
|
3334
|
+
content: [
|
|
3335
|
+
{
|
|
3336
|
+
type: "text",
|
|
3337
|
+
text: "ShipMail could not validate the cross-organization list result."
|
|
3338
|
+
}
|
|
3339
|
+
]
|
|
3340
|
+
};
|
|
3341
|
+
}
|
|
3342
|
+
const failedOrganizations = parsed.data.organizations.filter(
|
|
3343
|
+
(section) => section.status === "error"
|
|
3344
|
+
).length;
|
|
3345
|
+
process.stderr.write(
|
|
3346
|
+
`${JSON.stringify({
|
|
3347
|
+
tool: behavior.toolName,
|
|
3348
|
+
duration_ms: Math.round(performance.now() - startedAt),
|
|
3349
|
+
organizations: grants.length,
|
|
3350
|
+
failed_organizations: failedOrganizations,
|
|
3351
|
+
status: failedOrganizations === 0 ? "ok" : failedOrganizations === grants.length ? "failed" : "partial"
|
|
3352
|
+
})}
|
|
3353
|
+
`
|
|
3354
|
+
);
|
|
3355
|
+
return jsonResult(parsed.data);
|
|
3356
|
+
}
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
|
|
2827
3361
|
// src/prompts.ts
|
|
3362
|
+
import "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3363
|
+
import { WEBHOOK_EVENT_TYPES as WEBHOOK_EVENT_TYPES2 } from "shipmail";
|
|
3364
|
+
import { z as z3 } from "zod/v4";
|
|
2828
3365
|
var MAILBOX_LOCAL_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62}[a-zA-Z0-9])?$/;
|
|
2829
3366
|
var MAILBOX_ADDRESS_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62}[a-zA-Z0-9])?@[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+$/;
|
|
2830
3367
|
var TONE_REGEX = /^[A-Za-z][A-Za-z\s-]{0,49}$/;
|
|
2831
3368
|
var LIMIT_REGEX = /^[1-9][0-9]?$|^100$/;
|
|
2832
|
-
var mailboxAddressArg =
|
|
2833
|
-
var mailboxLocalArg =
|
|
3369
|
+
var mailboxAddressArg = z3.string().max(254).regex(MAILBOX_ADDRESS_REGEX, "mailbox_address must be a valid email address.").optional();
|
|
3370
|
+
var mailboxLocalArg = z3.string().max(64).regex(MAILBOX_LOCAL_REGEX, "Mailbox local-part contains invalid characters.").optional();
|
|
2834
3371
|
var domainNameArg = domainNameSchema.optional();
|
|
2835
|
-
var toneArg =
|
|
2836
|
-
var limitArg =
|
|
2837
|
-
var urlArg =
|
|
3372
|
+
var toneArg = z3.string().regex(TONE_REGEX, "tone must be 1-50 letters/spaces/hyphens.").optional();
|
|
3373
|
+
var limitArg = z3.string().regex(LIMIT_REGEX, "limit must be an integer 1-100.").optional();
|
|
3374
|
+
var urlArg = z3.url().max(2048).refine((value) => isPublicHttpsUrl(value), {
|
|
2838
3375
|
message: "url must use https on a public host."
|
|
2839
3376
|
}).optional();
|
|
2840
|
-
var eventsArg =
|
|
3377
|
+
var eventsArg = z3.string().max(500).refine(
|
|
2841
3378
|
(value) => {
|
|
2842
3379
|
const parts = value.split(",").map((p) => p.trim()).filter((p) => p.length > 0);
|
|
2843
3380
|
if (parts.length === 0) return false;
|
|
@@ -3190,145 +3727,6 @@ function toInboxMessageSummaries(messages) {
|
|
|
3190
3727
|
};
|
|
3191
3728
|
}
|
|
3192
3729
|
|
|
3193
|
-
// src/result.ts
|
|
3194
|
-
import { ShipMailError, ValidationError } from "shipmail";
|
|
3195
|
-
|
|
3196
|
-
// src/sanitize.ts
|
|
3197
|
-
var MAX_STRING_LENGTH = 16384;
|
|
3198
|
-
var TRUNCATION_MARKER = "\n\u2026[truncated]";
|
|
3199
|
-
var DANGEROUS_CHARS_REGEX = (
|
|
3200
|
-
// eslint-disable-next-line no-control-regex
|
|
3201
|
-
/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F--]/gu
|
|
3202
|
-
);
|
|
3203
|
-
function sanitizeString(value, maxLength = MAX_STRING_LENGTH) {
|
|
3204
|
-
const stripped = value.replace(DANGEROUS_CHARS_REGEX, "");
|
|
3205
|
-
if (stripped.length <= maxLength) return stripped;
|
|
3206
|
-
const keep = Math.max(0, maxLength - TRUNCATION_MARKER.length);
|
|
3207
|
-
return stripped.slice(0, keep) + TRUNCATION_MARKER;
|
|
3208
|
-
}
|
|
3209
|
-
function isPlainObject(value) {
|
|
3210
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3211
|
-
}
|
|
3212
|
-
function sanitizeValue(value, maxStringLength = MAX_STRING_LENGTH) {
|
|
3213
|
-
if (typeof value === "string") {
|
|
3214
|
-
return sanitizeString(value, maxStringLength);
|
|
3215
|
-
}
|
|
3216
|
-
if (Array.isArray(value)) {
|
|
3217
|
-
return value.map((item) => sanitizeValue(item, maxStringLength));
|
|
3218
|
-
}
|
|
3219
|
-
if (isPlainObject(value)) {
|
|
3220
|
-
const out = {};
|
|
3221
|
-
for (const [key, item] of Object.entries(value)) {
|
|
3222
|
-
out[key] = sanitizeValue(item, maxStringLength);
|
|
3223
|
-
}
|
|
3224
|
-
return out;
|
|
3225
|
-
}
|
|
3226
|
-
return value;
|
|
3227
|
-
}
|
|
3228
|
-
function sanitizeRecord(value, maxStringLength = MAX_STRING_LENGTH) {
|
|
3229
|
-
const out = {};
|
|
3230
|
-
for (const [key, item] of Object.entries(value)) {
|
|
3231
|
-
out[key] = sanitizeValue(item, maxStringLength);
|
|
3232
|
-
}
|
|
3233
|
-
return out;
|
|
3234
|
-
}
|
|
3235
|
-
|
|
3236
|
-
// src/result.ts
|
|
3237
|
-
var MAX_ERROR_MESSAGE_LENGTH = 500;
|
|
3238
|
-
var SAFE_ERROR_TYPES = /* @__PURE__ */ new Set([
|
|
3239
|
-
"validation_error",
|
|
3240
|
-
"not_found",
|
|
3241
|
-
"conflict",
|
|
3242
|
-
"rate_limit_error",
|
|
3243
|
-
"authentication_error",
|
|
3244
|
-
"authorization_error",
|
|
3245
|
-
"quota_exceeded"
|
|
3246
|
-
]);
|
|
3247
|
-
var GENERIC_INTERNAL_MESSAGE = "Internal MCP error. The original message is logged on the MCP server stderr.";
|
|
3248
|
-
var MCP_RATE_LIMIT_MARKER = "[mcp.rate_limit]";
|
|
3249
|
-
var MCP_SCHEMA_VIOLATION_MARKER = "[mcp.schema_violation]";
|
|
3250
|
-
var SAFE_MARKERS = [MCP_RATE_LIMIT_MARKER, MCP_SCHEMA_VIOLATION_MARKER];
|
|
3251
|
-
function jsonResult(structuredContent) {
|
|
3252
|
-
const sanitized = sanitizeRecord(structuredContent);
|
|
3253
|
-
return {
|
|
3254
|
-
content: [
|
|
3255
|
-
{
|
|
3256
|
-
type: "text",
|
|
3257
|
-
text: JSON.stringify(sanitized, null, 2)
|
|
3258
|
-
}
|
|
3259
|
-
],
|
|
3260
|
-
structuredContent: sanitized
|
|
3261
|
-
};
|
|
3262
|
-
}
|
|
3263
|
-
function formatShipMailError(error) {
|
|
3264
|
-
const parts = [];
|
|
3265
|
-
const isSafeMessage = error.type !== void 0 && SAFE_ERROR_TYPES.has(error.type);
|
|
3266
|
-
if (isSafeMessage) {
|
|
3267
|
-
parts.push(sanitizeString(error.message, MAX_ERROR_MESSAGE_LENGTH));
|
|
3268
|
-
} else {
|
|
3269
|
-
parts.push("ShipMail request failed. Contact ShipMail support with the request_id below.");
|
|
3270
|
-
}
|
|
3271
|
-
if (error.type) parts.push(`type=${error.type}`);
|
|
3272
|
-
if (error.status !== void 0) parts.push(`status=${error.status}`);
|
|
3273
|
-
if (error.requestId) parts.push(`request_id=${error.requestId}`);
|
|
3274
|
-
if (isSafeMessage && error instanceof ValidationError && error.details?.length) {
|
|
3275
|
-
const fields = error.details.map((detail) => sanitizeString(detail.field, 100)).filter((field) => field.length > 0).slice(0, 25).join(", ");
|
|
3276
|
-
if (fields.length > 0) parts.push(`fields=${fields}`);
|
|
3277
|
-
}
|
|
3278
|
-
return parts.join(" | ");
|
|
3279
|
-
}
|
|
3280
|
-
function safeMarkerStrip(message) {
|
|
3281
|
-
for (const marker of SAFE_MARKERS) {
|
|
3282
|
-
if (message.startsWith(marker)) {
|
|
3283
|
-
return message.slice(marker.length).trim();
|
|
3284
|
-
}
|
|
3285
|
-
}
|
|
3286
|
-
return null;
|
|
3287
|
-
}
|
|
3288
|
-
function errorResult(error) {
|
|
3289
|
-
let text;
|
|
3290
|
-
if (error instanceof ShipMailError) {
|
|
3291
|
-
text = formatShipMailError(error);
|
|
3292
|
-
} else if (error instanceof Error && safeMarkerStrip(error.message) !== null) {
|
|
3293
|
-
text = sanitizeString(safeMarkerStrip(error.message) ?? "", MAX_ERROR_MESSAGE_LENGTH);
|
|
3294
|
-
} else {
|
|
3295
|
-
if (error instanceof Error) {
|
|
3296
|
-
process.stderr.write(
|
|
3297
|
-
`${JSON.stringify({
|
|
3298
|
-
tool: "mcp_internal_error",
|
|
3299
|
-
name: error.name,
|
|
3300
|
-
message: sanitizeString(error.message, MAX_ERROR_MESSAGE_LENGTH)
|
|
3301
|
-
})}
|
|
3302
|
-
`
|
|
3303
|
-
);
|
|
3304
|
-
} else {
|
|
3305
|
-
process.stderr.write(
|
|
3306
|
-
`${JSON.stringify({
|
|
3307
|
-
tool: "mcp_internal_error",
|
|
3308
|
-
message: sanitizeString(String(error), MAX_ERROR_MESSAGE_LENGTH)
|
|
3309
|
-
})}
|
|
3310
|
-
`
|
|
3311
|
-
);
|
|
3312
|
-
}
|
|
3313
|
-
text = GENERIC_INTERNAL_MESSAGE;
|
|
3314
|
-
}
|
|
3315
|
-
return {
|
|
3316
|
-
content: [{ type: "text", text }],
|
|
3317
|
-
isError: true
|
|
3318
|
-
};
|
|
3319
|
-
}
|
|
3320
|
-
function asTextResource(uri, value) {
|
|
3321
|
-
return {
|
|
3322
|
-
contents: [
|
|
3323
|
-
{
|
|
3324
|
-
uri,
|
|
3325
|
-
mimeType: "application/json",
|
|
3326
|
-
text: JSON.stringify(sanitizeValue(value), null, 2)
|
|
3327
|
-
}
|
|
3328
|
-
]
|
|
3329
|
-
};
|
|
3330
|
-
}
|
|
3331
|
-
|
|
3332
3730
|
// src/resources.ts
|
|
3333
3731
|
var JSON_MIME = "application/json";
|
|
3334
3732
|
function resourceConfig(title, description) {
|
|
@@ -3448,6 +3846,15 @@ function registerResources(server, client, componentConnectDomain2 = "https://sh
|
|
|
3448
3846
|
return asTextResource(uri.toString(), await client.mailboxes.listIdentities(id));
|
|
3449
3847
|
}
|
|
3450
3848
|
);
|
|
3849
|
+
server.registerResource(
|
|
3850
|
+
"shipmail_mailbox_rules",
|
|
3851
|
+
new ResourceTemplate("shipmail://mailboxes/{id}/rules", { list: void 0 }),
|
|
3852
|
+
resourceConfig("ShipMail Mailbox Rules", "Server-side inbox rules and target folders."),
|
|
3853
|
+
async (uri, variables) => {
|
|
3854
|
+
const id = readId(variables);
|
|
3855
|
+
return asTextResource(uri.toString(), await client.mailboxes.getRules(id));
|
|
3856
|
+
}
|
|
3857
|
+
);
|
|
3451
3858
|
server.registerResource(
|
|
3452
3859
|
"shipmail_mailbox_inbox_messages",
|
|
3453
3860
|
new ResourceTemplate("shipmail://mailboxes/{id}/inbox/messages", { list: void 0 }),
|
|
@@ -3510,11 +3917,12 @@ function registerResources(server, client, componentConnectDomain2 = "https://sh
|
|
|
3510
3917
|
|
|
3511
3918
|
// src/tools.ts
|
|
3512
3919
|
import { randomUUID } from "crypto";
|
|
3513
|
-
import { performance } from "perf_hooks";
|
|
3920
|
+
import { performance as performance2 } from "perf_hooks";
|
|
3514
3921
|
import "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3515
3922
|
import {
|
|
3516
|
-
ShipMailError as
|
|
3923
|
+
ShipMailError as ShipMailError3
|
|
3517
3924
|
} from "shipmail";
|
|
3925
|
+
import { z as z4 } from "zod/v4";
|
|
3518
3926
|
var SESSION_LIMITS = {
|
|
3519
3927
|
shipmail_inject_sandbox_inbound: 20,
|
|
3520
3928
|
shipmail_compose_message_with_file: 10,
|
|
@@ -3556,6 +3964,7 @@ var SESSION_LIMITS = {
|
|
|
3556
3964
|
shipmail_reset_mailbox_password: 10,
|
|
3557
3965
|
shipmail_create_mailbox_forwarding: 10,
|
|
3558
3966
|
shipmail_delete_mailbox_forwarding: 10,
|
|
3967
|
+
shipmail_set_mailbox_rules: 20,
|
|
3559
3968
|
shipmail_set_auto_reply: 20,
|
|
3560
3969
|
shipmail_set_spam_filter: 20,
|
|
3561
3970
|
shipmail_update_inbox_message: 50,
|
|
@@ -3626,7 +4035,7 @@ function logToolCall(name, durationMs, error) {
|
|
|
3626
4035
|
tool: name,
|
|
3627
4036
|
duration_ms: Math.round(durationMs)
|
|
3628
4037
|
};
|
|
3629
|
-
if (error instanceof
|
|
4038
|
+
if (error instanceof ShipMailError3) {
|
|
3630
4039
|
entry["error_type"] = error.type ?? "unknown";
|
|
3631
4040
|
if (DEBUG_ENABLED) {
|
|
3632
4041
|
if (error.requestId) entry["request_id"] = error.requestId;
|
|
@@ -3640,7 +4049,25 @@ function logToolCall(name, durationMs, error) {
|
|
|
3640
4049
|
process.stderr.write(`${JSON.stringify(entry)}
|
|
3641
4050
|
`);
|
|
3642
4051
|
}
|
|
3643
|
-
function
|
|
4052
|
+
function organizationField(organizationIds) {
|
|
4053
|
+
return z4.string().optional().describe(
|
|
4054
|
+
`Organization to act in. This connection covers ${organizationIds.length} organizations: ${organizationIds.join(", ")}. Omit only when the target is unambiguous.`
|
|
4055
|
+
);
|
|
4056
|
+
}
|
|
4057
|
+
function withOrganizationParam(inputSchema, organizationIds) {
|
|
4058
|
+
if (organizationIds.length < 2) return inputSchema;
|
|
4059
|
+
if (inputSchema === void 0 || typeof inputSchema !== "object" || inputSchema === null) {
|
|
4060
|
+
return inputSchema;
|
|
4061
|
+
}
|
|
4062
|
+
if ("_def" in inputSchema || "~standard" in inputSchema) {
|
|
4063
|
+
const schema = inputSchema;
|
|
4064
|
+
if (typeof schema.extend !== "function") return inputSchema;
|
|
4065
|
+
return schema.extend({ organization_id: organizationField(organizationIds) });
|
|
4066
|
+
}
|
|
4067
|
+
return { ...inputSchema, organization_id: organizationField(organizationIds) };
|
|
4068
|
+
}
|
|
4069
|
+
function registerTools(rawServer, client, allowedTools, grantedOrganizations = []) {
|
|
4070
|
+
const organizationIds = grantedOrganizations.map((entry) => entry.id);
|
|
3644
4071
|
const knownTools = [];
|
|
3645
4072
|
const enabledTools = [];
|
|
3646
4073
|
const callCounts = /* @__PURE__ */ new Map();
|
|
@@ -3655,6 +4082,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3655
4082
|
name,
|
|
3656
4083
|
{
|
|
3657
4084
|
...config,
|
|
4085
|
+
...config.inputSchema === void 0 ? {} : { inputSchema: withOrganizationParam(config.inputSchema, organizationIds) },
|
|
3658
4086
|
annotations: {
|
|
3659
4087
|
...config.annotations,
|
|
3660
4088
|
...capability.annotations
|
|
@@ -3688,7 +4116,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3688
4116
|
callCounts.set(name, used + 1);
|
|
3689
4117
|
}
|
|
3690
4118
|
async function runTool(name, outputSchema, body) {
|
|
3691
|
-
const start =
|
|
4119
|
+
const start = performance2.now();
|
|
3692
4120
|
try {
|
|
3693
4121
|
checkRateLimit(name);
|
|
3694
4122
|
const raw = await body();
|
|
@@ -3699,7 +4127,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3699
4127
|
).join("; ");
|
|
3700
4128
|
throw new OutputSchemaViolation(name, issues);
|
|
3701
4129
|
}
|
|
3702
|
-
logToolCall(name,
|
|
4130
|
+
logToolCall(name, performance2.now() - start);
|
|
3703
4131
|
return jsonResult(parsed.data);
|
|
3704
4132
|
} catch (error) {
|
|
3705
4133
|
if (error instanceof OutputSchemaViolation) {
|
|
@@ -3708,12 +4136,12 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3708
4136
|
`
|
|
3709
4137
|
);
|
|
3710
4138
|
}
|
|
3711
|
-
logToolCall(name,
|
|
4139
|
+
logToolCall(name, performance2.now() - start, error instanceof Error ? error : void 0);
|
|
3712
4140
|
return errorResult(error);
|
|
3713
4141
|
}
|
|
3714
4142
|
}
|
|
3715
4143
|
async function runToolWithMeta(name, outputSchema, body) {
|
|
3716
|
-
const start =
|
|
4144
|
+
const start = performance2.now();
|
|
3717
4145
|
try {
|
|
3718
4146
|
checkRateLimit(name);
|
|
3719
4147
|
const raw = await body();
|
|
@@ -3724,7 +4152,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3724
4152
|
).join("; ");
|
|
3725
4153
|
throw new OutputSchemaViolation(name, issues);
|
|
3726
4154
|
}
|
|
3727
|
-
logToolCall(name,
|
|
4155
|
+
logToolCall(name, performance2.now() - start);
|
|
3728
4156
|
return { ...jsonResult(parsed.data), _meta: { ...raw.meta } };
|
|
3729
4157
|
} catch (error) {
|
|
3730
4158
|
if (error instanceof OutputSchemaViolation) {
|
|
@@ -3733,7 +4161,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3733
4161
|
`
|
|
3734
4162
|
);
|
|
3735
4163
|
}
|
|
3736
|
-
logToolCall(name,
|
|
4164
|
+
logToolCall(name, performance2.now() - start, error instanceof Error ? error : void 0);
|
|
3737
4165
|
return errorResult(error);
|
|
3738
4166
|
}
|
|
3739
4167
|
}
|
|
@@ -3747,7 +4175,16 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
3747
4175
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
3748
4176
|
},
|
|
3749
4177
|
async () => runTool("shipmail_status", statusOutputSchema, async () => ({
|
|
3750
|
-
status: await client.status.get()
|
|
4178
|
+
status: await client.status.get(),
|
|
4179
|
+
// Reported here rather than from a tool of its own: status is always available on every
|
|
4180
|
+
// profile, and it is what a caller checks before starting work. Without it the router's
|
|
4181
|
+
// "pass organization_id" error names ids the caller cannot resolve to anything.
|
|
4182
|
+
...grantedOrganizations.length > 1 ? {
|
|
4183
|
+
organizations: grantedOrganizations.map((entry) => ({
|
|
4184
|
+
id: entry.id,
|
|
4185
|
+
name: entry.name
|
|
4186
|
+
}))
|
|
4187
|
+
} : {}
|
|
3751
4188
|
}))
|
|
3752
4189
|
);
|
|
3753
4190
|
});
|
|
@@ -4262,7 +4699,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
4262
4699
|
"shipmail_list_mailbox_folders",
|
|
4263
4700
|
{
|
|
4264
4701
|
title: "List Mailbox Folders",
|
|
4265
|
-
description: "List system and custom folders for a mailbox, including unread counts and folder IDs for Assistant automations.",
|
|
4702
|
+
description: "List system and custom folders for a mailbox, including unread counts and folder IDs for inbox rules and Assistant automations.",
|
|
4266
4703
|
inputSchema: getByIdInputSchema,
|
|
4267
4704
|
outputSchema: mailboxFoldersOutputSchema,
|
|
4268
4705
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
@@ -4272,6 +4709,45 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
4272
4709
|
}))
|
|
4273
4710
|
);
|
|
4274
4711
|
});
|
|
4712
|
+
registerIfAllowed("shipmail_get_mailbox_rules", () => {
|
|
4713
|
+
server.registerTool(
|
|
4714
|
+
"shipmail_get_mailbox_rules",
|
|
4715
|
+
{
|
|
4716
|
+
title: "Get Mailbox Rules",
|
|
4717
|
+
description: "List deterministic server-side inbox rules and the available destination folders for a mailbox.",
|
|
4718
|
+
inputSchema: getByIdInputSchema,
|
|
4719
|
+
outputSchema: mailboxRulesOutputSchema,
|
|
4720
|
+
annotations: { readOnlyHint: true, openWorldHint: false }
|
|
4721
|
+
},
|
|
4722
|
+
async ({ id }) => runTool("shipmail_get_mailbox_rules", mailboxRulesOutputSchema, async () => ({
|
|
4723
|
+
rules: await client.mailboxes.getRules(id)
|
|
4724
|
+
}))
|
|
4725
|
+
);
|
|
4726
|
+
});
|
|
4727
|
+
registerIfAllowed("shipmail_set_mailbox_rules", () => {
|
|
4728
|
+
server.registerTool(
|
|
4729
|
+
"shipmail_set_mailbox_rules",
|
|
4730
|
+
{
|
|
4731
|
+
title: "Set Mailbox Rules",
|
|
4732
|
+
description: "Replace every deterministic server-side inbox rule for a mailbox. Call shipmail_get_mailbox_rules first and preserve any rules that should remain.",
|
|
4733
|
+
inputSchema: updateMailboxRulesInputSchema,
|
|
4734
|
+
outputSchema: mailboxRulesOutputSchema,
|
|
4735
|
+
annotations: {
|
|
4736
|
+
readOnlyHint: false,
|
|
4737
|
+
destructiveHint: true,
|
|
4738
|
+
idempotentHint: true,
|
|
4739
|
+
openWorldHint: false
|
|
4740
|
+
}
|
|
4741
|
+
},
|
|
4742
|
+
async (args) => runTool("shipmail_set_mailbox_rules", mailboxRulesOutputSchema, async () => ({
|
|
4743
|
+
rules: await client.mailboxes.updateRules(
|
|
4744
|
+
args.id,
|
|
4745
|
+
{ rules: args.rules },
|
|
4746
|
+
mutationOptions(args)
|
|
4747
|
+
)
|
|
4748
|
+
}))
|
|
4749
|
+
);
|
|
4750
|
+
});
|
|
4275
4751
|
registerIfAllowed("shipmail_create_mailbox_folder", () => {
|
|
4276
4752
|
server.registerTool(
|
|
4277
4753
|
"shipmail_create_mailbox_folder",
|
|
@@ -4301,7 +4777,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
4301
4777
|
"shipmail_update_mailbox_folder",
|
|
4302
4778
|
{
|
|
4303
4779
|
title: "Update Mailbox Folder",
|
|
4304
|
-
description: "Rename a custom mailbox folder. System folders cannot be renamed
|
|
4780
|
+
description: "Rename a custom mailbox folder. System folders cannot be renamed. Inbox rules and Assistant automations keep the stable folder ID.",
|
|
4305
4781
|
inputSchema: updateMailboxFolderInputSchema,
|
|
4306
4782
|
outputSchema: mailboxFolderOutputSchema,
|
|
4307
4783
|
annotations: {
|
|
@@ -4326,7 +4802,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
4326
4802
|
"shipmail_delete_mailbox_folder",
|
|
4327
4803
|
{
|
|
4328
4804
|
title: "Delete Mailbox Folder",
|
|
4329
|
-
description: "Delete a custom mailbox folder after moving its messages to Trash.
|
|
4805
|
+
description: "Delete a custom mailbox folder after moving its messages to Trash. Remove references from inbox rules and Assistant automations first.",
|
|
4330
4806
|
inputSchema: deleteMailboxFolderInputSchema,
|
|
4331
4807
|
outputSchema: acknowledgmentOutputSchema,
|
|
4332
4808
|
annotations: {
|
|
@@ -5528,7 +6004,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
5528
6004
|
"shipmail_create_newsletter",
|
|
5529
6005
|
{
|
|
5530
6006
|
title: "Create Newsletter",
|
|
5531
|
-
description: "Create a newsletter draft for an audience and sending domain. Prefer blocks for body content; Shipmail renders them to email-safe HTML and text. Provide at least one of blocks, body_html, or body_text. Drafts must pass preflight before scheduling.",
|
|
6007
|
+
description: "Create a newsletter draft for an audience and sending domain. Prefer blocks for body content; Shipmail renders them to email-safe HTML and text. Provide at least one of blocks, body_html, or body_text. Drafts must pass preflight before scheduling. styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it.",
|
|
5532
6008
|
inputSchema: createNewsletterInputSchema,
|
|
5533
6009
|
outputSchema: newsletterOutputSchema,
|
|
5534
6010
|
annotations: {
|
|
@@ -5551,7 +6027,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
5551
6027
|
"shipmail_create_newsletter_from_changelog",
|
|
5552
6028
|
{
|
|
5553
6029
|
title: "Create Newsletter From Changelog",
|
|
5554
|
-
description: "Create a newsletter draft from changelog entries, attached media, tone, and an optional final CTA. ShipMail renders the entries into email-safe blocks.",
|
|
6030
|
+
description: "Create a newsletter draft from changelog entries, attached media, tone, and an optional final CTA. ShipMail renders the entries into email-safe blocks. styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it.",
|
|
5555
6031
|
inputSchema: createNewsletterFromChangelogInputSchema,
|
|
5556
6032
|
outputSchema: newsletterOutputSchema,
|
|
5557
6033
|
annotations: {
|
|
@@ -5574,7 +6050,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
5574
6050
|
"shipmail_update_newsletter",
|
|
5575
6051
|
{
|
|
5576
6052
|
title: "Update Newsletter",
|
|
5577
|
-
description: "Update an editable newsletter draft or future scheduled newsletter. Prefer blocks for body content. When blocks already exist, body_text alone updates the plain-text override. Sending and sent newsletters cannot be edited. A concurrent save returns conflict (409); read the latest newsletter before retrying.",
|
|
6053
|
+
description: "Update an editable newsletter draft or future scheduled newsletter. Prefer blocks for body content. When blocks already exist, body_text alone updates the plain-text override. Sending and sent newsletters cannot be edited. A concurrent save returns conflict (409); read the latest newsletter before retrying. styled applies Shipmail's email theme. plain sends your HTML without injected styles, width, or centering, so the reader's email client styles it.",
|
|
5578
6054
|
inputSchema: updateNewsletterInputSchema,
|
|
5579
6055
|
outputSchema: newsletterOutputSchema,
|
|
5580
6056
|
annotations: {
|
|
@@ -5600,7 +6076,8 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
5600
6076
|
...rest.body_text !== void 0 ? { body_text: rest.body_text } : {},
|
|
5601
6077
|
...rest.blocks !== void 0 ? { blocks: rest.blocks } : {},
|
|
5602
6078
|
...rest.send_window_hours !== void 0 ? { send_window_hours: rest.send_window_hours } : {},
|
|
5603
|
-
...rest.archive_visibility !== void 0 ? { archive_visibility: rest.archive_visibility } : {}
|
|
6079
|
+
...rest.archive_visibility !== void 0 ? { archive_visibility: rest.archive_visibility } : {},
|
|
6080
|
+
...rest.styling_mode !== void 0 ? { styling_mode: rest.styling_mode } : {}
|
|
5604
6081
|
},
|
|
5605
6082
|
mutationOptions(args)
|
|
5606
6083
|
)
|
|
@@ -6438,7 +6915,7 @@ function registerTools(rawServer, client, allowedTools) {
|
|
|
6438
6915
|
}
|
|
6439
6916
|
|
|
6440
6917
|
// src/version.ts
|
|
6441
|
-
var VERSION = "0.5.
|
|
6918
|
+
var VERSION = "0.5.8";
|
|
6442
6919
|
|
|
6443
6920
|
// src/server.ts
|
|
6444
6921
|
var INSTRUCTIONS = `ShipMail MCP exposes the business email and calendar tools authorized by the connection's current ShipMail permissions.
|
|
@@ -6463,7 +6940,7 @@ function buildDefaultHeaders() {
|
|
|
6463
6940
|
function componentConnectDomain(baseUrl) {
|
|
6464
6941
|
return new URL(baseUrl ?? "https://shipmail.to/api/v1").origin;
|
|
6465
6942
|
}
|
|
6466
|
-
function createShipMailMcpServer(config, allowedTools) {
|
|
6943
|
+
function createShipMailMcpServer(config, allowedTools, organizationGrants = []) {
|
|
6467
6944
|
const defaultHeaders = buildDefaultHeaders();
|
|
6468
6945
|
const client = new ShipMailClient({
|
|
6469
6946
|
apiKey: config.apiKey,
|
|
@@ -6471,7 +6948,7 @@ function createShipMailMcpServer(config, allowedTools) {
|
|
|
6471
6948
|
...config.organizationId ? { organizationId: config.organizationId } : {},
|
|
6472
6949
|
defaultHeaders
|
|
6473
6950
|
});
|
|
6474
|
-
const server = new
|
|
6951
|
+
const server = new McpServer5(
|
|
6475
6952
|
{
|
|
6476
6953
|
name: "shipmail",
|
|
6477
6954
|
version: VERSION
|
|
@@ -6480,7 +6957,26 @@ function createShipMailMcpServer(config, allowedTools) {
|
|
|
6480
6957
|
instructions: INSTRUCTIONS
|
|
6481
6958
|
}
|
|
6482
6959
|
);
|
|
6483
|
-
|
|
6960
|
+
const grantedOrganizations = organizationGrants.map(({ id, name }) => ({ id, name }));
|
|
6961
|
+
registerTools(server, client, allowedTools, grantedOrganizations);
|
|
6962
|
+
const crossOrganizationGrants = organizationGrants.map(
|
|
6963
|
+
(grant) => ({
|
|
6964
|
+
id: grant.id,
|
|
6965
|
+
name: grant.name,
|
|
6966
|
+
allowedTools: grant.allowedTools,
|
|
6967
|
+
// Cross-organization tools deliberately make exactly one bounded REST request per grant.
|
|
6968
|
+
// Retries would silently amplify one MCP call further, and a short timeout lets the tool
|
|
6969
|
+
// report a failed section instead of losing every organization's result to the route limit.
|
|
6970
|
+
client: new ShipMailClient({
|
|
6971
|
+
apiKey: grant.apiKey,
|
|
6972
|
+
...config.baseUrl ? { baseUrl: config.baseUrl } : {},
|
|
6973
|
+
maxRetries: 0,
|
|
6974
|
+
timeout: 15e3,
|
|
6975
|
+
defaultHeaders
|
|
6976
|
+
})
|
|
6977
|
+
})
|
|
6978
|
+
);
|
|
6979
|
+
registerCrossOrganizationTools(server, crossOrganizationGrants);
|
|
6484
6980
|
registerResources(server, client, componentConnectDomain(config.baseUrl));
|
|
6485
6981
|
registerPrompts(server);
|
|
6486
6982
|
return server;
|