groundmail-mcp 0.9.0 → 0.11.1
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 +1 -1
- package/dist/index.js +421 -18
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -55,7 +55,7 @@ When you connect, you choose the access: **Full**, **Send only** or **Read only*
|
|
|
55
55
|
>
|
|
56
56
|
> "How is my deliverability over the last 7 days?"
|
|
57
57
|
|
|
58
|
-
Tools cover emails (send, schedule, cancel, look up), domains (add, apply DNS, verify), templates, webhooks, suppressions and
|
|
58
|
+
Tools cover emails (send, schedule, cancel, look up), domains (add, apply DNS, verify), templates, webhooks, suppressions, deliverability and tenants (one per customer you send for: create, update, pause, resume, stats; `tenant` on sends, domains and suppressions). Each tool carries read-only and destructive annotations and an output schema. Full list: [docs.groundmail.dev/ai/mcp](https://docs.groundmail.dev/ai/mcp).
|
|
59
59
|
|
|
60
60
|
## Transports
|
|
61
61
|
|
package/dist/index.js
CHANGED
|
@@ -161,7 +161,8 @@ var emailItem = object(
|
|
|
161
161
|
delivered_at: nullableString("When the email was delivered (ISO 8601)"),
|
|
162
162
|
bounced_at: nullableString("When the email bounced (ISO 8601)"),
|
|
163
163
|
opened_at: nullableString("First open (ISO 8601); needs open tracking on the sending domain"),
|
|
164
|
-
clicked_at: nullableString("First tracked-link click (ISO 8601); needs click tracking on the sending domain")
|
|
164
|
+
clicked_at: nullableString("First tracked-link click (ISO 8601); needs click tracking on the sending domain"),
|
|
165
|
+
tenant: nullableString("External id of the tenant the email was sent as. Present only when it was sent as a tenant")
|
|
165
166
|
},
|
|
166
167
|
["id", "from", "to", "subject", "status"]
|
|
167
168
|
);
|
|
@@ -179,7 +180,8 @@ var domainItem = object(
|
|
|
179
180
|
tls_mode: string("TLS mode for outgoing mail: opportunistic or enforced"),
|
|
180
181
|
inbound_enabled: boolean("Whether inbound receiving is enabled"),
|
|
181
182
|
inbound_status: string("Inbound status: disabled, pending, active or error"),
|
|
182
|
-
inbound_error: nullableString("Inbound setup error, if any")
|
|
183
|
+
inbound_error: nullableString("Inbound setup error, if any"),
|
|
184
|
+
tenant: nullableString("External id of the tenant that owns the domain. Absent for a domain the whole account can use")
|
|
183
185
|
},
|
|
184
186
|
["id", "name", "status"]
|
|
185
187
|
);
|
|
@@ -200,6 +202,7 @@ var webhookItem = object(
|
|
|
200
202
|
url: string("URL that receives events"),
|
|
201
203
|
events: stringArray("Event types delivered to this endpoint"),
|
|
202
204
|
enabled: boolean("Whether the endpoint receives events"),
|
|
205
|
+
tenants: stringArray("Present only when the endpoint receives email events for some tenants only: their external ids"),
|
|
203
206
|
created_at: string("When the endpoint was created (ISO 8601)"),
|
|
204
207
|
updated_at: string("When the endpoint was last changed (ISO 8601)")
|
|
205
208
|
},
|
|
@@ -211,6 +214,7 @@ var suppressionItem = object(
|
|
|
211
214
|
email: string("Suppressed email address"),
|
|
212
215
|
reason: nullableString("Why the address was suppressed"),
|
|
213
216
|
source: string("How the suppression was created, for example api or bounce"),
|
|
217
|
+
tenant: string("External id of the tenant, on an entry of a tenant's own suppression list"),
|
|
214
218
|
created_at: string("When the suppression was created (ISO 8601)"),
|
|
215
219
|
updated_at: string("When the suppression was last changed (ISO 8601)")
|
|
216
220
|
},
|
|
@@ -393,6 +397,37 @@ var deliverabilityOutput = object(
|
|
|
393
397
|
["window", "summary"]
|
|
394
398
|
);
|
|
395
399
|
var anyObject = (description) => ({ type: "object", description });
|
|
400
|
+
var dmarcOutput = object(
|
|
401
|
+
{
|
|
402
|
+
dmarc: described(
|
|
403
|
+
object(
|
|
404
|
+
{
|
|
405
|
+
domain: string("Monitored domain"),
|
|
406
|
+
enabled: boolean("True when monitoring is on"),
|
|
407
|
+
status: string("waiting (no report yet), receiving, or disabled"),
|
|
408
|
+
report_address: string("Where receivers send the domain's aggregate reports"),
|
|
409
|
+
rua: string("The rua= value to add to the DMARC record (mailto:<report_address>)"),
|
|
410
|
+
dns: anyObject("The DMARC record now (host, record, policy), whether it lists the report address (rua_present), and suggested_record: the record to publish"),
|
|
411
|
+
digest_enabled: boolean("True when the weekly digest email is on"),
|
|
412
|
+
last_report_at: nullableString("End of the newest report received (ISO 8601)"),
|
|
413
|
+
window_days: number("Days of reports the summary covers"),
|
|
414
|
+
summary: anyObject("Totals: messages, dmarc_pass, dmarc_fail, pass_rate (0-1), reports, reporters"),
|
|
415
|
+
sources: arrayOf(
|
|
416
|
+
anyObject("A sending source: name, provider, status (passing, failing, or unknown = possible spoofing), messages, pass_rate, spf_aligned_rate, dkim_aligned_rate, ips"),
|
|
417
|
+
"Who sends mail as the domain, grouped by provider, busiest first"
|
|
418
|
+
),
|
|
419
|
+
daily: arrayOf(anyObject("date, messages, dmarc_pass, pass_rate"), "Pass rate by day"),
|
|
420
|
+
readiness: anyObject("Whether it is safe to enforce DMARC: verdict (no_data, collecting, fix_sources, ready_quarantine, ready_reject, enforced), label, detail, blocking_sources")
|
|
421
|
+
},
|
|
422
|
+
["domain", "enabled"]
|
|
423
|
+
),
|
|
424
|
+
"DMARC monitoring for the domain"
|
|
425
|
+
),
|
|
426
|
+
applied: { type: ["object", "null"], description: "When apply was used: the DNS provider and what changed" },
|
|
427
|
+
apply_error: string("Why the DNS provider could not update the record (add it by hand instead)")
|
|
428
|
+
},
|
|
429
|
+
["dmarc"]
|
|
430
|
+
);
|
|
396
431
|
var migrationOutput = object(
|
|
397
432
|
{
|
|
398
433
|
migration: described(
|
|
@@ -429,6 +464,80 @@ var migrationOutput = object(
|
|
|
429
464
|
},
|
|
430
465
|
["migration"]
|
|
431
466
|
);
|
|
467
|
+
var nullableNumber = (description) => ({ type: ["number", "null"], description });
|
|
468
|
+
var tenantItem = object(
|
|
469
|
+
{
|
|
470
|
+
id: string("Groundmail tenant id"),
|
|
471
|
+
object: string("Always tenant"),
|
|
472
|
+
external_id: string("Your id for this customer; unique in the account"),
|
|
473
|
+
name: nullableString("Display name"),
|
|
474
|
+
status: { type: "string", enum: ["active", "paused"], description: "active, or paused (new sends are refused)" },
|
|
475
|
+
paused_reason: nullableString("Why the tenant was paused"),
|
|
476
|
+
paused_at: nullableString("When the tenant was paused (ISO 8601)"),
|
|
477
|
+
pause_source: {
|
|
478
|
+
type: ["string", "null"],
|
|
479
|
+
enum: ["api", "admin", "automated", null],
|
|
480
|
+
description: "Who paused it: api (you), admin (Groundmail support) or automated (Groundmail's bounce and complaint checks). Only a tenant paused over the API can be resumed over the API"
|
|
481
|
+
},
|
|
482
|
+
default_from_domain: nullableString("Completes a bare from (billing \u2192 billing@<domain>) on sends that name this tenant"),
|
|
483
|
+
stream_policy: string("all, or transactional_only (product email is refused for this tenant)"),
|
|
484
|
+
daily_limit: nullableNumber("Most recipients per UTC day for this tenant; null for no tenant limit"),
|
|
485
|
+
metadata: { type: ["object", "null"], description: "Your own data about the tenant" },
|
|
486
|
+
created_at: string("When the tenant was created (ISO 8601)"),
|
|
487
|
+
updated_at: string("When the tenant was last changed (ISO 8601)")
|
|
488
|
+
},
|
|
489
|
+
["id", "external_id", "status"]
|
|
490
|
+
);
|
|
491
|
+
var tenantOutput = object({ tenant: described(tenantItem, "The tenant") }, ["tenant"]);
|
|
492
|
+
var streamCounts = (description) => ({
|
|
493
|
+
...object({
|
|
494
|
+
transactional: nullableNumber("Transactional email recipients"),
|
|
495
|
+
product: nullableNumber("Product email recipients")
|
|
496
|
+
}),
|
|
497
|
+
description
|
|
498
|
+
});
|
|
499
|
+
var tenantStatsOutput = object(
|
|
500
|
+
{
|
|
501
|
+
tenant: string("External id of the tenant"),
|
|
502
|
+
stats: described(
|
|
503
|
+
object(
|
|
504
|
+
{
|
|
505
|
+
object: string("Always tenant_stats"),
|
|
506
|
+
days: number("Days covered"),
|
|
507
|
+
from: string("Start of the window (ISO 8601)"),
|
|
508
|
+
total: number("Emails created in the window"),
|
|
509
|
+
queued: number("Emails waiting to be sent"),
|
|
510
|
+
scheduled: number("Emails still scheduled"),
|
|
511
|
+
sent: number("Emails handed off for delivery"),
|
|
512
|
+
delivered: number("Emails delivered"),
|
|
513
|
+
bounced: number("Emails that bounced"),
|
|
514
|
+
complained: number("Emails reported as spam"),
|
|
515
|
+
failed: number("Emails that failed"),
|
|
516
|
+
suppressed: number("Emails not sent because the recipient was suppressed"),
|
|
517
|
+
opened: number("Emails opened"),
|
|
518
|
+
clicked: number("Emails with a tracked click"),
|
|
519
|
+
bounce_rate: number("Bounced / (delivered + bounced + complained), as a fraction"),
|
|
520
|
+
complaint_rate: number("Complained / (delivered + bounced + complained), as a fraction")
|
|
521
|
+
},
|
|
522
|
+
["days", "total"]
|
|
523
|
+
),
|
|
524
|
+
"Email counts and rates over the window"
|
|
525
|
+
),
|
|
526
|
+
ramp: described(
|
|
527
|
+
object({
|
|
528
|
+
object: string("Always tenant_ramp"),
|
|
529
|
+
active: boolean("True when a ramp-up daily limit applies to this tenant today"),
|
|
530
|
+
stage: nullableNumber("Ramp-up stage, 1-based; null when the ramp does not apply"),
|
|
531
|
+
daily_limit: { ...streamCounts("Today's limit for each stream"), type: ["object", "null"] },
|
|
532
|
+
used_today: streamCounts("Recipients sent today on each stream"),
|
|
533
|
+
completed: boolean("True when the tenant finished its ramp-up"),
|
|
534
|
+
resets_at: string("The next 00:00 UTC (ISO 8601)")
|
|
535
|
+
}),
|
|
536
|
+
"The tenant's sending ramp today, when known"
|
|
537
|
+
)
|
|
538
|
+
},
|
|
539
|
+
["tenant", "stats"]
|
|
540
|
+
);
|
|
432
541
|
|
|
433
542
|
// src/tools.ts
|
|
434
543
|
function textResult(value, isError = false) {
|
|
@@ -463,11 +572,40 @@ function optStringArray(args, key) {
|
|
|
463
572
|
if (Array.isArray(value)) return value.map((v) => String(v));
|
|
464
573
|
return void 0;
|
|
465
574
|
}
|
|
575
|
+
function tenantSettings(args) {
|
|
576
|
+
const out = {};
|
|
577
|
+
if (args.name !== void 0) out.name = args.name == null ? null : String(args.name);
|
|
578
|
+
if (args.default_from_domain !== void 0) {
|
|
579
|
+
out.default_from_domain = args.default_from_domain == null ? null : String(args.default_from_domain);
|
|
580
|
+
}
|
|
581
|
+
if (args.stream_policy !== void 0) {
|
|
582
|
+
if (args.stream_policy !== "all" && args.stream_policy !== "transactional_only") {
|
|
583
|
+
return "stream_policy must be all or transactional_only";
|
|
584
|
+
}
|
|
585
|
+
out.stream_policy = args.stream_policy;
|
|
586
|
+
}
|
|
587
|
+
if (args.daily_limit !== void 0) {
|
|
588
|
+
if (args.daily_limit === null) out.daily_limit = null;
|
|
589
|
+
else {
|
|
590
|
+
const n = Number(args.daily_limit);
|
|
591
|
+
if (!Number.isInteger(n) || n < 0) return "daily_limit must be a whole number, or null for no limit";
|
|
592
|
+
out.daily_limit = n;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
if (args.metadata !== void 0) {
|
|
596
|
+
if (args.metadata !== null && (typeof args.metadata !== "object" || Array.isArray(args.metadata))) {
|
|
597
|
+
return "metadata must be an object, or null";
|
|
598
|
+
}
|
|
599
|
+
out.metadata = args.metadata;
|
|
600
|
+
}
|
|
601
|
+
return out;
|
|
602
|
+
}
|
|
466
603
|
var EMAIL_WEBHOOK_EVENTS = /* @__PURE__ */ new Set([
|
|
467
604
|
"email.received",
|
|
468
605
|
"email.sent",
|
|
469
606
|
"email.delivered",
|
|
470
607
|
"email.delivery_delayed",
|
|
608
|
+
"email.held",
|
|
471
609
|
"email.bounced",
|
|
472
610
|
"email.complained",
|
|
473
611
|
"email.opened",
|
|
@@ -497,6 +635,7 @@ async function callMcpTool(client, name, args) {
|
|
|
497
635
|
html: optString(args, "html"),
|
|
498
636
|
text: optString(args, "text"),
|
|
499
637
|
reply_to: optString(args, "reply_to"),
|
|
638
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {},
|
|
500
639
|
...templateId ? { template: { id: templateId, ...variables ? { variables } : {} } } : {}
|
|
501
640
|
});
|
|
502
641
|
return textResult(result);
|
|
@@ -505,7 +644,8 @@ async function callMcpTool(client, name, args) {
|
|
|
505
644
|
const result = await client.emails.list({
|
|
506
645
|
limit: optNumber(args, "limit"),
|
|
507
646
|
cursor: optString(args, "cursor"),
|
|
508
|
-
...typeof args.test === "boolean" ? { test: args.test } : {}
|
|
647
|
+
...typeof args.test === "boolean" ? { test: args.test } : {},
|
|
648
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
509
649
|
});
|
|
510
650
|
return textResult(result);
|
|
511
651
|
}
|
|
@@ -542,7 +682,8 @@ async function callMcpTool(client, name, args) {
|
|
|
542
682
|
case "list_domains": {
|
|
543
683
|
const result = await client.domains.list({
|
|
544
684
|
limit: optNumber(args, "limit"),
|
|
545
|
-
cursor: optString(args, "cursor")
|
|
685
|
+
cursor: optString(args, "cursor"),
|
|
686
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
546
687
|
});
|
|
547
688
|
return textResult(result);
|
|
548
689
|
}
|
|
@@ -553,7 +694,8 @@ async function callMcpTool(client, name, args) {
|
|
|
553
694
|
return textResult(
|
|
554
695
|
await client.domains.create({
|
|
555
696
|
name: domainName,
|
|
556
|
-
...inbound !== void 0 ? { inbound_enabled: inbound } : {}
|
|
697
|
+
...inbound !== void 0 ? { inbound_enabled: inbound } : {},
|
|
698
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
557
699
|
})
|
|
558
700
|
);
|
|
559
701
|
}
|
|
@@ -569,10 +711,12 @@ async function callMcpTool(client, name, args) {
|
|
|
569
711
|
const url = optString(args, "url");
|
|
570
712
|
if (!url) return textResult("Missing required argument: url", true);
|
|
571
713
|
const events = optStringArray(args, "events");
|
|
714
|
+
const tenants = optStringArray(args, "tenants");
|
|
572
715
|
return textResult(
|
|
573
716
|
await client.webhooks.create({
|
|
574
717
|
url,
|
|
575
|
-
...events ? { events } : {}
|
|
718
|
+
...events ? { events } : {},
|
|
719
|
+
...tenants?.length ? { tenants } : {}
|
|
576
720
|
})
|
|
577
721
|
);
|
|
578
722
|
}
|
|
@@ -586,7 +730,8 @@ async function callMcpTool(client, name, args) {
|
|
|
586
730
|
await client.suppressions.list({
|
|
587
731
|
limit: optNumber(args, "limit"),
|
|
588
732
|
cursor: optString(args, "cursor"),
|
|
589
|
-
email: optString(args, "email")
|
|
733
|
+
email: optString(args, "email"),
|
|
734
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
590
735
|
})
|
|
591
736
|
);
|
|
592
737
|
}
|
|
@@ -601,7 +746,8 @@ async function callMcpTool(client, name, args) {
|
|
|
601
746
|
return textResult(
|
|
602
747
|
await client.suppressions.create({
|
|
603
748
|
email,
|
|
604
|
-
reason: optString(args, "reason")
|
|
749
|
+
reason: optString(args, "reason"),
|
|
750
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
605
751
|
})
|
|
606
752
|
);
|
|
607
753
|
}
|
|
@@ -610,7 +756,8 @@ async function callMcpTool(client, name, args) {
|
|
|
610
756
|
if (!idOrEmail) {
|
|
611
757
|
return textResult("Missing required argument: id or email", true);
|
|
612
758
|
}
|
|
613
|
-
|
|
759
|
+
const tenant = optString(args, "tenant");
|
|
760
|
+
return textResult(tenant ? await client.suppressions.remove(idOrEmail, { tenant }) : await client.suppressions.remove(idOrEmail));
|
|
614
761
|
}
|
|
615
762
|
case "list_templates": {
|
|
616
763
|
const status = optString(args, "status");
|
|
@@ -661,10 +808,79 @@ async function callMcpTool(client, name, args) {
|
|
|
661
808
|
await client.emails.testWebhook({
|
|
662
809
|
event,
|
|
663
810
|
email_id: optString(args, "email_id"),
|
|
664
|
-
deliver: optBoolean(args, "deliver")
|
|
811
|
+
deliver: optBoolean(args, "deliver"),
|
|
812
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
665
813
|
})
|
|
666
814
|
);
|
|
667
815
|
}
|
|
816
|
+
case "list_tenants": {
|
|
817
|
+
const status = optString(args, "status");
|
|
818
|
+
return textResult(
|
|
819
|
+
await client.tenants.list({
|
|
820
|
+
limit: optNumber(args, "limit"),
|
|
821
|
+
cursor: optString(args, "cursor"),
|
|
822
|
+
...status === "active" || status === "paused" ? { status } : {}
|
|
823
|
+
})
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
case "create_tenant": {
|
|
827
|
+
const externalId = optString(args, "external_id");
|
|
828
|
+
if (!externalId) return textResult("Missing required argument: external_id", true);
|
|
829
|
+
const settings = tenantSettings(args);
|
|
830
|
+
if (typeof settings === "string") return textResult(settings, true);
|
|
831
|
+
const { name: name2, ...rest } = settings;
|
|
832
|
+
return textResult(
|
|
833
|
+
await client.tenants.create({ external_id: externalId, ...name2 ? { name: name2 } : {}, ...rest })
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
case "get_tenant": {
|
|
837
|
+
const tenant = optString(args, "tenant");
|
|
838
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
839
|
+
return textResult(await client.tenants.get(tenant));
|
|
840
|
+
}
|
|
841
|
+
case "update_tenant": {
|
|
842
|
+
const tenant = optString(args, "tenant");
|
|
843
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
844
|
+
const settings = tenantSettings(args);
|
|
845
|
+
if (typeof settings === "string") return textResult(settings, true);
|
|
846
|
+
if (!Object.keys(settings).length) {
|
|
847
|
+
return textResult(
|
|
848
|
+
"Nothing to update: pass name, default_from_domain, stream_policy, daily_limit or metadata (use pause_tenant / resume_tenant to pause or resume)",
|
|
849
|
+
true
|
|
850
|
+
);
|
|
851
|
+
}
|
|
852
|
+
return textResult(await client.tenants.update(tenant, settings));
|
|
853
|
+
}
|
|
854
|
+
case "pause_tenant": {
|
|
855
|
+
const tenant = optString(args, "tenant");
|
|
856
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
857
|
+
return textResult(await client.tenants.pause(tenant, optString(args, "reason")));
|
|
858
|
+
}
|
|
859
|
+
case "resume_tenant": {
|
|
860
|
+
const tenant = optString(args, "tenant");
|
|
861
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
862
|
+
return textResult(await client.tenants.resume(tenant));
|
|
863
|
+
}
|
|
864
|
+
case "get_tenant_stats": {
|
|
865
|
+
const tenant = optString(args, "tenant");
|
|
866
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
867
|
+
const days = optNumber(args, "days");
|
|
868
|
+
return textResult(await client.tenants.stats(tenant, days !== void 0 ? { days } : {}));
|
|
869
|
+
}
|
|
870
|
+
case "get_dmarc_report": {
|
|
871
|
+
const domain = optString(args, "domain");
|
|
872
|
+
if (!domain) return textResult("Missing required argument: domain", true);
|
|
873
|
+
return textResult(await client.dmarc.get(domain));
|
|
874
|
+
}
|
|
875
|
+
case "enable_dmarc_monitoring": {
|
|
876
|
+
const domain = optString(args, "domain");
|
|
877
|
+
if (!domain) return textResult("Missing required argument: domain", true);
|
|
878
|
+
const apply = optString(args, "apply");
|
|
879
|
+
if (apply && apply !== "cloudflare" && apply !== "vercel" && apply !== "godaddy") {
|
|
880
|
+
return textResult("apply must be cloudflare, vercel or godaddy", true);
|
|
881
|
+
}
|
|
882
|
+
return textResult(await client.dmarc.enable(domain, apply ? { apply } : {}));
|
|
883
|
+
}
|
|
668
884
|
case "get_deliverability": {
|
|
669
885
|
const window = args.window === "7d" ? "7d" : "30d";
|
|
670
886
|
const result = await client.emails.insights(window);
|
|
@@ -738,6 +954,27 @@ var paginationProps = {
|
|
|
738
954
|
limit: { type: "number", description: "Page size (1\u2013100)" },
|
|
739
955
|
cursor: { type: "string", description: "Opaque cursor for the next page" }
|
|
740
956
|
};
|
|
957
|
+
var tenantProp = (description) => ({
|
|
958
|
+
type: "string",
|
|
959
|
+
description: `${description}. The tenant's external id (your id for that customer) or its Groundmail id`
|
|
960
|
+
});
|
|
961
|
+
var tenantSettingsProps = {
|
|
962
|
+
name: { type: "string", description: "Display name" },
|
|
963
|
+
default_from_domain: {
|
|
964
|
+
type: ["string", "null"],
|
|
965
|
+
description: "Domain that completes a bare from (billing \u2192 billing@<domain>) on sends naming this tenant; null to clear"
|
|
966
|
+
},
|
|
967
|
+
stream_policy: {
|
|
968
|
+
type: "string",
|
|
969
|
+
enum: ["all", "transactional_only"],
|
|
970
|
+
description: "all (default), or transactional_only to refuse product email for this tenant"
|
|
971
|
+
},
|
|
972
|
+
daily_limit: {
|
|
973
|
+
type: ["number", "null"],
|
|
974
|
+
description: "Most recipients per UTC day for this tenant; null for no tenant limit"
|
|
975
|
+
},
|
|
976
|
+
metadata: { type: ["object", "null"], description: "Your own data about the tenant (JSON object); null to clear" }
|
|
977
|
+
};
|
|
741
978
|
var readOnly = (title) => ({
|
|
742
979
|
title,
|
|
743
980
|
readOnlyHint: true,
|
|
@@ -767,7 +1004,10 @@ var MCP_TOOLS = [
|
|
|
767
1004
|
type: "object",
|
|
768
1005
|
description: "Template variables when using template_id or template_alias",
|
|
769
1006
|
additionalProperties: true
|
|
770
|
-
}
|
|
1007
|
+
},
|
|
1008
|
+
tenant: tenantProp(
|
|
1009
|
+
"Send as this tenant: the send uses its domains, suppression list, pause switch and daily limit. Omit to send from the account as a whole"
|
|
1010
|
+
)
|
|
771
1011
|
},
|
|
772
1012
|
required: ["from", "to"]
|
|
773
1013
|
},
|
|
@@ -787,7 +1027,8 @@ var MCP_TOOLS = [
|
|
|
787
1027
|
type: "object",
|
|
788
1028
|
properties: {
|
|
789
1029
|
...paginationProps,
|
|
790
|
-
test: { type: "boolean", description: "true: only test emails; false: only live ones. Omit for both" }
|
|
1030
|
+
test: { type: "boolean", description: "true: only test emails; false: only live ones. Omit for both" },
|
|
1031
|
+
tenant: tenantProp("Only emails sent as this tenant")
|
|
791
1032
|
}
|
|
792
1033
|
},
|
|
793
1034
|
outputSchema: pagedOutput("emails", emailItem, "Emails on this page, newest first"),
|
|
@@ -811,7 +1052,7 @@ var MCP_TOOLS = [
|
|
|
811
1052
|
description: "List sending domains for this account.",
|
|
812
1053
|
inputSchema: {
|
|
813
1054
|
type: "object",
|
|
814
|
-
properties: { ...paginationProps }
|
|
1055
|
+
properties: { ...paginationProps, tenant: tenantProp("Only domains this tenant owns") }
|
|
815
1056
|
},
|
|
816
1057
|
outputSchema: pagedOutput("domains", domainItem, "Sending domains on this page"),
|
|
817
1058
|
annotations: readOnly("List domains")
|
|
@@ -823,7 +1064,8 @@ var MCP_TOOLS = [
|
|
|
823
1064
|
type: "object",
|
|
824
1065
|
properties: {
|
|
825
1066
|
name: { type: "string", description: "Domain name (example.com)" },
|
|
826
|
-
inbound_enabled: { type: "boolean", description: "Enable inbound receiving on this domain" }
|
|
1067
|
+
inbound_enabled: { type: "boolean", description: "Enable inbound receiving on this domain" },
|
|
1068
|
+
tenant: tenantProp("Give the domain to this tenant; only that tenant can then send from it")
|
|
827
1069
|
},
|
|
828
1070
|
required: ["name"]
|
|
829
1071
|
},
|
|
@@ -907,6 +1149,11 @@ var MCP_TOOLS = [
|
|
|
907
1149
|
type: "array",
|
|
908
1150
|
items: { type: "string" },
|
|
909
1151
|
description: "Optional event types (defaults apply when omitted)"
|
|
1152
|
+
},
|
|
1153
|
+
tenants: {
|
|
1154
|
+
type: "array",
|
|
1155
|
+
items: { type: "string" },
|
|
1156
|
+
description: "Optional: only these tenants' email events (external ids or Groundmail ids). Omit for all"
|
|
910
1157
|
}
|
|
911
1158
|
},
|
|
912
1159
|
required: ["url"]
|
|
@@ -946,7 +1193,8 @@ var MCP_TOOLS = [
|
|
|
946
1193
|
type: "object",
|
|
947
1194
|
properties: {
|
|
948
1195
|
...paginationProps,
|
|
949
|
-
email: { type: "string", description: "Optional filter by email" }
|
|
1196
|
+
email: { type: "string", description: "Optional filter by email" },
|
|
1197
|
+
tenant: tenantProp("List this tenant's own suppression list instead of the account's")
|
|
950
1198
|
}
|
|
951
1199
|
},
|
|
952
1200
|
outputSchema: pagedOutput("data", suppressionItem, "Suppressed addresses on this page"),
|
|
@@ -972,7 +1220,8 @@ var MCP_TOOLS = [
|
|
|
972
1220
|
type: "object",
|
|
973
1221
|
properties: {
|
|
974
1222
|
email: { type: "string", description: "Email address to stop sending to" },
|
|
975
|
-
reason: { type: "string", description: "Optional note on why the address is suppressed" }
|
|
1223
|
+
reason: { type: "string", description: "Optional note on why the address is suppressed" },
|
|
1224
|
+
tenant: tenantProp("Add to this tenant's own list instead of the account's")
|
|
976
1225
|
},
|
|
977
1226
|
required: ["email"]
|
|
978
1227
|
},
|
|
@@ -993,7 +1242,8 @@ var MCP_TOOLS = [
|
|
|
993
1242
|
type: "object",
|
|
994
1243
|
properties: {
|
|
995
1244
|
id: { type: "string", description: "Suppression id" },
|
|
996
|
-
email: { type: "string", description: "Email address (alternative to id)" }
|
|
1245
|
+
email: { type: "string", description: "Email address (alternative to id)" },
|
|
1246
|
+
tenant: tenantProp("Remove the address (by email) from this tenant's own list")
|
|
997
1247
|
}
|
|
998
1248
|
},
|
|
999
1249
|
outputSchema: okOutput,
|
|
@@ -1090,7 +1340,8 @@ var MCP_TOOLS = [
|
|
|
1090
1340
|
deliver: {
|
|
1091
1341
|
type: "boolean",
|
|
1092
1342
|
description: "When true (default), enqueue delivery to account webhooks"
|
|
1093
|
-
}
|
|
1343
|
+
},
|
|
1344
|
+
tenant: tenantProp("Put this tenant on the test event, to test endpoints that only receive some tenants")
|
|
1094
1345
|
},
|
|
1095
1346
|
required: ["event"]
|
|
1096
1347
|
},
|
|
@@ -1103,6 +1354,158 @@ var MCP_TOOLS = [
|
|
|
1103
1354
|
openWorldHint: true
|
|
1104
1355
|
}
|
|
1105
1356
|
},
|
|
1357
|
+
{
|
|
1358
|
+
name: "list_tenants",
|
|
1359
|
+
description: "List tenants: one per customer you send email for, each with its own domains, suppression list, stats, pause switch and daily limit. Tenants are included on the Scale and Dedicated plans.",
|
|
1360
|
+
inputSchema: {
|
|
1361
|
+
type: "object",
|
|
1362
|
+
properties: {
|
|
1363
|
+
...paginationProps,
|
|
1364
|
+
status: { type: "string", enum: ["active", "paused"], description: "Optional filter by status" }
|
|
1365
|
+
}
|
|
1366
|
+
},
|
|
1367
|
+
outputSchema: pagedOutput("data", tenantItem, "Tenants on this page, newest first"),
|
|
1368
|
+
annotations: readOnly("List tenants")
|
|
1369
|
+
},
|
|
1370
|
+
{
|
|
1371
|
+
name: "create_tenant",
|
|
1372
|
+
description: "Create a tenant for one of your customers. Then send with tenant set to its external id, and give it its own domains with create_domain (tenant).",
|
|
1373
|
+
inputSchema: {
|
|
1374
|
+
type: "object",
|
|
1375
|
+
properties: {
|
|
1376
|
+
external_id: {
|
|
1377
|
+
type: "string",
|
|
1378
|
+
description: "Your id for this customer: 1\u2013128 characters of letters, digits and . _ : @ -. Cannot be changed later"
|
|
1379
|
+
},
|
|
1380
|
+
...tenantSettingsProps
|
|
1381
|
+
},
|
|
1382
|
+
required: ["external_id"]
|
|
1383
|
+
},
|
|
1384
|
+
outputSchema: tenantOutput,
|
|
1385
|
+
annotations: {
|
|
1386
|
+
title: "Create tenant",
|
|
1387
|
+
readOnlyHint: false,
|
|
1388
|
+
destructiveHint: false,
|
|
1389
|
+
idempotentHint: false,
|
|
1390
|
+
openWorldHint: false
|
|
1391
|
+
}
|
|
1392
|
+
},
|
|
1393
|
+
{
|
|
1394
|
+
name: "get_tenant",
|
|
1395
|
+
description: "Get one tenant, including whether it is paused and why.",
|
|
1396
|
+
inputSchema: {
|
|
1397
|
+
type: "object",
|
|
1398
|
+
properties: { tenant: tenantProp("The tenant") },
|
|
1399
|
+
required: ["tenant"]
|
|
1400
|
+
},
|
|
1401
|
+
outputSchema: tenantOutput,
|
|
1402
|
+
annotations: readOnly("Get tenant")
|
|
1403
|
+
},
|
|
1404
|
+
{
|
|
1405
|
+
name: "update_tenant",
|
|
1406
|
+
description: "Change a tenant's name, default from domain, stream policy, daily limit or metadata.",
|
|
1407
|
+
inputSchema: {
|
|
1408
|
+
type: "object",
|
|
1409
|
+
properties: { tenant: tenantProp("The tenant to change"), ...tenantSettingsProps },
|
|
1410
|
+
required: ["tenant"]
|
|
1411
|
+
},
|
|
1412
|
+
outputSchema: tenantOutput,
|
|
1413
|
+
annotations: {
|
|
1414
|
+
title: "Update tenant",
|
|
1415
|
+
readOnlyHint: false,
|
|
1416
|
+
destructiveHint: false,
|
|
1417
|
+
idempotentHint: true,
|
|
1418
|
+
openWorldHint: false
|
|
1419
|
+
}
|
|
1420
|
+
},
|
|
1421
|
+
{
|
|
1422
|
+
name: "pause_tenant",
|
|
1423
|
+
description: "Pause a tenant: its new sends are refused until it is resumed. The rest of the account keeps sending. Confirm with the user first.",
|
|
1424
|
+
inputSchema: {
|
|
1425
|
+
type: "object",
|
|
1426
|
+
properties: {
|
|
1427
|
+
tenant: tenantProp("The tenant to pause"),
|
|
1428
|
+
reason: { type: "string", description: "Why it is paused (shown on the tenant)" }
|
|
1429
|
+
},
|
|
1430
|
+
required: ["tenant"]
|
|
1431
|
+
},
|
|
1432
|
+
outputSchema: tenantOutput,
|
|
1433
|
+
annotations: {
|
|
1434
|
+
title: "Pause tenant",
|
|
1435
|
+
readOnlyHint: false,
|
|
1436
|
+
destructiveHint: false,
|
|
1437
|
+
idempotentHint: true,
|
|
1438
|
+
openWorldHint: false
|
|
1439
|
+
}
|
|
1440
|
+
},
|
|
1441
|
+
{
|
|
1442
|
+
name: "resume_tenant",
|
|
1443
|
+
description: "Resume a paused tenant so it can send again. A tenant Groundmail paused (pause_source automated or admin) cannot be resumed this way; the user should fix the cause and contact support.",
|
|
1444
|
+
inputSchema: {
|
|
1445
|
+
type: "object",
|
|
1446
|
+
properties: { tenant: tenantProp("The tenant to resume") },
|
|
1447
|
+
required: ["tenant"]
|
|
1448
|
+
},
|
|
1449
|
+
outputSchema: tenantOutput,
|
|
1450
|
+
annotations: {
|
|
1451
|
+
title: "Resume tenant",
|
|
1452
|
+
readOnlyHint: false,
|
|
1453
|
+
destructiveHint: false,
|
|
1454
|
+
idempotentHint: true,
|
|
1455
|
+
openWorldHint: false
|
|
1456
|
+
}
|
|
1457
|
+
},
|
|
1458
|
+
{
|
|
1459
|
+
name: "get_tenant_stats",
|
|
1460
|
+
description: "A tenant's email counts, bounce and complaint rates over the last days (1\u201390, default 7), and its sending ramp today.",
|
|
1461
|
+
inputSchema: {
|
|
1462
|
+
type: "object",
|
|
1463
|
+
properties: {
|
|
1464
|
+
tenant: tenantProp("The tenant"),
|
|
1465
|
+
days: { type: "number", description: "Days to cover, 1\u201390 (default 7)" }
|
|
1466
|
+
},
|
|
1467
|
+
required: ["tenant"]
|
|
1468
|
+
},
|
|
1469
|
+
outputSchema: tenantStatsOutput,
|
|
1470
|
+
annotations: readOnly("Get tenant stats")
|
|
1471
|
+
},
|
|
1472
|
+
{
|
|
1473
|
+
name: "get_dmarc_report",
|
|
1474
|
+
description: "DMARC monitoring for a sending domain: who sends mail as it (grouped by provider, with unknown senders flagged as possible spoofing), SPF/DKIM/DMARC pass rates over the last 30 days, and whether it is safe to move the DMARC policy to quarantine. If monitoring is off, shows the current DMARC record; turn it on with enable_dmarc_monitoring.",
|
|
1475
|
+
inputSchema: {
|
|
1476
|
+
type: "object",
|
|
1477
|
+
properties: {
|
|
1478
|
+
domain: { type: "string", description: "Domain name or id (example.com)" }
|
|
1479
|
+
},
|
|
1480
|
+
required: ["domain"]
|
|
1481
|
+
},
|
|
1482
|
+
outputSchema: dmarcOutput,
|
|
1483
|
+
annotations: readOnly("Get DMARC report")
|
|
1484
|
+
},
|
|
1485
|
+
{
|
|
1486
|
+
name: "enable_dmarc_monitoring",
|
|
1487
|
+
description: "Turn on free DMARC monitoring for a sending domain. Returns the report address and the exact DMARC record to publish (the current record with the address added to rua=, keeping the policy and other report addresses). Pass apply to update the record through a DNS provider connected in the dashboard. Reports start arriving within 24 to 48 hours.",
|
|
1488
|
+
inputSchema: {
|
|
1489
|
+
type: "object",
|
|
1490
|
+
properties: {
|
|
1491
|
+
domain: { type: "string", description: "Domain name or id (example.com)" },
|
|
1492
|
+
apply: {
|
|
1493
|
+
type: "string",
|
|
1494
|
+
enum: ["cloudflare", "vercel", "godaddy"],
|
|
1495
|
+
description: "Also write the DMARC record through this DNS provider (connected under Settings \u2192 Integrations)"
|
|
1496
|
+
}
|
|
1497
|
+
},
|
|
1498
|
+
required: ["domain"]
|
|
1499
|
+
},
|
|
1500
|
+
outputSchema: dmarcOutput,
|
|
1501
|
+
annotations: {
|
|
1502
|
+
title: "Enable DMARC monitoring",
|
|
1503
|
+
readOnlyHint: false,
|
|
1504
|
+
destructiveHint: false,
|
|
1505
|
+
idempotentHint: true,
|
|
1506
|
+
openWorldHint: true
|
|
1507
|
+
}
|
|
1508
|
+
},
|
|
1106
1509
|
{
|
|
1107
1510
|
name: "get_deliverability",
|
|
1108
1511
|
description: "Fetch best-effort deliverability metrics for recent sends (7d or 30d window).",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "groundmail-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.1",
|
|
4
4
|
"mcpName": "io.github.Groundmail/groundmail",
|
|
5
5
|
"description": "Groundmail MCP server: send transactional email, manage domains, templates, webhooks and suppressions from Claude, Cursor, ChatGPT and other MCP clients",
|
|
6
6
|
"license": "MIT",
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
44
|
-
"groundmail": "0.
|
|
44
|
+
"groundmail": "0.20.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.5.0",
|