groundmail-mcp 0.5.9 → 0.5.12

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.
Files changed (3) hide show
  1. package/LICENSE +21 -0
  2. package/dist/index.js +422 -30
  3. package/package.json +9 -4
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Groundmail
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/dist/index.js CHANGED
@@ -82,10 +82,277 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
82
82
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
83
83
  import { Groundmail } from "groundmail";
84
84
 
85
+ // src/output-schemas.ts
86
+ var nullableString = (description) => ({ type: ["string", "null"], description });
87
+ var string = (description) => ({ type: "string", description });
88
+ var boolean = (description) => ({ type: "boolean", description });
89
+ var number = (description) => ({ type: "number", description });
90
+ var stringArray = (description) => ({
91
+ type: "array",
92
+ items: { type: "string" },
93
+ description
94
+ });
95
+ function object(properties, required = []) {
96
+ return { type: "object", properties, ...required.length ? { required } : {} };
97
+ }
98
+ function described(schema, description) {
99
+ return { ...schema, description };
100
+ }
101
+ function arrayOf(items, description) {
102
+ return { type: "array", items, description };
103
+ }
104
+ var okOutput = object(
105
+ { ok: boolean("True when the request completed") },
106
+ ["ok"]
107
+ );
108
+ var paginationOutput = {
109
+ has_more: boolean("True when another page of results is available"),
110
+ next_cursor: nullableString("Cursor to pass as `cursor` to fetch the next page; null on the last page")
111
+ };
112
+ function pagedOutput(key, item, description) {
113
+ return object(
114
+ {
115
+ [key]: arrayOf(item, description),
116
+ ...paginationOutput
117
+ },
118
+ [key]
119
+ );
120
+ }
121
+ var emailItem = object(
122
+ {
123
+ id: string("Email id (msg_\u2026)"),
124
+ from: string("Sender address"),
125
+ to: stringArray("Recipient addresses"),
126
+ cc: stringArray("Cc addresses"),
127
+ bcc: stringArray("Bcc addresses"),
128
+ reply_to: stringArray("Reply-To addresses"),
129
+ subject: string("Subject line"),
130
+ status: string("Current delivery status, for example sent, delivered or bounced"),
131
+ last_event: nullableString("Most recent delivery event, if any"),
132
+ bounce_reason: nullableString("Reason reported when the email bounced"),
133
+ tags: { description: "Tags attached to the email" },
134
+ scheduled_at: nullableString("When the email is scheduled to send (ISO 8601)"),
135
+ cancelled_at: nullableString("When the scheduled email was cancelled (ISO 8601)"),
136
+ created_at: string("When the email was created (ISO 8601)"),
137
+ sent_at: nullableString("When the email was handed off for delivery (ISO 8601)"),
138
+ delivered_at: nullableString("When the email was delivered (ISO 8601)"),
139
+ bounced_at: nullableString("When the email bounced (ISO 8601)"),
140
+ opened_at: nullableString("First open (ISO 8601); needs open tracking on the sending domain"),
141
+ clicked_at: nullableString("First tracked-link click (ISO 8601); needs click tracking on the sending domain")
142
+ },
143
+ ["id", "from", "to", "subject", "status"]
144
+ );
145
+ var domainItem = object(
146
+ {
147
+ id: string("Domain id"),
148
+ name: string("Domain name, for example example.com"),
149
+ status: string("Verification status: pending, verified or failed"),
150
+ created_at: string("When the domain was added (ISO 8601)"),
151
+ verified_at: nullableString("When the domain was verified (ISO 8601)"),
152
+ open_tracking: boolean("Whether open tracking is enabled"),
153
+ click_tracking: boolean("Whether click tracking is enabled"),
154
+ tracking_status: string("Whether tracking is applied: disabled, pending, active or error"),
155
+ tracking_error: nullableString("Why tracking is not active yet, when pending or in error"),
156
+ tls_mode: string("TLS mode for outgoing mail: opportunistic or enforced"),
157
+ inbound_enabled: boolean("Whether inbound receiving is enabled"),
158
+ inbound_status: string("Inbound status: disabled, pending, active or error"),
159
+ inbound_error: nullableString("Inbound setup error, if any")
160
+ },
161
+ ["id", "name", "status"]
162
+ );
163
+ var dnsRecordItem = object(
164
+ {
165
+ type: string("DNS record type, for example TXT, CNAME or MX"),
166
+ host: string("Record host name"),
167
+ value: string("Record value"),
168
+ purpose: string("What the record is for, for example SPF or DKIM"),
169
+ priority: number("MX preference, present on MX records")
170
+ },
171
+ ["type", "host", "value"]
172
+ );
173
+ var dnsRecords = arrayOf(dnsRecordItem, "DNS records to publish for the domain");
174
+ var webhookItem = object(
175
+ {
176
+ id: string("Webhook id (wh_\u2026)"),
177
+ url: string("URL that receives events"),
178
+ events: stringArray("Event types delivered to this endpoint"),
179
+ enabled: boolean("Whether the endpoint receives events"),
180
+ created_at: string("When the endpoint was created (ISO 8601)"),
181
+ updated_at: string("When the endpoint was last changed (ISO 8601)")
182
+ },
183
+ ["id", "url", "events"]
184
+ );
185
+ var suppressionItem = object(
186
+ {
187
+ id: string("Suppression id"),
188
+ email: string("Suppressed email address"),
189
+ reason: nullableString("Why the address was suppressed"),
190
+ source: string("How the suppression was created, for example api or bounce"),
191
+ created_at: string("When the suppression was created (ISO 8601)"),
192
+ updated_at: string("When the suppression was last changed (ISO 8601)")
193
+ },
194
+ ["id", "email"]
195
+ );
196
+ var templateItem = object(
197
+ {
198
+ id: string("Template id (tpl_\u2026)"),
199
+ name: string("Template name"),
200
+ alias: nullableString("Alias that can be used in place of the id"),
201
+ status: string("draft or published"),
202
+ format: string("html or blocks"),
203
+ subject: string("Subject line, may contain variables"),
204
+ preheader: nullableString("Preview text shown after the subject in some inboxes"),
205
+ html: nullableString("HTML body"),
206
+ text: nullableString("Plain-text body"),
207
+ variables: {
208
+ type: ["array", "null"],
209
+ items: { type: "string" },
210
+ description: "Variable names the template uses"
211
+ },
212
+ unsubscribe_enabled: boolean("Whether managed unsubscribe is on for sends using this template"),
213
+ category: string("transactional, product or newsletter"),
214
+ published_version_id: nullableString("Id of the version sends currently use"),
215
+ published_at: nullableString("When the template was last published (ISO 8601)"),
216
+ created_at: string("When the template was created (ISO 8601)"),
217
+ updated_at: string("When the template was last changed (ISO 8601)"),
218
+ warnings: arrayOf(
219
+ object({ code: string("Warning code"), message: string("What to fix") }),
220
+ "Content warnings found when the template was saved"
221
+ )
222
+ },
223
+ ["id", "name", "status", "subject"]
224
+ );
225
+ var bucketProperties = {
226
+ total_emails: number("Emails created in the window"),
227
+ attempted: number("Emails with a delivery attempt"),
228
+ sent: number("Emails handed off for delivery"),
229
+ delivered: number("Emails delivered"),
230
+ bounced: number("Emails that bounced"),
231
+ complained: number("Emails reported as spam"),
232
+ failed: number("Emails that failed"),
233
+ suppressed: number("Emails not sent because the recipient was suppressed"),
234
+ scheduled: number("Emails still scheduled"),
235
+ opened: number("Emails opened"),
236
+ clicked: number("Emails with a tracked click"),
237
+ bounce_rate: number("Bounced / attempted, as a fraction"),
238
+ complaint_rate: number("Complained / attempted, as a fraction"),
239
+ delivery_rate: number("Delivered / attempted, as a fraction"),
240
+ open_rate: number("Opened / delivered, as a fraction"),
241
+ click_rate: number("Clicked / delivered, as a fraction")
242
+ };
243
+ var sendEmailOutput = object(
244
+ {
245
+ id: string("Email id (msg_\u2026)"),
246
+ status: string("Status after the request, for example queued or scheduled")
247
+ },
248
+ ["id", "status"]
249
+ );
250
+ var getEmailOutput = object({ email: described(emailItem, "The email") }, ["email"]);
251
+ var domainWithRecordsOutput = object(
252
+ {
253
+ domain: described(domainItem, "The domain"),
254
+ records: dnsRecords,
255
+ inbound_records: arrayOf(dnsRecordItem, "MX records to publish when inbound receiving is enabled"),
256
+ inbound_note: string("Guidance about adding the inbound MX records")
257
+ },
258
+ ["domain", "records"]
259
+ );
260
+ var verifyDomainOutput = object(
261
+ {
262
+ domain: described(domainItem, "The domain"),
263
+ records: dnsRecords,
264
+ verified: boolean("True when the domain passed verification"),
265
+ checks: described(
266
+ object({
267
+ ownership: boolean("Ownership record found"),
268
+ spf: boolean("SPF record found"),
269
+ dkim: boolean("DKIM record found"),
270
+ dmarc: boolean("DMARC record found")
271
+ }),
272
+ "Result of each DNS check"
273
+ ),
274
+ signing_ready: boolean("True when outgoing mail can be signed for this domain"),
275
+ inbound_configured: boolean("True when inbound receiving is set up"),
276
+ inbound_pending: boolean("True while inbound setup is still in progress"),
277
+ inbound_error: nullableString("Inbound setup error, if any")
278
+ },
279
+ ["domain", "verified"]
280
+ );
281
+ var applyDnsOutput = object(
282
+ {
283
+ provider: string("DNS provider the records were written to"),
284
+ domain: string("Domain the records belong to"),
285
+ results: arrayOf(
286
+ object(
287
+ {
288
+ purpose: string("What the record is for"),
289
+ host: string("Record host name"),
290
+ action: string("created, updated, unchanged or merged"),
291
+ detail: string("Extra detail about the change")
292
+ },
293
+ ["purpose", "host", "action"]
294
+ ),
295
+ "Outcome for each record"
296
+ ),
297
+ records: dnsRecords
298
+ },
299
+ ["provider", "domain", "results"]
300
+ );
301
+ var createWebhookOutput = object(
302
+ {
303
+ webhook: described(webhookItem, "The created webhook endpoint"),
304
+ secret: string("Signing secret for verifying deliveries. Shown only once, so store it")
305
+ },
306
+ ["webhook", "secret"]
307
+ );
308
+ var suppressionOutput = object({ suppression: described(suppressionItem, "The suppression entry") }, ["suppression"]);
309
+ var templateOutput = object({ template: described(templateItem, "The template") }, ["template"]);
310
+ var testWebhookOutput = object(
311
+ {
312
+ ok: boolean("True when the simulated event was built"),
313
+ payload: { description: "The event payload that was (or would be) delivered" },
314
+ delivered: boolean("True when delivery to subscribed endpoints was started"),
315
+ deliveries: arrayOf(
316
+ object(
317
+ {
318
+ endpoint_id: string("Webhook endpoint id"),
319
+ url: string("Endpoint URL"),
320
+ success: boolean("Whether the endpoint accepted the event"),
321
+ status: number("HTTP status returned by the endpoint"),
322
+ error: string("Error message when delivery failed")
323
+ },
324
+ ["endpoint_id", "url", "success"]
325
+ ),
326
+ "Per-endpoint results when delivery ran synchronously; empty otherwise"
327
+ )
328
+ },
329
+ ["ok", "delivered"]
330
+ );
331
+ var deliverabilityOutput = object(
332
+ {
333
+ window: string("Window covered: 7d or 30d"),
334
+ window_start: string("Start of the window (ISO 8601)"),
335
+ generated_at: string("When the report was generated (ISO 8601)"),
336
+ summary: { ...object(bucketProperties), description: "Totals across all domains" },
337
+ by_domain: arrayOf(
338
+ object({ domain: string("Sending domain"), ...bucketProperties }, ["domain"]),
339
+ "Metrics for each sending domain"
340
+ ),
341
+ top_bounce_reasons: arrayOf(
342
+ object({ reason: string("Bounce reason"), count: number("Bounces with this reason") }),
343
+ "Most common bounce reasons"
344
+ ),
345
+ notes: stringArray("Caveats about how the metrics were measured")
346
+ },
347
+ ["window", "summary"]
348
+ );
349
+
85
350
  // src/tools.ts
86
351
  function textResult(value, isError = false) {
352
+ const isObject = value !== null && typeof value === "object" && !Array.isArray(value);
87
353
  return {
88
354
  content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }],
355
+ ...!isError && isObject ? { structuredContent: value } : {},
89
356
  ...isError ? { isError: true } : {}
90
357
  };
91
358
  }
@@ -352,6 +619,13 @@ var paginationProps = {
352
619
  limit: { type: "number", description: "Page size (1\u2013100)" },
353
620
  cursor: { type: "string", description: "Opaque cursor for the next page" }
354
621
  };
622
+ var readOnly = (title) => ({
623
+ title,
624
+ readOnlyHint: true,
625
+ destructiveHint: false,
626
+ idempotentHint: true,
627
+ openWorldHint: false
628
+ });
355
629
  var MCP_TOOLS = [
356
630
  {
357
631
  name: "send_email",
@@ -361,10 +635,10 @@ var MCP_TOOLS = [
361
635
  properties: {
362
636
  from: { type: "string", description: "Sender address (must match a verified domain or sandbox sender)" },
363
637
  to: { type: "string", description: "Recipient email address" },
364
- subject: { type: "string", description: "Optional when using a published template" },
365
- html: { type: "string" },
366
- text: { type: "string" },
367
- reply_to: { type: "string" },
638
+ subject: { type: "string", description: "Subject line. Optional when using a published template" },
639
+ html: { type: "string", description: "HTML body. Do not combine with template_id or template_alias" },
640
+ text: { type: "string", description: "Plain-text body. Do not combine with template_id or template_alias" },
641
+ reply_to: { type: "string", description: "Address that replies should go to" },
368
642
  template_id: { type: "string", description: "Published template UUID" },
369
643
  template_alias: { type: "string", description: "Published template alias" },
370
644
  variables: {
@@ -374,6 +648,14 @@ var MCP_TOOLS = [
374
648
  }
375
649
  },
376
650
  required: ["from", "to"]
651
+ },
652
+ outputSchema: sendEmailOutput,
653
+ annotations: {
654
+ title: "Send email",
655
+ readOnlyHint: false,
656
+ destructiveHint: false,
657
+ idempotentHint: false,
658
+ openWorldHint: true
377
659
  }
378
660
  },
379
661
  {
@@ -382,7 +664,9 @@ var MCP_TOOLS = [
382
664
  inputSchema: {
383
665
  type: "object",
384
666
  properties: { ...paginationProps }
385
- }
667
+ },
668
+ outputSchema: pagedOutput("emails", emailItem, "Emails on this page, newest first"),
669
+ annotations: readOnly("List emails")
386
670
  },
387
671
  {
388
672
  name: "get_email",
@@ -393,7 +677,9 @@ var MCP_TOOLS = [
393
677
  id: { type: "string", description: "Email id (msg_\u2026)" }
394
678
  },
395
679
  required: ["id"]
396
- }
680
+ },
681
+ outputSchema: getEmailOutput,
682
+ annotations: readOnly("Get email")
397
683
  },
398
684
  {
399
685
  name: "list_domains",
@@ -401,7 +687,9 @@ var MCP_TOOLS = [
401
687
  inputSchema: {
402
688
  type: "object",
403
689
  properties: { ...paginationProps }
404
- }
690
+ },
691
+ outputSchema: pagedOutput("domains", domainItem, "Sending domains on this page"),
692
+ annotations: readOnly("List domains")
405
693
  },
406
694
  {
407
695
  name: "create_domain",
@@ -413,6 +701,14 @@ var MCP_TOOLS = [
413
701
  inbound_enabled: { type: "boolean", description: "Enable inbound receiving on this domain" }
414
702
  },
415
703
  required: ["name"]
704
+ },
705
+ outputSchema: domainWithRecordsOutput,
706
+ annotations: {
707
+ title: "Create domain",
708
+ readOnlyHint: false,
709
+ destructiveHint: false,
710
+ idempotentHint: false,
711
+ openWorldHint: false
416
712
  }
417
713
  },
418
714
  {
@@ -421,14 +717,28 @@ var MCP_TOOLS = [
421
717
  inputSchema: {
422
718
  type: "object",
423
719
  properties: {
424
- domain: { type: "string" },
425
- provider: { type: "string", enum: ["cloudflare", "godaddy", "vercel"] },
720
+ domain: { type: "string", description: "Domain name or id of a domain already added to this account (example.com)" },
721
+ provider: {
722
+ type: "string",
723
+ enum: ["cloudflare", "godaddy", "vercel"],
724
+ description: "DNS provider that hosts the domain"
725
+ },
426
726
  credentials: {
427
727
  type: "object",
428
728
  description: "Optional inline registrar credentials (GoDaddy api key/secret)"
429
729
  }
430
730
  },
431
731
  required: ["domain", "provider"]
732
+ },
733
+ outputSchema: applyDnsOutput,
734
+ // Writes to the user's DNS provider and can update records that already exist at the same host.
735
+ // Re-running it leaves matching records unchanged.
736
+ annotations: {
737
+ title: "Apply domain DNS records",
738
+ readOnlyHint: false,
739
+ destructiveHint: true,
740
+ idempotentHint: true,
741
+ openWorldHint: true
432
742
  }
433
743
  },
434
744
  {
@@ -437,9 +747,18 @@ var MCP_TOOLS = [
437
747
  inputSchema: {
438
748
  type: "object",
439
749
  properties: {
440
- domain: { type: "string" }
750
+ domain: { type: "string", description: "Domain name or id to check (example.com)" }
441
751
  },
442
752
  required: ["domain"]
753
+ },
754
+ outputSchema: verifyDomainOutput,
755
+ // Looks up public DNS and records the result on the domain.
756
+ annotations: {
757
+ title: "Verify domain",
758
+ readOnlyHint: false,
759
+ destructiveHint: false,
760
+ idempotentHint: true,
761
+ openWorldHint: true
443
762
  }
444
763
  },
445
764
  {
@@ -448,7 +767,9 @@ var MCP_TOOLS = [
448
767
  inputSchema: {
449
768
  type: "object",
450
769
  properties: { ...paginationProps }
451
- }
770
+ },
771
+ outputSchema: pagedOutput("webhooks", webhookItem, "Webhook endpoints on this page"),
772
+ annotations: readOnly("List webhooks")
452
773
  },
453
774
  {
454
775
  name: "create_webhook",
@@ -464,6 +785,14 @@ var MCP_TOOLS = [
464
785
  }
465
786
  },
466
787
  required: ["url"]
788
+ },
789
+ outputSchema: createWebhookOutput,
790
+ annotations: {
791
+ title: "Create webhook",
792
+ readOnlyHint: false,
793
+ destructiveHint: false,
794
+ idempotentHint: false,
795
+ openWorldHint: false
467
796
  }
468
797
  },
469
798
  {
@@ -475,6 +804,14 @@ var MCP_TOOLS = [
475
804
  id: { type: "string", description: "Webhook id (wh_\u2026)" }
476
805
  },
477
806
  required: ["id"]
807
+ },
808
+ outputSchema: okOutput,
809
+ annotations: {
810
+ title: "Delete webhook",
811
+ readOnlyHint: false,
812
+ destructiveHint: true,
813
+ idempotentHint: true,
814
+ openWorldHint: false
478
815
  }
479
816
  },
480
817
  {
@@ -486,7 +823,9 @@ var MCP_TOOLS = [
486
823
  ...paginationProps,
487
824
  email: { type: "string", description: "Optional filter by email" }
488
825
  }
489
- }
826
+ },
827
+ outputSchema: pagedOutput("data", suppressionItem, "Suppressed addresses on this page"),
828
+ annotations: readOnly("List suppressions")
490
829
  },
491
830
  {
492
831
  name: "add_suppression",
@@ -494,10 +833,19 @@ var MCP_TOOLS = [
494
833
  inputSchema: {
495
834
  type: "object",
496
835
  properties: {
497
- email: { type: "string" },
498
- reason: { type: "string" }
836
+ email: { type: "string", description: "Email address to stop sending to" },
837
+ reason: { type: "string", description: "Optional note on why the address is suppressed" }
499
838
  },
500
839
  required: ["email"]
840
+ },
841
+ outputSchema: suppressionOutput,
842
+ // Adding an address that is already suppressed updates that entry instead of creating a second one.
843
+ annotations: {
844
+ title: "Add suppression",
845
+ readOnlyHint: false,
846
+ destructiveHint: false,
847
+ idempotentHint: true,
848
+ openWorldHint: false
501
849
  }
502
850
  },
503
851
  {
@@ -509,6 +857,14 @@ var MCP_TOOLS = [
509
857
  id: { type: "string", description: "Suppression id" },
510
858
  email: { type: "string", description: "Email address (alternative to id)" }
511
859
  }
860
+ },
861
+ outputSchema: okOutput,
862
+ annotations: {
863
+ title: "Remove suppression",
864
+ readOnlyHint: false,
865
+ destructiveHint: true,
866
+ idempotentHint: true,
867
+ openWorldHint: false
512
868
  }
513
869
  },
514
870
  {
@@ -518,9 +874,11 @@ var MCP_TOOLS = [
518
874
  type: "object",
519
875
  properties: {
520
876
  ...paginationProps,
521
- status: { type: "string", enum: ["draft", "published"] }
877
+ status: { type: "string", enum: ["draft", "published"], description: "Optional filter by template status" }
522
878
  }
523
- }
879
+ },
880
+ outputSchema: pagedOutput("data", templateItem, "Templates on this page"),
881
+ annotations: readOnly("List templates")
524
882
  },
525
883
  {
526
884
  name: "get_template",
@@ -531,7 +889,9 @@ var MCP_TOOLS = [
531
889
  id: { type: "string", description: "Template id (tpl_\u2026)" },
532
890
  alias: { type: "string", description: "Published alias (alternative to id)" }
533
891
  }
534
- }
892
+ },
893
+ outputSchema: templateOutput,
894
+ annotations: readOnly("Get template")
535
895
  },
536
896
  {
537
897
  name: "create_template",
@@ -539,14 +899,22 @@ var MCP_TOOLS = [
539
899
  inputSchema: {
540
900
  type: "object",
541
901
  properties: {
542
- name: { type: "string" },
543
- subject: { type: "string" },
544
- format: { type: "string", enum: ["html", "blocks"] },
545
- html: { type: "string" },
546
- text: { type: "string" },
547
- alias: { type: "string" }
902
+ name: { type: "string", description: "Template name" },
903
+ subject: { type: "string", description: "Subject line, may contain {{variables}}" },
904
+ format: { type: "string", enum: ["html", "blocks"], description: "Template format (html or blocks)" },
905
+ html: { type: "string", description: "HTML body" },
906
+ text: { type: "string", description: "Plain-text body" },
907
+ alias: { type: "string", description: "Optional short alias to reference the template by instead of its id" }
548
908
  },
549
909
  required: ["name", "subject"]
910
+ },
911
+ outputSchema: templateOutput,
912
+ annotations: {
913
+ title: "Create template",
914
+ readOnlyHint: false,
915
+ destructiveHint: false,
916
+ idempotentHint: false,
917
+ openWorldHint: false
550
918
  }
551
919
  },
552
920
  {
@@ -555,9 +923,18 @@ var MCP_TOOLS = [
555
923
  inputSchema: {
556
924
  type: "object",
557
925
  properties: {
558
- id: { type: "string" },
559
- alias: { type: "string" }
926
+ id: { type: "string", description: "Template id (tpl_\u2026)" },
927
+ alias: { type: "string", description: "Template alias (alternative to id)" }
560
928
  }
929
+ },
930
+ outputSchema: templateOutput,
931
+ // Each publish snapshots a new version; earlier versions are kept.
932
+ annotations: {
933
+ title: "Publish template",
934
+ readOnlyHint: false,
935
+ destructiveHint: false,
936
+ idempotentHint: false,
937
+ openWorldHint: false
561
938
  }
562
939
  },
563
940
  {
@@ -578,6 +955,14 @@ var MCP_TOOLS = [
578
955
  }
579
956
  },
580
957
  required: ["event"]
958
+ },
959
+ outputSchema: testWebhookOutput,
960
+ annotations: {
961
+ title: "Send test webhook event",
962
+ readOnlyHint: false,
963
+ destructiveHint: false,
964
+ idempotentHint: false,
965
+ openWorldHint: true
581
966
  }
582
967
  },
583
968
  {
@@ -586,9 +971,11 @@ var MCP_TOOLS = [
586
971
  inputSchema: {
587
972
  type: "object",
588
973
  properties: {
589
- window: { type: "string", enum: ["7d", "30d"] }
974
+ window: { type: "string", enum: ["7d", "30d"], description: "Time window to summarize (default 30d)" }
590
975
  }
591
- }
976
+ },
977
+ outputSchema: deliverabilityOutput,
978
+ annotations: readOnly("Get deliverability metrics")
592
979
  }
593
980
  ];
594
981
  function resolveApiKey() {
@@ -603,7 +990,7 @@ function resolveApiKey() {
603
990
  }
604
991
 
605
992
  // src/server.ts
606
- var MCP_SERVER_VERSION = "0.5.9";
993
+ var MCP_SERVER_VERSION = "0.5.11";
607
994
  var MCP_SERVER_INSTRUCTIONS = [
608
995
  "Groundmail sends transactional email from domains the user has verified.",
609
996
  'To test before a domain is verified, call send_email with from "noreply@mail.groundmail.dev" and to set to the Groundmail account email; sandbox sends to any other address are rejected.',
@@ -621,7 +1008,10 @@ function createMcpServer(apiKey, baseUrl) {
621
1008
  tools: MCP_TOOLS.map((tool) => ({
622
1009
  name: tool.name,
623
1010
  description: tool.description,
624
- inputSchema: tool.inputSchema
1011
+ title: tool.annotations.title,
1012
+ inputSchema: tool.inputSchema,
1013
+ outputSchema: tool.outputSchema,
1014
+ annotations: tool.annotations
625
1015
  }))
626
1016
  }));
627
1017
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
@@ -655,7 +1045,9 @@ var MCP_SCOPES_SUPPORTED = [
655
1045
  "webhooks:write",
656
1046
  "templates:write",
657
1047
  "suppressions:write",
658
- "account:read"
1048
+ "account:read",
1049
+ "openid",
1050
+ "email"
659
1051
  ];
660
1052
  function trimOrigin(origin) {
661
1053
  return origin.replace(/\/$/, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "groundmail-mcp",
3
- "version": "0.5.9",
3
+ "version": "0.5.12",
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",
@@ -11,11 +11,12 @@
11
11
  "files": [
12
12
  "dist",
13
13
  "README.md",
14
- "smithery.yaml"
14
+ "smithery.yaml",
15
+ "LICENSE"
15
16
  ],
16
17
  "repository": {
17
18
  "type": "git",
18
- "url": "git+https://github.com/Groundmail/groundmail-sdks.git"
19
+ "url": "git+https://github.com/Groundmail/groundmail-mcp.git"
19
20
  },
20
21
  "homepage": "https://docs.groundmail.dev/ai/mcp",
21
22
  "keywords": [
@@ -45,6 +46,10 @@
45
46
  "devDependencies": {
46
47
  "tsup": "^8.5.0",
47
48
  "typescript": "^5",
48
- "vitest": "^3.2.4"
49
+ "vitest": "^3.2.4",
50
+ "@types/node": "^20"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/Groundmail/groundmail-mcp/issues"
49
54
  }
50
55
  }