shipmail-mcp 0.2.9 → 0.3.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/README.md CHANGED
@@ -181,17 +181,18 @@ Once connected, ask your agent:
181
181
 
182
182
  All tools are namespaced with `shipmail_` to avoid collisions with peer MCP servers.
183
183
 
184
- | Group | Tools |
185
- | -------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
- | Status | `shipmail_status` |
187
- | Domains | `shipmail_list_domains`, `shipmail_get_domain`, `shipmail_get_domain_dns_records`, `shipmail_create_domain`, `shipmail_update_domain`, `shipmail_delete_domain`, `shipmail_verify_domain`, `shipmail_search_domains` |
188
- | Mailboxes | `shipmail_list_mailboxes`, `shipmail_get_mailbox`, `shipmail_create_mailbox`, `shipmail_update_mailbox`, `shipmail_delete_mailbox`, `shipmail_suspend_mailbox`, `shipmail_resume_mailbox`, `shipmail_list_mailbox_forwarding`, `shipmail_create_mailbox_forwarding`, `shipmail_delete_mailbox_forwarding`, `shipmail_list_mailbox_folders`, `shipmail_create_mailbox_folder`, `shipmail_update_mailbox_folder`, `shipmail_delete_mailbox_folder`, `shipmail_list_mailbox_identities`, `shipmail_get_mailbox_rules`, `shipmail_set_mailbox_rules`, `shipmail_reset_mailbox_password`, `shipmail_set_auto_reply`, `shipmail_set_spam_filter`, `shipmail_inject_sandbox_inbound` |
189
- | Mailbox inbox | `shipmail_list_mailbox_inbox_messages`, `shipmail_get_mailbox_inbox_thread`, `shipmail_update_inbox_message`, `shipmail_move_inbox_message`, `shipmail_delete_inbox_message` |
190
- | Messages and threads | `shipmail_list_messages`, `shipmail_get_message`, `shipmail_send_message`, `shipmail_reply_to_message`, `shipmail_list_threads`, `shipmail_get_thread`, `shipmail_reply_to_thread` |
191
- | Webhooks | `shipmail_list_webhooks`, `shipmail_get_webhook`, `shipmail_create_webhook`, `shipmail_update_webhook`, `shipmail_delete_webhook`, `shipmail_rotate_webhook_secret`, `shipmail_test_webhook`, `shipmail_list_webhook_deliveries`, `shipmail_get_webhook_delivery`, `shipmail_replay_webhook_delivery` |
192
- | Suppressions | `shipmail_list_suppressions`, `shipmail_remove_suppression` |
193
- | Audiences | `shipmail_list_audiences`, `shipmail_get_audience`, `shipmail_create_audience`, `shipmail_update_audience`, `shipmail_delete_audience`, `shipmail_list_subscribers`, `shipmail_get_subscriber`, `shipmail_get_subscriber_by_email`, `shipmail_add_subscriber`, `shipmail_add_subscribers_batch`, `shipmail_update_subscriber`, `shipmail_unsubscribe_subscriber`, `shipmail_resubscribe_subscriber`, `shipmail_remove_subscriber` |
194
- | Newsletters | `shipmail_list_newsletter_domains`, `shipmail_list_newsletter_assets`, `shipmail_list_newsletters`, `shipmail_get_newsletter`, `shipmail_preview_newsletter`, `shipmail_create_newsletter`, `shipmail_create_newsletter_from_changelog`, `shipmail_update_newsletter`, `shipmail_run_newsletter_preflight`, `shipmail_send_newsletter_test`, `shipmail_schedule_newsletter`, `shipmail_cancel_newsletter`, `shipmail_resume_newsletter` |
184
+ | Group | Tools |
185
+ | -------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
186
+ | Status | `shipmail_status` |
187
+ | Domains | `shipmail_list_domains`, `shipmail_get_domain`, `shipmail_get_domain_dns_records`, `shipmail_create_domain`, `shipmail_update_domain`, `shipmail_delete_domain`, `shipmail_verify_domain`, `shipmail_search_domains` |
188
+ | Mailboxes | `shipmail_list_mailboxes`, `shipmail_get_mailbox`, `shipmail_create_mailbox`, `shipmail_update_mailbox`, `shipmail_delete_mailbox`, `shipmail_suspend_mailbox`, `shipmail_resume_mailbox`, `shipmail_list_mailbox_app_passwords`, `shipmail_create_mailbox_app_password`, `shipmail_revoke_mailbox_app_password`, `shipmail_list_mailbox_forwarding`, `shipmail_create_mailbox_forwarding`, `shipmail_delete_mailbox_forwarding`, `shipmail_list_mailbox_folders`, `shipmail_create_mailbox_folder`, `shipmail_update_mailbox_folder`, `shipmail_delete_mailbox_folder`, `shipmail_list_mailbox_identities`, `shipmail_get_mailbox_rules`, `shipmail_set_mailbox_rules`, `shipmail_reset_mailbox_password`, `shipmail_set_auto_reply`, `shipmail_set_spam_filter`, `shipmail_inject_sandbox_inbound` |
189
+ | Mailbox inbox | `shipmail_list_mailbox_inbox_messages`, `shipmail_get_mailbox_inbox_thread`, `shipmail_update_inbox_message`, `shipmail_move_inbox_message`, `shipmail_delete_inbox_message` |
190
+ | Messages and threads | `shipmail_list_messages`, `shipmail_get_message`, `shipmail_send_message`, `shipmail_reply_to_message`, `shipmail_list_threads`, `shipmail_get_thread`, `shipmail_reply_to_thread` |
191
+ | Webhooks | `shipmail_list_webhooks`, `shipmail_get_webhook`, `shipmail_create_webhook`, `shipmail_update_webhook`, `shipmail_delete_webhook`, `shipmail_rotate_webhook_secret`, `shipmail_test_webhook`, `shipmail_list_webhook_deliveries`, `shipmail_get_webhook_delivery`, `shipmail_replay_webhook_delivery` |
192
+ | Suppressions | `shipmail_list_suppressions`, `shipmail_remove_suppression` |
193
+ | Audiences | `shipmail_list_audiences`, `shipmail_get_audience`, `shipmail_create_audience`, `shipmail_update_audience`, `shipmail_delete_audience`, `shipmail_list_subscribers`, `shipmail_get_subscriber`, `shipmail_get_subscriber_by_email`, `shipmail_add_subscriber`, `shipmail_add_subscribers_batch`, `shipmail_update_subscriber`, `shipmail_unsubscribe_subscriber`, `shipmail_resubscribe_subscriber`, `shipmail_remove_subscriber` |
194
+ | Newsletters | `shipmail_list_newsletter_domains`, `shipmail_list_newsletter_assets`, `shipmail_list_newsletters`, `shipmail_get_newsletter`, `shipmail_preview_newsletter`, `shipmail_create_newsletter`, `shipmail_create_newsletter_from_changelog`, `shipmail_update_newsletter`, `shipmail_run_newsletter_preflight`, `shipmail_send_newsletter_test`, `shipmail_schedule_newsletter`, `shipmail_cancel_newsletter`, `shipmail_resume_newsletter` |
195
+ | Partner beta | `shipmail_list_partner_organizations`, `shipmail_create_partner_organization`, `shipmail_get_partner_organization`, `shipmail_update_partner_organization`, `shipmail_resend_partner_ownership_invitation`, `shipmail_suspend_partner_organization`, `shipmail_resume_partner_organization`, `shipmail_offboard_partner_organization`, `shipmail_list_partner_mailbox_credential_grants`, `shipmail_consume_partner_mailbox_credential_grant`, `shipmail_get_partner_usage` |
195
196
 
196
197
  Message send and reply tools accept optional `client_reference`, scalar `metadata`,
197
198
  `source_rfc_message_id`, and validated safe `headers`. `shipmail_list_messages` accepts either a
@@ -199,6 +200,12 @@ Message send and reply tools accept optional `client_reference`, scalar `metadat
199
200
 
200
201
  With an `sm_test_...` API key, send and reply tools accept `sandbox_outcome` and the sandbox inbound tool creates fake inbound mail. The API keeps test storage and events isolated and never delivers sandbox mail to real recipients.
201
202
 
203
+ App-password creation, revocation, and partner grant consumption are destructive/high-risk tools.
204
+ Creation and grant consumption return a live secret once; revocation disconnects that client
205
+ immediately. Partner grant consumption requires the exact `partner:mailbox_credentials:issue`
206
+ scope and an operator-approved one-time grant. App-password creation and grant consumption do not
207
+ accept idempotency keys.
208
+
202
209
  To restrict the surface, pass `--tools` (overrides `SHIPMAIL_MCP_TOOLS`):
203
210
 
204
211
  ```json
@@ -240,20 +247,24 @@ Pre-built prompts the agent can use as guided workflows:
240
247
 
241
248
  ## Configuration
242
249
 
243
- | Variable | Required | Description |
244
- | ---------------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
245
- | `SHIPMAIL_API_KEY` | Yes (or `SHIPMAIL_API_KEY_FILE`) | Shipmail API key (`sm_live_...`). |
246
- | `SHIPMAIL_API_KEY_FILE` | No | Path to a file containing the API key. Takes precedence over `SHIPMAIL_API_KEY`. Reduces env-trace leak surface (Docker secrets, systemd `LoadCredential`). |
247
- | `SHIPMAIL_BASE_URL` | No | Override the API base URL. Must be https on a `shipmail.to` host. Defaults to `https://shipmail.to/api/v1`. |
248
- | `SHIPMAIL_MCP_TOOLS` | No | Comma-separated tool allowlist. The `--tools` flag overrides this. |
249
- | `SHIPMAIL_ALLOW_INSECURE_BASE_URL` | No | Set to `1` to permit a non-https or non-`shipmail.to` base URL. Local development only. |
250
- | `SHIPMAIL_MCP_DEBUG` | No | Set to `1` to include `request_id` and `status` in stderr tool-call logs. |
250
+ | Variable | Required | Description |
251
+ | -------------------------- | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
252
+ | `SHIPMAIL_API_KEY` | Yes (or `SHIPMAIL_API_KEY_FILE`) | Shipmail API key (`sm_live_...`). |
253
+ | `SHIPMAIL_API_KEY_FILE` | No | Path to a file containing the API key. Takes precedence over `SHIPMAIL_API_KEY`. Reduces env-trace leak surface (Docker secrets, systemd `LoadCredential`). |
254
+ | `SHIPMAIL_BASE_URL` | No | Override the API base URL. Must be https on a `shipmail.to` host. Defaults to `https://shipmail.to/api/v1`. |
255
+ | `SHIPMAIL_ORGANIZATION_ID` | No | Delegated child organization for an approved infrastructure-only MCP session. |
256
+
257
+ In a delegated partner session, call `shipmail_create_mailbox` with `generate_password: true`.
258
+ Shipmail generates the primary credential and never returns it to the partner.
259
+ | `SHIPMAIL_MCP_TOOLS` | No | Comma-separated tool allowlist. The `--tools` flag overrides this. |
260
+ | `SHIPMAIL_ALLOW_INSECURE_BASE_URL` | No | Set to `1` to permit a non-https or non-`shipmail.to` base URL. Local development only. |
261
+ | `SHIPMAIL_MCP_DEBUG` | No | Set to `1` to include `request_id` and `status` in stderr tool-call logs. |
251
262
 
252
263
  ## Security
253
264
 
254
265
  - **Tool namespacing**: All tools are prefixed with `shipmail_` to avoid collisions with peer MCP servers in the same host.
255
266
  - **Structured outputs**: Successful tools return both text fallback content and structured MCP `structuredContent`.
256
- - **Idempotency**: Mutating tools accept an optional `idempotency_key`. When omitted, the server generates a fresh key per tool call. Supply your own key if a specific request must stay idempotent across MCP retries.
267
+ - **Idempotency**: Mutating tools accept an optional `idempotency_key`. When omitted, the server generates a fresh key per tool call. Supply your own key if a specific request must stay idempotent across MCP retries. Mailbox app-password creation and partner grant consumption are excluded because their one-time plaintext secrets must never be cached.
257
268
  - **Input sanitization**: Email content, addresses, and error text are stripped of ASCII control characters, DEL, and Unicode directional or BiDi markers (U+061C, U+200E/F, U+202A-E, U+2066-9). Long strings are truncated.
258
269
  - **Error redaction**: 5xx and unexpected Shipmail errors are redacted to a generic message; the original `request_id` is preserved for support. Generic `Error` thrown values (network errors, deserialization) are redacted to "Internal MCP error" before reaching the LLM. Detail lands on stderr.
259
270
  - **Circuit breaker**: Each session enforces per-tool rate limits and a hard total-call ceiling as a runaway-agent guard. These are not abuse controls. Real abuse limits live at the API per API key. Restart the server to reset.
package/dist/index.js CHANGED
@@ -18,6 +18,8 @@ Environment:
18
18
  SHIPMAIL_API_KEY when set; reduces env-trace leak surface for hosts that
19
19
  log environment variables.
20
20
  SHIPMAIL_BASE_URL Optional API base URL. Must be https. Defaults to ${DEFAULT_BASE_URL}.
21
+ SHIPMAIL_ORGANIZATION_ID
22
+ Optional delegated child organization ID for infrastructure tools.
21
23
  SHIPMAIL_MCP_TOOLS Optional comma-separated tool allowlist. --tools overrides this.
22
24
  SHIPMAIL_ALLOW_INSECURE_BASE_URL=1
23
25
  Permit non-https or non-shipmail.to base URL (development only).`;
@@ -95,6 +97,7 @@ function readConfig(argv = process.argv.slice(2)) {
95
97
  return {
96
98
  apiKey,
97
99
  baseUrl,
100
+ organizationId: env["SHIPMAIL_ORGANIZATION_ID"] || void 0,
98
101
  selectedTools: parseToolsArg(argv) ?? parseToolsList(env["SHIPMAIL_MCP_TOOLS"])
99
102
  };
100
103
  }
@@ -110,6 +113,7 @@ import { z as z2 } from "zod/v4";
110
113
 
111
114
  // src/schemas.ts
112
115
  import {
116
+ CONFERENCING_PROVIDER_IDS,
113
117
  DOMAIN_STATUSES,
114
118
  MESSAGE_SOURCES,
115
119
  MESSAGE_STATUSES,
@@ -434,6 +438,39 @@ var mailboxExportSchema = z.object({
434
438
  started_at: z.string().nullable(),
435
439
  completed_at: z.string().nullable()
436
440
  });
441
+ var mailboxAppPasswordSchema = z.object({
442
+ object: z.literal("mailbox_app_password"),
443
+ id: z.string(),
444
+ mailbox_id: z.string(),
445
+ name: z.string(),
446
+ state: z.enum(["pending_create", "active", "pending_revoke", "revoked", "failed"]),
447
+ purpose: z.enum(["operator_client", "partner_embedded_webmail"]),
448
+ expires_at: z.string().nullable(),
449
+ allowed_cidrs: z.array(z.string()),
450
+ last_used_at: z.string().nullable(),
451
+ created_at: z.string(),
452
+ revoked_at: z.string().nullable()
453
+ });
454
+ var createdMailboxAppPasswordSchema = mailboxAppPasswordSchema.extend({
455
+ secret: z.string()
456
+ });
457
+ var partnerMailboxCredentialSchema = createdMailboxAppPasswordSchema.extend({
458
+ operator_notified: z.boolean()
459
+ });
460
+ var partnerMailboxCredentialGrantSchema = z.object({
461
+ object: z.literal("partner_mailbox_credential_grant"),
462
+ id: z.string(),
463
+ partner_organization_id: z.string(),
464
+ organization_id: z.string(),
465
+ organization_name: z.string(),
466
+ external_reference: z.string(),
467
+ operator_email: z.string(),
468
+ mailbox_id: z.string(),
469
+ mailbox_address: z.string(),
470
+ disclosure_version: z.string(),
471
+ expires_at: z.string(),
472
+ consented_at: z.string()
473
+ });
437
474
  var mailboxFolderSchema = z.object({
438
475
  object: z.literal("mailbox_folder"),
439
476
  id: z.string(),
@@ -809,6 +846,26 @@ var domainOutputSchema = z.object({ domain: domainSchema });
809
846
  var domainDnsRecordsOutputSchema = z.object({ dns_records: domainDnsRecordSetSchema });
810
847
  var mailboxOutputSchema = z.object({ mailbox: mailboxSchema });
811
848
  var mailboxExportOutputSchema = z.object({ export: mailboxExportSchema });
849
+ var mailboxAppPasswordOutputSchema = z.object({
850
+ app_password: mailboxAppPasswordSchema
851
+ });
852
+ var createdMailboxAppPasswordOutputSchema = z.object({
853
+ app_password: createdMailboxAppPasswordSchema
854
+ });
855
+ var mailboxAppPasswordsOutputSchema = z.object({
856
+ app_passwords: z.object({
857
+ object: z.literal("list"),
858
+ data: z.array(mailboxAppPasswordSchema)
859
+ })
860
+ });
861
+ var partnerMailboxCredentialOutputSchema = z.object({
862
+ credential: partnerMailboxCredentialSchema
863
+ });
864
+ var partnerMailboxCredentialGrantsOutputSchema = z.object({
865
+ grants: z.object({
866
+ data: z.array(partnerMailboxCredentialGrantSchema)
867
+ })
868
+ });
812
869
  var mailboxFolderOutputSchema = z.object({ folder: mailboxFolderSchema });
813
870
  var mailboxFoldersOutputSchema = z.object({ folders: mailboxFoldersSchema });
814
871
  var mailboxIdentitiesOutputSchema = z.object({ identities: mailboxIdentitiesSchema });
@@ -971,15 +1028,38 @@ var createMailboxInputSchema = z.object({
971
1028
  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.").refine(
972
1029
  (value) => !COMMON_MAILBOX_PASSWORDS.has(value.toLowerCase()),
973
1030
  "This password is too common. Choose something stronger."
974
- ),
1031
+ ).optional(),
1032
+ generate_password: z.literal(true).optional(),
975
1033
  display_name: recipientNameSchema.max(128).optional(),
976
1034
  idempotency_key: idempotencyKeySchema
1035
+ }).superRefine((value, ctx) => {
1036
+ if (Boolean(value.password) === Boolean(value.generate_password)) {
1037
+ ctx.addIssue({
1038
+ code: "custom",
1039
+ path: ["password"],
1040
+ message: "Provide password or set generate_password to true, but not both."
1041
+ });
1042
+ }
977
1043
  });
978
1044
  var updateMailboxInputSchema = z.object({
979
1045
  id: idSchema,
980
1046
  display_name: recipientNameSchema.max(200).nullable().describe("New display name, or null to clear."),
981
1047
  idempotency_key: idempotencyKeySchema
982
1048
  });
1049
+ var appPasswordCidrInputSchema = z.union([
1050
+ z.cidrv4("Must be a valid IPv4 CIDR range."),
1051
+ z.cidrv6("Must be a valid IPv6 CIDR range.")
1052
+ ]);
1053
+ var createMailboxAppPasswordInputSchema = z.object({
1054
+ id: idSchema.describe("Mailbox ID."),
1055
+ name: noControlString(100, "name").trim().min(1),
1056
+ expires_at: z.iso.datetime().optional(),
1057
+ allowed_cidrs: z.array(appPasswordCidrInputSchema).max(20).optional()
1058
+ });
1059
+ var revokeMailboxAppPasswordInputSchema = z.object({
1060
+ id: idSchema.describe("Mailbox ID."),
1061
+ app_password_id: idSchema.describe("App password ID.")
1062
+ });
983
1063
  var createMailboxFolderInputSchema = z.object({
984
1064
  id: idSchema,
985
1065
  name: folderNameSchema.describe("Custom folder name to create."),
@@ -1776,6 +1856,7 @@ var bookingPageSchema = z.object({
1776
1856
  buffer_minutes: z.number(),
1777
1857
  minimum_notice_minutes: z.number(),
1778
1858
  max_advance_days: z.number(),
1859
+ conferencing_provider: z.enum(CONFERENCING_PROVIDER_IDS).nullable(),
1779
1860
  active: z.boolean(),
1780
1861
  created_at: z.string(),
1781
1862
  updated_at: z.string()
@@ -1892,6 +1973,9 @@ var createBookingPageInputSchema = z.object({
1892
1973
  buffer_minutes: z.number().int().min(0).max(240).optional(),
1893
1974
  minimum_notice_minutes: z.number().int().min(0).optional(),
1894
1975
  max_advance_days: z.number().int().min(1).max(365).optional(),
1976
+ conferencing_provider: z.enum(CONFERENCING_PROVIDER_IDS).nullish().describe(
1977
+ "Opt-in meeting provider. It must already be connected to the page's mailbox. Null means no meeting link."
1978
+ ),
1895
1979
  active: z.boolean().optional(),
1896
1980
  idempotency_key: idempotencyKeySchema
1897
1981
  });
@@ -1909,9 +1993,101 @@ var updateBookingPageInputSchema = z.object({
1909
1993
  buffer_minutes: z.number().int().min(0).max(240).optional(),
1910
1994
  minimum_notice_minutes: z.number().int().min(0).optional(),
1911
1995
  max_advance_days: z.number().int().min(1).max(365).optional(),
1996
+ conferencing_provider: z.enum(CONFERENCING_PROVIDER_IDS).nullish().describe(
1997
+ "Opt-in meeting provider. It must already be connected to the page's mailbox. Set null to stop creating meeting links."
1998
+ ),
1912
1999
  active: z.boolean().optional(),
1913
2000
  idempotency_key: idempotencyKeySchema
1914
2001
  });
2002
+ var partnerOrganizationSchema = z.object({
2003
+ object: z.literal("partner_organization"),
2004
+ id: z.string(),
2005
+ organization_id: z.string(),
2006
+ name: z.string(),
2007
+ external_reference: z.string(),
2008
+ owner_email: z.string(),
2009
+ owner_user_id: z.string().nullable(),
2010
+ status: z.enum(["pending_owner", "active", "suspended", "offboarding", "disconnected"]),
2011
+ data_classification: z.enum(["internal_test", "customer"]),
2012
+ mailbox_limit: z.number().int(),
2013
+ delegated_permissions: z.array(z.string()),
2014
+ owner_accepted_at: z.string().nullable(),
2015
+ activated_at: z.string().nullable(),
2016
+ suspended_at: z.string().nullable(),
2017
+ disconnected_at: z.string().nullable(),
2018
+ created_at: z.string(),
2019
+ updated_at: z.string()
2020
+ });
2021
+ var partnerOrganizationOutputSchema = z.object({
2022
+ organization: partnerOrganizationSchema
2023
+ });
2024
+ var createdPartnerOrganizationSchema = partnerOrganizationSchema.extend({
2025
+ ownership_invitation: z.object({
2026
+ object: z.literal("partner_ownership_invitation"),
2027
+ owner_email: z.string(),
2028
+ email_sent: z.boolean(),
2029
+ expires_at: z.string()
2030
+ }).nullable()
2031
+ });
2032
+ var createdPartnerOrganizationOutputSchema = z.object({
2033
+ organization: createdPartnerOrganizationSchema
2034
+ });
2035
+ var partnerOrganizationsOutputSchema = z.object({
2036
+ data: z.array(partnerOrganizationSchema)
2037
+ });
2038
+ var partnerInvitationOutputSchema = z.object({
2039
+ invitation: z.object({
2040
+ object: z.literal("partner_ownership_invitation"),
2041
+ owner_email: z.string(),
2042
+ email_sent: z.boolean(),
2043
+ expires_at: z.string()
2044
+ })
2045
+ });
2046
+ var partnerUsageOutputSchema = z.object({
2047
+ usage: z.object({
2048
+ object: z.literal("partner_usage"),
2049
+ period_start: z.string(),
2050
+ period_end: z.string(),
2051
+ closed_through: z.string().nullable(),
2052
+ active_children: z.number().int(),
2053
+ active_mailboxes: z.number().int(),
2054
+ child_active_seconds: z.number().int(),
2055
+ mailbox_active_seconds: z.number().int(),
2056
+ child_subtotal: z.number().int(),
2057
+ mailbox_subtotal: z.number().int(),
2058
+ minimum_shortfall: z.number().int(),
2059
+ projected_total: z.number().int(),
2060
+ currency: z.string()
2061
+ })
2062
+ });
2063
+ var createPartnerOrganizationInputSchema = z.object({
2064
+ name: noControlString(120, "name").min(1),
2065
+ external_reference: noControlString(200, "external reference").min(1),
2066
+ owner_email: emailSchema,
2067
+ mailbox_limit: z.number().int().min(1).max(50).default(3),
2068
+ data_classification: z.enum(["internal_test", "customer"]).default("customer"),
2069
+ idempotency_key: idempotencyKeySchema
2070
+ });
2071
+ var partnerOrganizationByIdInputSchema = z.object({
2072
+ id: idSchema.describe("Partner organization relationship ID.")
2073
+ });
2074
+ var updatePartnerOrganizationInputSchema = partnerOrganizationByIdInputSchema.extend({
2075
+ name: noControlString(120, "name").min(1).optional(),
2076
+ mailbox_limit: z.number().int().min(1).max(50).optional(),
2077
+ idempotency_key: idempotencyKeySchema
2078
+ }).refine((value) => value.name !== void 0 || value.mailbox_limit !== void 0, {
2079
+ message: "Provide name or mailbox_limit."
2080
+ });
2081
+ var resendPartnerInvitationInputSchema = partnerOrganizationByIdInputSchema.extend({
2082
+ owner_email: emailSchema.optional(),
2083
+ idempotency_key: idempotencyKeySchema
2084
+ });
2085
+ var consumePartnerMailboxCredentialGrantInputSchema = z.object({
2086
+ grant_id: idSchema.describe("Single-use mailbox credential grant ID."),
2087
+ name: noControlString(100, "name").trim().min(1).optional(),
2088
+ expires_at: z.iso.datetime().optional(),
2089
+ allowed_cidrs: z.array(appPasswordCidrInputSchema).max(20).optional()
2090
+ });
1915
2091
 
1916
2092
  // src/prompts.ts
1917
2093
  var MAILBOX_LOCAL_REGEX = /^[a-zA-Z0-9]([a-zA-Z0-9._-]{0,62}[a-zA-Z0-9])?$/;
@@ -2357,6 +2533,8 @@ var SESSION_LIMITS = {
2357
2533
  shipmail_create_domain: 10,
2358
2534
  shipmail_create_mailbox: 20,
2359
2535
  shipmail_create_mailbox_export: 5,
2536
+ shipmail_create_mailbox_app_password: 10,
2537
+ shipmail_revoke_mailbox_app_password: 20,
2360
2538
  shipmail_create_mailbox_import: 5,
2361
2539
  shipmail_cancel_mailbox_import: 10,
2362
2540
  shipmail_undo_mailbox_import: 10,
@@ -2404,7 +2582,14 @@ var SESSION_LIMITS = {
2404
2582
  shipmail_delete_calendar_event: 50,
2405
2583
  shipmail_create_booking_page: 20,
2406
2584
  shipmail_update_booking_page: 20,
2407
- shipmail_delete_booking_page: 10
2585
+ shipmail_delete_booking_page: 10,
2586
+ shipmail_create_partner_organization: 20,
2587
+ shipmail_update_partner_organization: 50,
2588
+ shipmail_resend_partner_ownership_invitation: 20,
2589
+ shipmail_suspend_partner_organization: 20,
2590
+ shipmail_resume_partner_organization: 20,
2591
+ shipmail_offboard_partner_organization: 10,
2592
+ shipmail_consume_partner_mailbox_credential_grant: 20
2408
2593
  };
2409
2594
  var SESSION_TOTAL_LIMIT = 500;
2410
2595
  var DEBUG_ENABLED = process.env["SHIPMAIL_MCP_DEBUG"] === "1";
@@ -2696,6 +2881,72 @@ function registerTools(server, client, selectedTools) {
2696
2881
  }))
2697
2882
  );
2698
2883
  });
2884
+ registerIfAllowed("shipmail_list_mailbox_app_passwords", () => {
2885
+ server.registerTool(
2886
+ "shipmail_list_mailbox_app_passwords",
2887
+ {
2888
+ title: "List Mailbox App Passwords",
2889
+ description: "List app-password metadata for one mailbox. Secret values are never returned by this read operation.",
2890
+ inputSchema: getByIdInputSchema,
2891
+ outputSchema: mailboxAppPasswordsOutputSchema,
2892
+ annotations: { readOnlyHint: true, openWorldHint: false }
2893
+ },
2894
+ async ({ id }) => runTool(
2895
+ "shipmail_list_mailbox_app_passwords",
2896
+ mailboxAppPasswordsOutputSchema,
2897
+ async () => ({ app_passwords: await client.mailboxes.listAppPasswords(id) })
2898
+ )
2899
+ );
2900
+ });
2901
+ registerIfAllowed("shipmail_create_mailbox_app_password", () => {
2902
+ server.registerTool(
2903
+ "shipmail_create_mailbox_app_password",
2904
+ {
2905
+ title: "Create Mailbox App Password",
2906
+ description: "Create a revocable mailbox credential for an email client. The secret is returned exactly once in this tool result, so only call after explicit operator approval and store it securely.",
2907
+ inputSchema: createMailboxAppPasswordInputSchema,
2908
+ outputSchema: createdMailboxAppPasswordOutputSchema,
2909
+ annotations: {
2910
+ readOnlyHint: false,
2911
+ destructiveHint: true,
2912
+ idempotentHint: false,
2913
+ openWorldHint: false
2914
+ }
2915
+ },
2916
+ async (args) => runTool(
2917
+ "shipmail_create_mailbox_app_password",
2918
+ createdMailboxAppPasswordOutputSchema,
2919
+ async () => ({
2920
+ app_password: await client.mailboxes.createAppPassword(args.id, {
2921
+ name: args.name,
2922
+ ...args.expires_at ? { expires_at: args.expires_at } : {},
2923
+ ...args.allowed_cidrs ? { allowed_cidrs: args.allowed_cidrs } : {}
2924
+ })
2925
+ })
2926
+ )
2927
+ );
2928
+ });
2929
+ registerIfAllowed("shipmail_revoke_mailbox_app_password", () => {
2930
+ server.registerTool(
2931
+ "shipmail_revoke_mailbox_app_password",
2932
+ {
2933
+ title: "Revoke Mailbox App Password",
2934
+ description: "Permanently revoke one mailbox app password. The associated client will lose access immediately.",
2935
+ inputSchema: revokeMailboxAppPasswordInputSchema,
2936
+ outputSchema: acknowledgmentOutputSchema,
2937
+ annotations: {
2938
+ readOnlyHint: false,
2939
+ destructiveHint: true,
2940
+ idempotentHint: true,
2941
+ openWorldHint: false
2942
+ }
2943
+ },
2944
+ async ({ id, app_password_id }) => runTool("shipmail_revoke_mailbox_app_password", acknowledgmentOutputSchema, async () => {
2945
+ await client.mailboxes.revokeAppPassword(id, app_password_id);
2946
+ return { result: { ok: true, id: app_password_id } };
2947
+ })
2948
+ );
2949
+ });
2699
2950
  registerIfAllowed("shipmail_suspend_mailbox", () => {
2700
2951
  server.registerTool(
2701
2952
  "shipmail_suspend_mailbox",
@@ -2751,9 +3002,24 @@ function registerTools(server, client, selectedTools) {
2751
3002
  openWorldHint: false
2752
3003
  }
2753
3004
  },
2754
- async (args) => runTool("shipmail_create_mailbox", mailboxOutputSchema, async () => ({
2755
- mailbox: await client.mailboxes.create(stripIdempotencyKey(args), mutationOptions(args))
2756
- }))
3005
+ async (args) => runTool("shipmail_create_mailbox", mailboxOutputSchema, async () => {
3006
+ const base = {
3007
+ domain_id: args.domain_id,
3008
+ address: args.address,
3009
+ ...args.display_name === void 0 ? {} : { display_name: args.display_name }
3010
+ };
3011
+ let params;
3012
+ if (args.generate_password === true) {
3013
+ params = { ...base, generate_password: true };
3014
+ } else if (args.password !== void 0) {
3015
+ params = { ...base, password: args.password };
3016
+ } else {
3017
+ throw new Error("Mailbox password validation failed.");
3018
+ }
3019
+ return {
3020
+ mailbox: await client.mailboxes.create(params, mutationOptions(args))
3021
+ };
3022
+ })
2757
3023
  );
2758
3024
  });
2759
3025
  registerIfAllowed("shipmail_create_mailbox_export", () => {
@@ -4493,7 +4759,7 @@ function registerTools(server, client, selectedTools) {
4493
4759
  "shipmail_create_booking_page",
4494
4760
  {
4495
4761
  title: "Create Booking Page",
4496
- description: "Create a booking page exposing one mailbox's availability. availability_days is 0=Sunday..6=Saturday.",
4762
+ description: "Create a booking page exposing one mailbox's availability. availability_days is 0=Sunday..6=Saturday. conferencing_provider is opt-in and must already be connected to the mailbox.",
4497
4763
  inputSchema: createBookingPageInputSchema,
4498
4764
  outputSchema: bookingPageOutputSchema,
4499
4765
  annotations: {
@@ -4516,7 +4782,7 @@ function registerTools(server, client, selectedTools) {
4516
4782
  "shipmail_update_booking_page",
4517
4783
  {
4518
4784
  title: "Update Booking Page",
4519
- description: "Update a booking page. All fields optional; provide at least one.",
4785
+ description: "Update a booking page. All fields optional; provide at least one. Set conferencing_provider to null to stop creating meeting links.",
4520
4786
  inputSchema: updateBookingPageInputSchema,
4521
4787
  outputSchema: bookingPageOutputSchema,
4522
4788
  annotations: {
@@ -4555,6 +4821,204 @@ function registerTools(server, client, selectedTools) {
4555
4821
  })
4556
4822
  );
4557
4823
  });
4824
+ registerIfAllowed("shipmail_list_partner_organizations", () => {
4825
+ server.registerTool(
4826
+ "shipmail_list_partner_organizations",
4827
+ {
4828
+ title: "List Partner Organizations",
4829
+ description: "List operator-owned organizations connected to the partner account.",
4830
+ outputSchema: partnerOrganizationsOutputSchema,
4831
+ annotations: { readOnlyHint: true, openWorldHint: false }
4832
+ },
4833
+ async () => runTool(
4834
+ "shipmail_list_partner_organizations",
4835
+ partnerOrganizationsOutputSchema,
4836
+ () => client.partner.listOrganizations()
4837
+ )
4838
+ );
4839
+ });
4840
+ registerIfAllowed("shipmail_create_partner_organization", () => {
4841
+ server.registerTool(
4842
+ "shipmail_create_partner_organization",
4843
+ {
4844
+ title: "Create Partner Organization",
4845
+ description: "Create an operator organization and email its ownership invitation. No domain or mailbox is created.",
4846
+ inputSchema: createPartnerOrganizationInputSchema,
4847
+ outputSchema: createdPartnerOrganizationOutputSchema,
4848
+ annotations: {
4849
+ readOnlyHint: false,
4850
+ destructiveHint: false,
4851
+ idempotentHint: true,
4852
+ openWorldHint: false
4853
+ }
4854
+ },
4855
+ async (args) => runTool(
4856
+ "shipmail_create_partner_organization",
4857
+ createdPartnerOrganizationOutputSchema,
4858
+ async () => ({
4859
+ organization: await client.partner.createOrganization(
4860
+ stripIdempotencyKey(args),
4861
+ mutationOptions(args)
4862
+ )
4863
+ })
4864
+ )
4865
+ );
4866
+ });
4867
+ registerIfAllowed("shipmail_get_partner_organization", () => {
4868
+ server.registerTool(
4869
+ "shipmail_get_partner_organization",
4870
+ {
4871
+ title: "Get Partner Organization",
4872
+ description: "Get one partner child relationship and its ownership state.",
4873
+ inputSchema: partnerOrganizationByIdInputSchema,
4874
+ outputSchema: partnerOrganizationOutputSchema,
4875
+ annotations: { readOnlyHint: true, openWorldHint: false }
4876
+ },
4877
+ async ({ id }) => runTool("shipmail_get_partner_organization", partnerOrganizationOutputSchema, async () => ({
4878
+ organization: await client.partner.getOrganization(id)
4879
+ }))
4880
+ );
4881
+ });
4882
+ registerIfAllowed("shipmail_update_partner_organization", () => {
4883
+ server.registerTool(
4884
+ "shipmail_update_partner_organization",
4885
+ {
4886
+ title: "Update Partner Organization",
4887
+ description: "Update an operator organization name or mailbox allocation.",
4888
+ inputSchema: updatePartnerOrganizationInputSchema,
4889
+ outputSchema: partnerOrganizationOutputSchema,
4890
+ annotations: {
4891
+ readOnlyHint: false,
4892
+ destructiveHint: false,
4893
+ idempotentHint: true,
4894
+ openWorldHint: false
4895
+ }
4896
+ },
4897
+ async (args) => runTool(
4898
+ "shipmail_update_partner_organization",
4899
+ partnerOrganizationOutputSchema,
4900
+ async () => {
4901
+ const { id, idempotency_key: _key, ...params } = args;
4902
+ return {
4903
+ organization: await client.partner.updateOrganization(
4904
+ id,
4905
+ params,
4906
+ mutationOptions(args)
4907
+ )
4908
+ };
4909
+ }
4910
+ )
4911
+ );
4912
+ });
4913
+ registerIfAllowed("shipmail_resend_partner_ownership_invitation", () => {
4914
+ server.registerTool(
4915
+ "shipmail_resend_partner_ownership_invitation",
4916
+ {
4917
+ title: "Resend Partner Ownership Invitation",
4918
+ description: "Revoke the pending ownership link and email a new single-use link.",
4919
+ inputSchema: resendPartnerInvitationInputSchema,
4920
+ outputSchema: partnerInvitationOutputSchema,
4921
+ annotations: {
4922
+ readOnlyHint: false,
4923
+ destructiveHint: false,
4924
+ idempotentHint: true,
4925
+ openWorldHint: true
4926
+ }
4927
+ },
4928
+ async (args) => runTool(
4929
+ "shipmail_resend_partner_ownership_invitation",
4930
+ partnerInvitationOutputSchema,
4931
+ async () => ({
4932
+ invitation: await client.partner.resendOwnershipInvitation(
4933
+ args.id,
4934
+ { owner_email: args.owner_email },
4935
+ mutationOptions(args)
4936
+ )
4937
+ })
4938
+ )
4939
+ );
4940
+ });
4941
+ for (const transition of ["suspend", "resume", "offboard"]) {
4942
+ const toolName = `shipmail_${transition}_partner_organization`;
4943
+ registerIfAllowed(toolName, () => {
4944
+ server.registerTool(
4945
+ toolName,
4946
+ {
4947
+ title: `${transition[0]?.toUpperCase() ?? ""}${transition.slice(1)} Partner Organization`,
4948
+ 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.",
4949
+ inputSchema: partnerOrganizationByIdInputSchema,
4950
+ outputSchema: partnerOrganizationOutputSchema,
4951
+ annotations: {
4952
+ readOnlyHint: false,
4953
+ destructiveHint: transition !== "resume",
4954
+ idempotentHint: true,
4955
+ openWorldHint: false
4956
+ }
4957
+ },
4958
+ async ({ id }) => runTool(toolName, partnerOrganizationOutputSchema, async () => ({
4959
+ organization: transition === "suspend" ? await client.partner.suspendOrganization(id) : transition === "resume" ? await client.partner.resumeOrganization(id) : await client.partner.offboardOrganization(id)
4960
+ }))
4961
+ );
4962
+ });
4963
+ }
4964
+ registerIfAllowed("shipmail_consume_partner_mailbox_credential_grant", () => {
4965
+ server.registerTool(
4966
+ "shipmail_consume_partner_mailbox_credential_grant",
4967
+ {
4968
+ title: "Consume Partner Mailbox Credential Grant",
4969
+ description: "Consume an operator-approved, single-use grant and issue an embedded-webmail credential. The secret is returned exactly once and the operator is notified. Call only after explicit partner approval.",
4970
+ inputSchema: consumePartnerMailboxCredentialGrantInputSchema,
4971
+ outputSchema: partnerMailboxCredentialOutputSchema,
4972
+ annotations: {
4973
+ readOnlyHint: false,
4974
+ destructiveHint: true,
4975
+ idempotentHint: false,
4976
+ openWorldHint: true
4977
+ }
4978
+ },
4979
+ async (args) => runTool(
4980
+ "shipmail_consume_partner_mailbox_credential_grant",
4981
+ partnerMailboxCredentialOutputSchema,
4982
+ async () => ({
4983
+ credential: await client.partner.consumeMailboxCredentialGrant(args.grant_id, {
4984
+ ...args.name ? { name: args.name } : {},
4985
+ ...args.expires_at ? { expires_at: args.expires_at } : {},
4986
+ ...args.allowed_cidrs ? { allowed_cidrs: args.allowed_cidrs } : {}
4987
+ })
4988
+ })
4989
+ )
4990
+ );
4991
+ });
4992
+ registerIfAllowed("shipmail_list_partner_mailbox_credential_grants", () => {
4993
+ server.registerTool(
4994
+ "shipmail_list_partner_mailbox_credential_grants",
4995
+ {
4996
+ title: "List Partner Mailbox Credential Grants",
4997
+ description: "List active ten-minute mailbox approvals created by operator owners. Grant metadata contains no app-password secret.",
4998
+ outputSchema: partnerMailboxCredentialGrantsOutputSchema,
4999
+ annotations: { readOnlyHint: true, openWorldHint: false }
5000
+ },
5001
+ async () => runTool(
5002
+ "shipmail_list_partner_mailbox_credential_grants",
5003
+ partnerMailboxCredentialGrantsOutputSchema,
5004
+ async () => ({ grants: await client.partner.listMailboxCredentialGrants() })
5005
+ )
5006
+ );
5007
+ });
5008
+ registerIfAllowed("shipmail_get_partner_usage", () => {
5009
+ server.registerTool(
5010
+ "shipmail_get_partner_usage",
5011
+ {
5012
+ title: "Get Partner Usage",
5013
+ description: "Get consolidated child and mailbox usage for the current UTC month.",
5014
+ outputSchema: partnerUsageOutputSchema,
5015
+ annotations: { readOnlyHint: true, openWorldHint: false }
5016
+ },
5017
+ async () => runTool("shipmail_get_partner_usage", partnerUsageOutputSchema, async () => ({
5018
+ usage: await client.partner.usage()
5019
+ }))
5020
+ );
5021
+ });
4558
5022
  if (selectedTools) {
4559
5023
  const unknown = [...selectedTools].filter((name) => !knownTools.includes(name));
4560
5024
  if (unknown.length > 0) {
@@ -4565,7 +5029,7 @@ function registerTools(server, client, selectedTools) {
4565
5029
  }
4566
5030
 
4567
5031
  // src/version.ts
4568
- var VERSION = "0.2.9";
5032
+ var VERSION = "0.3.0";
4569
5033
 
4570
5034
  // src/server.ts
4571
5035
  var INSTRUCTIONS = `ShipMail MCP exposes business email tools for domains, mailboxes, messages, threads, webhooks, and suppressions.
@@ -4590,6 +5054,7 @@ function createShipMailMcpServer(config) {
4590
5054
  const client = new ShipMailClient({
4591
5055
  apiKey: config.apiKey,
4592
5056
  ...config.baseUrl ? { baseUrl: config.baseUrl } : {},
5057
+ ...config.organizationId ? { organizationId: config.organizationId } : {},
4593
5058
  defaultHeaders
4594
5059
  });
4595
5060
  const server = new McpServer4(
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipmail-mcp",
3
- "version": "0.2.9",
3
+ "version": "0.3.0",
4
4
  "mcpName": "io.github.jcoulaud/shipmail-mcp",
5
5
  "description": "Official Model Context Protocol (MCP) server for Shipmail, a business email provider with REST API, webhooks, and custom-domain inboxes for AI agents.",
6
6
  "type": "module",
@@ -59,7 +59,7 @@
59
59
  },
60
60
  "dependencies": {
61
61
  "@modelcontextprotocol/sdk": "1.29.0",
62
- "shipmail": "0.1.32",
62
+ "shipmail": "0.2.0",
63
63
  "zod": "4.4.3"
64
64
  },
65
65
  "devDependencies": {
package/server.json CHANGED
@@ -6,14 +6,14 @@
6
6
  "url": "https://github.com/jcoulaud/shipmail-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.2.9",
9
+ "version": "0.3.0",
10
10
  "websiteUrl": "https://shipmail.to/docs/mcp",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "shipmail-mcp",
16
- "version": "0.2.9",
16
+ "version": "0.3.0",
17
17
  "transport": {
18
18
  "type": "stdio"
19
19
  },
package/smithery.yaml CHANGED
@@ -1,6 +1,6 @@
1
1
  # Smithery configuration file: https://smithery.ai/docs/build/project-config/smithery-yaml
2
2
  name: shipmail-mcp
3
- version: 0.2.9
3
+ version: 0.3.0
4
4
  description: Official Shipmail MCP server for AI-agent custom-domain business email inboxes with REST API and webhooks.
5
5
  author: ShipMail
6
6
  repository: https://github.com/jcoulaud/shipmail-mcp