groundmail-mcp 0.9.0 → 0.11.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 +1 -1
- package/dist/index.js +420 -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,6 +572,34 @@ 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",
|
|
@@ -497,6 +634,7 @@ async function callMcpTool(client, name, args) {
|
|
|
497
634
|
html: optString(args, "html"),
|
|
498
635
|
text: optString(args, "text"),
|
|
499
636
|
reply_to: optString(args, "reply_to"),
|
|
637
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {},
|
|
500
638
|
...templateId ? { template: { id: templateId, ...variables ? { variables } : {} } } : {}
|
|
501
639
|
});
|
|
502
640
|
return textResult(result);
|
|
@@ -505,7 +643,8 @@ async function callMcpTool(client, name, args) {
|
|
|
505
643
|
const result = await client.emails.list({
|
|
506
644
|
limit: optNumber(args, "limit"),
|
|
507
645
|
cursor: optString(args, "cursor"),
|
|
508
|
-
...typeof args.test === "boolean" ? { test: args.test } : {}
|
|
646
|
+
...typeof args.test === "boolean" ? { test: args.test } : {},
|
|
647
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
509
648
|
});
|
|
510
649
|
return textResult(result);
|
|
511
650
|
}
|
|
@@ -542,7 +681,8 @@ async function callMcpTool(client, name, args) {
|
|
|
542
681
|
case "list_domains": {
|
|
543
682
|
const result = await client.domains.list({
|
|
544
683
|
limit: optNumber(args, "limit"),
|
|
545
|
-
cursor: optString(args, "cursor")
|
|
684
|
+
cursor: optString(args, "cursor"),
|
|
685
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
546
686
|
});
|
|
547
687
|
return textResult(result);
|
|
548
688
|
}
|
|
@@ -553,7 +693,8 @@ async function callMcpTool(client, name, args) {
|
|
|
553
693
|
return textResult(
|
|
554
694
|
await client.domains.create({
|
|
555
695
|
name: domainName,
|
|
556
|
-
...inbound !== void 0 ? { inbound_enabled: inbound } : {}
|
|
696
|
+
...inbound !== void 0 ? { inbound_enabled: inbound } : {},
|
|
697
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
557
698
|
})
|
|
558
699
|
);
|
|
559
700
|
}
|
|
@@ -569,10 +710,12 @@ async function callMcpTool(client, name, args) {
|
|
|
569
710
|
const url = optString(args, "url");
|
|
570
711
|
if (!url) return textResult("Missing required argument: url", true);
|
|
571
712
|
const events = optStringArray(args, "events");
|
|
713
|
+
const tenants = optStringArray(args, "tenants");
|
|
572
714
|
return textResult(
|
|
573
715
|
await client.webhooks.create({
|
|
574
716
|
url,
|
|
575
|
-
...events ? { events } : {}
|
|
717
|
+
...events ? { events } : {},
|
|
718
|
+
...tenants?.length ? { tenants } : {}
|
|
576
719
|
})
|
|
577
720
|
);
|
|
578
721
|
}
|
|
@@ -586,7 +729,8 @@ async function callMcpTool(client, name, args) {
|
|
|
586
729
|
await client.suppressions.list({
|
|
587
730
|
limit: optNumber(args, "limit"),
|
|
588
731
|
cursor: optString(args, "cursor"),
|
|
589
|
-
email: optString(args, "email")
|
|
732
|
+
email: optString(args, "email"),
|
|
733
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
590
734
|
})
|
|
591
735
|
);
|
|
592
736
|
}
|
|
@@ -601,7 +745,8 @@ async function callMcpTool(client, name, args) {
|
|
|
601
745
|
return textResult(
|
|
602
746
|
await client.suppressions.create({
|
|
603
747
|
email,
|
|
604
|
-
reason: optString(args, "reason")
|
|
748
|
+
reason: optString(args, "reason"),
|
|
749
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
605
750
|
})
|
|
606
751
|
);
|
|
607
752
|
}
|
|
@@ -610,7 +755,8 @@ async function callMcpTool(client, name, args) {
|
|
|
610
755
|
if (!idOrEmail) {
|
|
611
756
|
return textResult("Missing required argument: id or email", true);
|
|
612
757
|
}
|
|
613
|
-
|
|
758
|
+
const tenant = optString(args, "tenant");
|
|
759
|
+
return textResult(tenant ? await client.suppressions.remove(idOrEmail, { tenant }) : await client.suppressions.remove(idOrEmail));
|
|
614
760
|
}
|
|
615
761
|
case "list_templates": {
|
|
616
762
|
const status = optString(args, "status");
|
|
@@ -661,10 +807,79 @@ async function callMcpTool(client, name, args) {
|
|
|
661
807
|
await client.emails.testWebhook({
|
|
662
808
|
event,
|
|
663
809
|
email_id: optString(args, "email_id"),
|
|
664
|
-
deliver: optBoolean(args, "deliver")
|
|
810
|
+
deliver: optBoolean(args, "deliver"),
|
|
811
|
+
...optString(args, "tenant") ? { tenant: optString(args, "tenant") } : {}
|
|
665
812
|
})
|
|
666
813
|
);
|
|
667
814
|
}
|
|
815
|
+
case "list_tenants": {
|
|
816
|
+
const status = optString(args, "status");
|
|
817
|
+
return textResult(
|
|
818
|
+
await client.tenants.list({
|
|
819
|
+
limit: optNumber(args, "limit"),
|
|
820
|
+
cursor: optString(args, "cursor"),
|
|
821
|
+
...status === "active" || status === "paused" ? { status } : {}
|
|
822
|
+
})
|
|
823
|
+
);
|
|
824
|
+
}
|
|
825
|
+
case "create_tenant": {
|
|
826
|
+
const externalId = optString(args, "external_id");
|
|
827
|
+
if (!externalId) return textResult("Missing required argument: external_id", true);
|
|
828
|
+
const settings = tenantSettings(args);
|
|
829
|
+
if (typeof settings === "string") return textResult(settings, true);
|
|
830
|
+
const { name: name2, ...rest } = settings;
|
|
831
|
+
return textResult(
|
|
832
|
+
await client.tenants.create({ external_id: externalId, ...name2 ? { name: name2 } : {}, ...rest })
|
|
833
|
+
);
|
|
834
|
+
}
|
|
835
|
+
case "get_tenant": {
|
|
836
|
+
const tenant = optString(args, "tenant");
|
|
837
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
838
|
+
return textResult(await client.tenants.get(tenant));
|
|
839
|
+
}
|
|
840
|
+
case "update_tenant": {
|
|
841
|
+
const tenant = optString(args, "tenant");
|
|
842
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
843
|
+
const settings = tenantSettings(args);
|
|
844
|
+
if (typeof settings === "string") return textResult(settings, true);
|
|
845
|
+
if (!Object.keys(settings).length) {
|
|
846
|
+
return textResult(
|
|
847
|
+
"Nothing to update: pass name, default_from_domain, stream_policy, daily_limit or metadata (use pause_tenant / resume_tenant to pause or resume)",
|
|
848
|
+
true
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return textResult(await client.tenants.update(tenant, settings));
|
|
852
|
+
}
|
|
853
|
+
case "pause_tenant": {
|
|
854
|
+
const tenant = optString(args, "tenant");
|
|
855
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
856
|
+
return textResult(await client.tenants.pause(tenant, optString(args, "reason")));
|
|
857
|
+
}
|
|
858
|
+
case "resume_tenant": {
|
|
859
|
+
const tenant = optString(args, "tenant");
|
|
860
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
861
|
+
return textResult(await client.tenants.resume(tenant));
|
|
862
|
+
}
|
|
863
|
+
case "get_tenant_stats": {
|
|
864
|
+
const tenant = optString(args, "tenant");
|
|
865
|
+
if (!tenant) return textResult("Missing required argument: tenant", true);
|
|
866
|
+
const days = optNumber(args, "days");
|
|
867
|
+
return textResult(await client.tenants.stats(tenant, days !== void 0 ? { days } : {}));
|
|
868
|
+
}
|
|
869
|
+
case "get_dmarc_report": {
|
|
870
|
+
const domain = optString(args, "domain");
|
|
871
|
+
if (!domain) return textResult("Missing required argument: domain", true);
|
|
872
|
+
return textResult(await client.dmarc.get(domain));
|
|
873
|
+
}
|
|
874
|
+
case "enable_dmarc_monitoring": {
|
|
875
|
+
const domain = optString(args, "domain");
|
|
876
|
+
if (!domain) return textResult("Missing required argument: domain", true);
|
|
877
|
+
const apply = optString(args, "apply");
|
|
878
|
+
if (apply && apply !== "cloudflare" && apply !== "vercel" && apply !== "godaddy") {
|
|
879
|
+
return textResult("apply must be cloudflare, vercel or godaddy", true);
|
|
880
|
+
}
|
|
881
|
+
return textResult(await client.dmarc.enable(domain, apply ? { apply } : {}));
|
|
882
|
+
}
|
|
668
883
|
case "get_deliverability": {
|
|
669
884
|
const window = args.window === "7d" ? "7d" : "30d";
|
|
670
885
|
const result = await client.emails.insights(window);
|
|
@@ -738,6 +953,27 @@ var paginationProps = {
|
|
|
738
953
|
limit: { type: "number", description: "Page size (1\u2013100)" },
|
|
739
954
|
cursor: { type: "string", description: "Opaque cursor for the next page" }
|
|
740
955
|
};
|
|
956
|
+
var tenantProp = (description) => ({
|
|
957
|
+
type: "string",
|
|
958
|
+
description: `${description}. The tenant's external id (your id for that customer) or its Groundmail id`
|
|
959
|
+
});
|
|
960
|
+
var tenantSettingsProps = {
|
|
961
|
+
name: { type: "string", description: "Display name" },
|
|
962
|
+
default_from_domain: {
|
|
963
|
+
type: ["string", "null"],
|
|
964
|
+
description: "Domain that completes a bare from (billing \u2192 billing@<domain>) on sends naming this tenant; null to clear"
|
|
965
|
+
},
|
|
966
|
+
stream_policy: {
|
|
967
|
+
type: "string",
|
|
968
|
+
enum: ["all", "transactional_only"],
|
|
969
|
+
description: "all (default), or transactional_only to refuse product email for this tenant"
|
|
970
|
+
},
|
|
971
|
+
daily_limit: {
|
|
972
|
+
type: ["number", "null"],
|
|
973
|
+
description: "Most recipients per UTC day for this tenant; null for no tenant limit"
|
|
974
|
+
},
|
|
975
|
+
metadata: { type: ["object", "null"], description: "Your own data about the tenant (JSON object); null to clear" }
|
|
976
|
+
};
|
|
741
977
|
var readOnly = (title) => ({
|
|
742
978
|
title,
|
|
743
979
|
readOnlyHint: true,
|
|
@@ -767,7 +1003,10 @@ var MCP_TOOLS = [
|
|
|
767
1003
|
type: "object",
|
|
768
1004
|
description: "Template variables when using template_id or template_alias",
|
|
769
1005
|
additionalProperties: true
|
|
770
|
-
}
|
|
1006
|
+
},
|
|
1007
|
+
tenant: tenantProp(
|
|
1008
|
+
"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"
|
|
1009
|
+
)
|
|
771
1010
|
},
|
|
772
1011
|
required: ["from", "to"]
|
|
773
1012
|
},
|
|
@@ -787,7 +1026,8 @@ var MCP_TOOLS = [
|
|
|
787
1026
|
type: "object",
|
|
788
1027
|
properties: {
|
|
789
1028
|
...paginationProps,
|
|
790
|
-
test: { type: "boolean", description: "true: only test emails; false: only live ones. Omit for both" }
|
|
1029
|
+
test: { type: "boolean", description: "true: only test emails; false: only live ones. Omit for both" },
|
|
1030
|
+
tenant: tenantProp("Only emails sent as this tenant")
|
|
791
1031
|
}
|
|
792
1032
|
},
|
|
793
1033
|
outputSchema: pagedOutput("emails", emailItem, "Emails on this page, newest first"),
|
|
@@ -811,7 +1051,7 @@ var MCP_TOOLS = [
|
|
|
811
1051
|
description: "List sending domains for this account.",
|
|
812
1052
|
inputSchema: {
|
|
813
1053
|
type: "object",
|
|
814
|
-
properties: { ...paginationProps }
|
|
1054
|
+
properties: { ...paginationProps, tenant: tenantProp("Only domains this tenant owns") }
|
|
815
1055
|
},
|
|
816
1056
|
outputSchema: pagedOutput("domains", domainItem, "Sending domains on this page"),
|
|
817
1057
|
annotations: readOnly("List domains")
|
|
@@ -823,7 +1063,8 @@ var MCP_TOOLS = [
|
|
|
823
1063
|
type: "object",
|
|
824
1064
|
properties: {
|
|
825
1065
|
name: { type: "string", description: "Domain name (example.com)" },
|
|
826
|
-
inbound_enabled: { type: "boolean", description: "Enable inbound receiving on this domain" }
|
|
1066
|
+
inbound_enabled: { type: "boolean", description: "Enable inbound receiving on this domain" },
|
|
1067
|
+
tenant: tenantProp("Give the domain to this tenant; only that tenant can then send from it")
|
|
827
1068
|
},
|
|
828
1069
|
required: ["name"]
|
|
829
1070
|
},
|
|
@@ -907,6 +1148,11 @@ var MCP_TOOLS = [
|
|
|
907
1148
|
type: "array",
|
|
908
1149
|
items: { type: "string" },
|
|
909
1150
|
description: "Optional event types (defaults apply when omitted)"
|
|
1151
|
+
},
|
|
1152
|
+
tenants: {
|
|
1153
|
+
type: "array",
|
|
1154
|
+
items: { type: "string" },
|
|
1155
|
+
description: "Optional: only these tenants' email events (external ids or Groundmail ids). Omit for all"
|
|
910
1156
|
}
|
|
911
1157
|
},
|
|
912
1158
|
required: ["url"]
|
|
@@ -946,7 +1192,8 @@ var MCP_TOOLS = [
|
|
|
946
1192
|
type: "object",
|
|
947
1193
|
properties: {
|
|
948
1194
|
...paginationProps,
|
|
949
|
-
email: { type: "string", description: "Optional filter by email" }
|
|
1195
|
+
email: { type: "string", description: "Optional filter by email" },
|
|
1196
|
+
tenant: tenantProp("List this tenant's own suppression list instead of the account's")
|
|
950
1197
|
}
|
|
951
1198
|
},
|
|
952
1199
|
outputSchema: pagedOutput("data", suppressionItem, "Suppressed addresses on this page"),
|
|
@@ -972,7 +1219,8 @@ var MCP_TOOLS = [
|
|
|
972
1219
|
type: "object",
|
|
973
1220
|
properties: {
|
|
974
1221
|
email: { type: "string", description: "Email address to stop sending to" },
|
|
975
|
-
reason: { type: "string", description: "Optional note on why the address is suppressed" }
|
|
1222
|
+
reason: { type: "string", description: "Optional note on why the address is suppressed" },
|
|
1223
|
+
tenant: tenantProp("Add to this tenant's own list instead of the account's")
|
|
976
1224
|
},
|
|
977
1225
|
required: ["email"]
|
|
978
1226
|
},
|
|
@@ -993,7 +1241,8 @@ var MCP_TOOLS = [
|
|
|
993
1241
|
type: "object",
|
|
994
1242
|
properties: {
|
|
995
1243
|
id: { type: "string", description: "Suppression id" },
|
|
996
|
-
email: { type: "string", description: "Email address (alternative to id)" }
|
|
1244
|
+
email: { type: "string", description: "Email address (alternative to id)" },
|
|
1245
|
+
tenant: tenantProp("Remove the address (by email) from this tenant's own list")
|
|
997
1246
|
}
|
|
998
1247
|
},
|
|
999
1248
|
outputSchema: okOutput,
|
|
@@ -1090,7 +1339,8 @@ var MCP_TOOLS = [
|
|
|
1090
1339
|
deliver: {
|
|
1091
1340
|
type: "boolean",
|
|
1092
1341
|
description: "When true (default), enqueue delivery to account webhooks"
|
|
1093
|
-
}
|
|
1342
|
+
},
|
|
1343
|
+
tenant: tenantProp("Put this tenant on the test event, to test endpoints that only receive some tenants")
|
|
1094
1344
|
},
|
|
1095
1345
|
required: ["event"]
|
|
1096
1346
|
},
|
|
@@ -1103,6 +1353,158 @@ var MCP_TOOLS = [
|
|
|
1103
1353
|
openWorldHint: true
|
|
1104
1354
|
}
|
|
1105
1355
|
},
|
|
1356
|
+
{
|
|
1357
|
+
name: "list_tenants",
|
|
1358
|
+
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.",
|
|
1359
|
+
inputSchema: {
|
|
1360
|
+
type: "object",
|
|
1361
|
+
properties: {
|
|
1362
|
+
...paginationProps,
|
|
1363
|
+
status: { type: "string", enum: ["active", "paused"], description: "Optional filter by status" }
|
|
1364
|
+
}
|
|
1365
|
+
},
|
|
1366
|
+
outputSchema: pagedOutput("data", tenantItem, "Tenants on this page, newest first"),
|
|
1367
|
+
annotations: readOnly("List tenants")
|
|
1368
|
+
},
|
|
1369
|
+
{
|
|
1370
|
+
name: "create_tenant",
|
|
1371
|
+
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).",
|
|
1372
|
+
inputSchema: {
|
|
1373
|
+
type: "object",
|
|
1374
|
+
properties: {
|
|
1375
|
+
external_id: {
|
|
1376
|
+
type: "string",
|
|
1377
|
+
description: "Your id for this customer: 1\u2013128 characters of letters, digits and . _ : @ -. Cannot be changed later"
|
|
1378
|
+
},
|
|
1379
|
+
...tenantSettingsProps
|
|
1380
|
+
},
|
|
1381
|
+
required: ["external_id"]
|
|
1382
|
+
},
|
|
1383
|
+
outputSchema: tenantOutput,
|
|
1384
|
+
annotations: {
|
|
1385
|
+
title: "Create tenant",
|
|
1386
|
+
readOnlyHint: false,
|
|
1387
|
+
destructiveHint: false,
|
|
1388
|
+
idempotentHint: false,
|
|
1389
|
+
openWorldHint: false
|
|
1390
|
+
}
|
|
1391
|
+
},
|
|
1392
|
+
{
|
|
1393
|
+
name: "get_tenant",
|
|
1394
|
+
description: "Get one tenant, including whether it is paused and why.",
|
|
1395
|
+
inputSchema: {
|
|
1396
|
+
type: "object",
|
|
1397
|
+
properties: { tenant: tenantProp("The tenant") },
|
|
1398
|
+
required: ["tenant"]
|
|
1399
|
+
},
|
|
1400
|
+
outputSchema: tenantOutput,
|
|
1401
|
+
annotations: readOnly("Get tenant")
|
|
1402
|
+
},
|
|
1403
|
+
{
|
|
1404
|
+
name: "update_tenant",
|
|
1405
|
+
description: "Change a tenant's name, default from domain, stream policy, daily limit or metadata.",
|
|
1406
|
+
inputSchema: {
|
|
1407
|
+
type: "object",
|
|
1408
|
+
properties: { tenant: tenantProp("The tenant to change"), ...tenantSettingsProps },
|
|
1409
|
+
required: ["tenant"]
|
|
1410
|
+
},
|
|
1411
|
+
outputSchema: tenantOutput,
|
|
1412
|
+
annotations: {
|
|
1413
|
+
title: "Update tenant",
|
|
1414
|
+
readOnlyHint: false,
|
|
1415
|
+
destructiveHint: false,
|
|
1416
|
+
idempotentHint: true,
|
|
1417
|
+
openWorldHint: false
|
|
1418
|
+
}
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
name: "pause_tenant",
|
|
1422
|
+
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.",
|
|
1423
|
+
inputSchema: {
|
|
1424
|
+
type: "object",
|
|
1425
|
+
properties: {
|
|
1426
|
+
tenant: tenantProp("The tenant to pause"),
|
|
1427
|
+
reason: { type: "string", description: "Why it is paused (shown on the tenant)" }
|
|
1428
|
+
},
|
|
1429
|
+
required: ["tenant"]
|
|
1430
|
+
},
|
|
1431
|
+
outputSchema: tenantOutput,
|
|
1432
|
+
annotations: {
|
|
1433
|
+
title: "Pause tenant",
|
|
1434
|
+
readOnlyHint: false,
|
|
1435
|
+
destructiveHint: false,
|
|
1436
|
+
idempotentHint: true,
|
|
1437
|
+
openWorldHint: false
|
|
1438
|
+
}
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: "resume_tenant",
|
|
1442
|
+
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.",
|
|
1443
|
+
inputSchema: {
|
|
1444
|
+
type: "object",
|
|
1445
|
+
properties: { tenant: tenantProp("The tenant to resume") },
|
|
1446
|
+
required: ["tenant"]
|
|
1447
|
+
},
|
|
1448
|
+
outputSchema: tenantOutput,
|
|
1449
|
+
annotations: {
|
|
1450
|
+
title: "Resume tenant",
|
|
1451
|
+
readOnlyHint: false,
|
|
1452
|
+
destructiveHint: false,
|
|
1453
|
+
idempotentHint: true,
|
|
1454
|
+
openWorldHint: false
|
|
1455
|
+
}
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
name: "get_tenant_stats",
|
|
1459
|
+
description: "A tenant's email counts, bounce and complaint rates over the last days (1\u201390, default 7), and its sending ramp today.",
|
|
1460
|
+
inputSchema: {
|
|
1461
|
+
type: "object",
|
|
1462
|
+
properties: {
|
|
1463
|
+
tenant: tenantProp("The tenant"),
|
|
1464
|
+
days: { type: "number", description: "Days to cover, 1\u201390 (default 7)" }
|
|
1465
|
+
},
|
|
1466
|
+
required: ["tenant"]
|
|
1467
|
+
},
|
|
1468
|
+
outputSchema: tenantStatsOutput,
|
|
1469
|
+
annotations: readOnly("Get tenant stats")
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
name: "get_dmarc_report",
|
|
1473
|
+
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.",
|
|
1474
|
+
inputSchema: {
|
|
1475
|
+
type: "object",
|
|
1476
|
+
properties: {
|
|
1477
|
+
domain: { type: "string", description: "Domain name or id (example.com)" }
|
|
1478
|
+
},
|
|
1479
|
+
required: ["domain"]
|
|
1480
|
+
},
|
|
1481
|
+
outputSchema: dmarcOutput,
|
|
1482
|
+
annotations: readOnly("Get DMARC report")
|
|
1483
|
+
},
|
|
1484
|
+
{
|
|
1485
|
+
name: "enable_dmarc_monitoring",
|
|
1486
|
+
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.",
|
|
1487
|
+
inputSchema: {
|
|
1488
|
+
type: "object",
|
|
1489
|
+
properties: {
|
|
1490
|
+
domain: { type: "string", description: "Domain name or id (example.com)" },
|
|
1491
|
+
apply: {
|
|
1492
|
+
type: "string",
|
|
1493
|
+
enum: ["cloudflare", "vercel", "godaddy"],
|
|
1494
|
+
description: "Also write the DMARC record through this DNS provider (connected under Settings \u2192 Integrations)"
|
|
1495
|
+
}
|
|
1496
|
+
},
|
|
1497
|
+
required: ["domain"]
|
|
1498
|
+
},
|
|
1499
|
+
outputSchema: dmarcOutput,
|
|
1500
|
+
annotations: {
|
|
1501
|
+
title: "Enable DMARC monitoring",
|
|
1502
|
+
readOnlyHint: false,
|
|
1503
|
+
destructiveHint: false,
|
|
1504
|
+
idempotentHint: true,
|
|
1505
|
+
openWorldHint: true
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1106
1508
|
{
|
|
1107
1509
|
name: "get_deliverability",
|
|
1108
1510
|
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.0",
|
|
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.19.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"tsup": "^8.5.0",
|