@aiwerk/mcp-server-resend 0.1.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.
Files changed (55) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/LICENSE +21 -0
  3. package/README.md +154 -0
  4. package/dist/src/errors.d.ts +16 -0
  5. package/dist/src/errors.d.ts.map +1 -0
  6. package/dist/src/errors.js +21 -0
  7. package/dist/src/lib/destructive-gate.d.ts +27 -0
  8. package/dist/src/lib/destructive-gate.d.ts.map +1 -0
  9. package/dist/src/lib/destructive-gate.js +72 -0
  10. package/dist/src/lib/idempotency.d.ts +2 -0
  11. package/dist/src/lib/idempotency.d.ts.map +1 -0
  12. package/dist/src/lib/idempotency.js +12 -0
  13. package/dist/src/lib/resend-client.d.ts +13 -0
  14. package/dist/src/lib/resend-client.d.ts.map +1 -0
  15. package/dist/src/lib/resend-client.js +128 -0
  16. package/dist/src/server.d.ts +12 -0
  17. package/dist/src/server.d.ts.map +1 -0
  18. package/dist/src/server.js +400 -0
  19. package/dist/src/tools/apikeys.d.ts +37 -0
  20. package/dist/src/tools/apikeys.d.ts.map +1 -0
  21. package/dist/src/tools/apikeys.js +72 -0
  22. package/dist/src/tools/broadcasts.d.ts +85 -0
  23. package/dist/src/tools/broadcasts.d.ts.map +1 -0
  24. package/dist/src/tools/broadcasts.js +118 -0
  25. package/dist/src/tools/contact-properties.d.ts +47 -0
  26. package/dist/src/tools/contact-properties.d.ts.map +1 -0
  27. package/dist/src/tools/contact-properties.js +80 -0
  28. package/dist/src/tools/contacts.d.ts +137 -0
  29. package/dist/src/tools/contacts.d.ts.map +1 -0
  30. package/dist/src/tools/contacts.js +139 -0
  31. package/dist/src/tools/domains.d.ts +95 -0
  32. package/dist/src/tools/domains.d.ts.map +1 -0
  33. package/dist/src/tools/domains.js +118 -0
  34. package/dist/src/tools/emails.d.ts +214 -0
  35. package/dist/src/tools/emails.d.ts.map +1 -0
  36. package/dist/src/tools/emails.js +142 -0
  37. package/dist/src/tools/logs.d.ts +18 -0
  38. package/dist/src/tools/logs.d.ts.map +1 -0
  39. package/dist/src/tools/logs.js +18 -0
  40. package/dist/src/tools/segments.d.ts +49 -0
  41. package/dist/src/tools/segments.d.ts.map +1 -0
  42. package/dist/src/tools/segments.js +79 -0
  43. package/dist/src/tools/templates.d.ts +113 -0
  44. package/dist/src/tools/templates.d.ts.map +1 -0
  45. package/dist/src/tools/templates.js +113 -0
  46. package/dist/src/tools/topics.d.ts +53 -0
  47. package/dist/src/tools/topics.d.ts.map +1 -0
  48. package/dist/src/tools/topics.js +81 -0
  49. package/dist/src/tools/webhooks.d.ts +49 -0
  50. package/dist/src/tools/webhooks.d.ts.map +1 -0
  51. package/dist/src/tools/webhooks.js +87 -0
  52. package/dist/src/version.d.ts +2 -0
  53. package/dist/src/version.d.ts.map +1 -0
  54. package/dist/src/version.js +2 -0
  55. package/package.json +42 -0
@@ -0,0 +1,400 @@
1
+ #!/usr/bin/env node
2
+ import { realpathSync } from 'fs';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { ResendApiError, ResendConfigError, ResendNetworkError, ResendTimeoutError, } from './errors.js';
7
+ import { VERSION } from './version.js';
8
+ import { emailSend, emailSendInputSchema, emailSendBatch, emailSendBatchInputSchema, emailGet, emailGetInputSchema, emailList, emailListInputSchema, emailUpdate, emailUpdateInputSchema, emailCancel, emailCancelInputSchema, emailListReceived, emailListReceivedInputSchema, emailGetReceived, emailGetReceivedInputSchema, } from './tools/emails.js';
9
+ import { domainCreate, domainCreateInputSchema, domainList, domainListInputSchema, domainGet, domainGetInputSchema, domainUpdate, domainUpdateInputSchema, domainVerify, domainVerifyInputSchema, domainDelete, domainDeleteInputSchema, } from './tools/domains.js';
10
+ import { contactCreate, contactCreateInputSchema, contactList, contactListInputSchema, contactGet, contactGetInputSchema, contactUpdate, contactUpdateInputSchema, contactDelete, contactDeleteInputSchema, contactListSegments, contactListSegmentsInputSchema, contactAddSegment, contactAddSegmentInputSchema, contactRemoveSegment, contactRemoveSegmentInputSchema, contactGetTopics, contactGetTopicsInputSchema, contactUpdateTopics, contactUpdateTopicsInputSchema, } from './tools/contacts.js';
11
+ import { contactPropertyCreate, contactPropertyCreateInputSchema, contactPropertyList, contactPropertyListInputSchema, contactPropertyGet, contactPropertyGetInputSchema, contactPropertyUpdate, contactPropertyUpdateInputSchema, contactPropertyDelete, contactPropertyDeleteInputSchema, } from './tools/contact-properties.js';
12
+ import { segmentCreate, segmentCreateInputSchema, segmentList, segmentListInputSchema, segmentGet, segmentGetInputSchema, segmentListContacts, segmentListContactsInputSchema, segmentDelete, segmentDeleteInputSchema, } from './tools/segments.js';
13
+ import { templateCreate, templateCreateInputSchema, templateList, templateListInputSchema, templateGet, templateGetInputSchema, templateUpdate, templateUpdateInputSchema, templateDelete, templateDeleteInputSchema, templateDuplicate, templateDuplicateInputSchema, templatePublish, templatePublishInputSchema, } from './tools/templates.js';
14
+ import { topicCreate, topicCreateInputSchema, topicList, topicListInputSchema, topicGet, topicGetInputSchema, topicUpdate, topicUpdateInputSchema, topicDelete, topicDeleteInputSchema, } from './tools/topics.js';
15
+ import { broadcastCreate, broadcastCreateInputSchema, broadcastList, broadcastListInputSchema, broadcastGet, broadcastGetInputSchema, broadcastUpdate, broadcastUpdateInputSchema, broadcastDelete, broadcastDeleteInputSchema, broadcastSend, broadcastSendInputSchema, } from './tools/broadcasts.js';
16
+ import { webhookCreate, webhookCreateInputSchema, webhookList, webhookListInputSchema, webhookGet, webhookGetInputSchema, webhookUpdate, webhookUpdateInputSchema, webhookDelete, webhookDeleteInputSchema, } from './tools/webhooks.js';
17
+ import { logList, logListInputSchema, logGet, logGetInputSchema } from './tools/logs.js';
18
+ import { apikeyCreate, apikeyCreateInputSchema, apikeyList, apikeyListInputSchema, apikeyDelete, apikeyDeleteInputSchema, } from './tools/apikeys.js';
19
+ function toolSuccess(data) {
20
+ return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
21
+ }
22
+ export function toolError(error) {
23
+ let message;
24
+ if (error instanceof ResendTimeoutError) {
25
+ message = `Timeout: ${error.message}. Raise RESEND_API_TIMEOUT_MS or retry.`;
26
+ }
27
+ else if (error instanceof ResendNetworkError) {
28
+ message = `Network error: ${error.message}. Check connectivity.`;
29
+ }
30
+ else if (error instanceof ResendConfigError) {
31
+ message = `Configuration error: ${error.message}`;
32
+ }
33
+ else if (error instanceof ResendApiError) {
34
+ // error.message is the compact "{status} on {endpoint} — {hint}" from compactError().
35
+ // Do NOT append raw body — that risks PII leakage (Decision #9).
36
+ message = error.message;
37
+ }
38
+ else if (error instanceof Error) {
39
+ message = error.message;
40
+ }
41
+ else {
42
+ message = String(error);
43
+ }
44
+ return { isError: true, content: [{ type: 'text', text: message }] };
45
+ }
46
+ function wrap(fn) {
47
+ return async (args) => {
48
+ try {
49
+ return toolSuccess(await fn(args));
50
+ }
51
+ catch (err) {
52
+ return toolError(err);
53
+ }
54
+ };
55
+ }
56
+ export function createServer() {
57
+ const server = new McpServer({ name: '@aiwerk/mcp-server-resend', version: VERSION });
58
+ // ---- Emails ----
59
+ server.registerTool('resend_email_send', {
60
+ description: 'Send a transactional email via Resend. α-gated: first call returns a confirm_token; second call with that token actually sends. Provide from, to, subject, and html/text. For testing without a verified domain, use onboarding@resend.dev as sender and delivered@resend.dev as recipient.',
61
+ inputSchema: emailSendInputSchema,
62
+ annotations: { title: 'Send Email', readOnlyHint: false, openWorldHint: true },
63
+ }, wrap(emailSend));
64
+ server.registerTool('resend_email_send_batch', {
65
+ description: 'Send up to 100 emails in a single batch call. α-gated: first call returns a confirm_token; second call actually sends. Each email in the array has the same fields as resend_email_send.',
66
+ inputSchema: emailSendBatchInputSchema,
67
+ annotations: { title: 'Send Batch Emails', readOnlyHint: false, openWorldHint: true },
68
+ }, wrap(emailSendBatch));
69
+ server.registerTool('resend_email_get', {
70
+ description: 'Get the status and details of a sent email by ID (delivery status, timestamps, recipients).',
71
+ inputSchema: emailGetInputSchema,
72
+ annotations: { title: 'Get Email', readOnlyHint: true, openWorldHint: true },
73
+ }, wrap(emailGet));
74
+ server.registerTool('resend_email_list', {
75
+ description: 'List sent emails with optional pagination.',
76
+ inputSchema: emailListInputSchema,
77
+ annotations: { title: 'List Emails', readOnlyHint: true, openWorldHint: true },
78
+ }, wrap(emailList));
79
+ server.registerTool('resend_email_update', {
80
+ description: 'Reschedule a scheduled email. Only works before the email is sent.',
81
+ inputSchema: emailUpdateInputSchema,
82
+ annotations: { title: 'Update Email', readOnlyHint: false, openWorldHint: true },
83
+ }, wrap(emailUpdate));
84
+ server.registerTool('resend_email_cancel', {
85
+ description: 'Cancel a scheduled email before it is sent. This is a safety action and is NOT α-gated.',
86
+ inputSchema: emailCancelInputSchema,
87
+ annotations: { title: 'Cancel Email', readOnlyHint: false, openWorldHint: true },
88
+ }, wrap(emailCancel));
89
+ server.registerTool('resend_email_list_received', {
90
+ description: 'List inbound emails received via Resend. Requires inbound email routing to be configured. May be plan-gated on free tier.',
91
+ inputSchema: emailListReceivedInputSchema,
92
+ annotations: { title: 'List Received Emails', readOnlyHint: true, openWorldHint: true },
93
+ }, wrap(emailListReceived));
94
+ server.registerTool('resend_email_get_received', {
95
+ description: 'Get a specific inbound/received email by ID.',
96
+ inputSchema: emailGetReceivedInputSchema,
97
+ annotations: { title: 'Get Received Email', readOnlyHint: true, openWorldHint: true },
98
+ }, wrap(emailGetReceived));
99
+ // ---- Domains ----
100
+ server.registerTool('resend_domain_create', {
101
+ description: 'Add a new sending domain to Resend. After creation, add the returned DNS records to your DNS provider, then call resend_domain_verify.',
102
+ inputSchema: domainCreateInputSchema,
103
+ annotations: { title: 'Create Domain', readOnlyHint: false, openWorldHint: true },
104
+ }, wrap(domainCreate));
105
+ server.registerTool('resend_domain_list', {
106
+ description: 'List all sending domains configured on the account.',
107
+ inputSchema: domainListInputSchema,
108
+ annotations: { title: 'List Domains', readOnlyHint: true, openWorldHint: true },
109
+ }, wrap(domainList));
110
+ server.registerTool('resend_domain_get', {
111
+ description: 'Get details and DNS record status for a specific domain by ID.',
112
+ inputSchema: domainGetInputSchema,
113
+ annotations: { title: 'Get Domain', readOnlyHint: true, openWorldHint: true },
114
+ }, wrap(domainGet));
115
+ server.registerTool('resend_domain_update', {
116
+ description: 'Update domain tracking settings (open tracking, click tracking, TLS mode).',
117
+ inputSchema: domainUpdateInputSchema,
118
+ annotations: { title: 'Update Domain', readOnlyHint: false, openWorldHint: true },
119
+ }, wrap(domainUpdate));
120
+ server.registerTool('resend_domain_verify', {
121
+ description: 'Trigger DNS verification for a domain. Call after adding the DNS records returned by resend_domain_create.',
122
+ inputSchema: domainVerifyInputSchema,
123
+ annotations: { title: 'Verify Domain', readOnlyHint: false, openWorldHint: true },
124
+ }, wrap(domainVerify));
125
+ server.registerTool('resend_domain_delete', {
126
+ description: 'Delete one or more sending domains. α-gated: first call returns a confirm_token; second call executes. Supports bulk deletion via domain_ids[].',
127
+ inputSchema: domainDeleteInputSchema,
128
+ annotations: { title: 'Delete Domain', readOnlyHint: false, openWorldHint: true },
129
+ }, wrap(domainDelete));
130
+ // ---- Contacts ----
131
+ server.registerTool('resend_contact_create', {
132
+ description: 'Create a new contact. Can add to segments and set topic subscriptions at creation time.',
133
+ inputSchema: contactCreateInputSchema,
134
+ annotations: { title: 'Create Contact', readOnlyHint: false, openWorldHint: true },
135
+ }, wrap(contactCreate));
136
+ server.registerTool('resend_contact_list', {
137
+ description: 'List contacts with optional segment filter and cursor pagination.',
138
+ inputSchema: contactListInputSchema,
139
+ annotations: { title: 'List Contacts', readOnlyHint: true, openWorldHint: true },
140
+ }, wrap(contactList));
141
+ server.registerTool('resend_contact_get', {
142
+ description: 'Get a contact by ID or email address.',
143
+ inputSchema: contactGetInputSchema,
144
+ annotations: { title: 'Get Contact', readOnlyHint: true, openWorldHint: true },
145
+ }, wrap(contactGet));
146
+ server.registerTool('resend_contact_update', {
147
+ description: 'Update a contact\'s details (name, email, custom properties, unsubscribe status).',
148
+ inputSchema: contactUpdateInputSchema,
149
+ annotations: { title: 'Update Contact', readOnlyHint: false, openWorldHint: true },
150
+ }, wrap(contactUpdate));
151
+ server.registerTool('resend_contact_delete', {
152
+ description: 'Delete one or more contacts. α-gated. Supports bulk via ids[].',
153
+ inputSchema: contactDeleteInputSchema,
154
+ annotations: { title: 'Delete Contact', readOnlyHint: false, openWorldHint: true },
155
+ }, wrap(contactDelete));
156
+ server.registerTool('resend_contact_list_segments', {
157
+ description: 'List segments a contact belongs to.',
158
+ inputSchema: contactListSegmentsInputSchema,
159
+ annotations: { title: 'List Contact Segments', readOnlyHint: true, openWorldHint: true },
160
+ }, wrap(contactListSegments));
161
+ server.registerTool('resend_contact_add_segment', {
162
+ description: 'Add a contact to a segment.',
163
+ inputSchema: contactAddSegmentInputSchema,
164
+ annotations: { title: 'Add Contact to Segment', readOnlyHint: false, openWorldHint: true },
165
+ }, wrap(contactAddSegment));
166
+ server.registerTool('resend_contact_remove_segment', {
167
+ description: 'Remove a contact from a segment. NOT α-gated (recoverable).',
168
+ inputSchema: contactRemoveSegmentInputSchema,
169
+ annotations: { title: 'Remove Contact from Segment', readOnlyHint: false, openWorldHint: true },
170
+ }, wrap(contactRemoveSegment));
171
+ server.registerTool('resend_contact_get_topics', {
172
+ description: 'Get topic subscription statuses for a contact.',
173
+ inputSchema: contactGetTopicsInputSchema,
174
+ annotations: { title: 'Get Contact Topics', readOnlyHint: true, openWorldHint: true },
175
+ }, wrap(contactGetTopics));
176
+ server.registerTool('resend_contact_update_topics', {
177
+ description: 'Update a contact\'s topic subscriptions (opt_in or opt_out per topic).',
178
+ inputSchema: contactUpdateTopicsInputSchema,
179
+ annotations: { title: 'Update Contact Topics', readOnlyHint: false, openWorldHint: true },
180
+ }, wrap(contactUpdateTopics));
181
+ // ---- Contact Properties ----
182
+ server.registerTool('resend_contactproperty_create', {
183
+ description: 'Create a custom contact property (key, type, optional fallback value).',
184
+ inputSchema: contactPropertyCreateInputSchema,
185
+ annotations: { title: 'Create Contact Property', readOnlyHint: false, openWorldHint: true },
186
+ }, wrap(contactPropertyCreate));
187
+ server.registerTool('resend_contactproperty_list', {
188
+ description: 'List all contact properties defined on the account.',
189
+ inputSchema: contactPropertyListInputSchema,
190
+ annotations: { title: 'List Contact Properties', readOnlyHint: true, openWorldHint: true },
191
+ }, wrap(contactPropertyList));
192
+ server.registerTool('resend_contactproperty_get', {
193
+ description: 'Get a contact property by ID.',
194
+ inputSchema: contactPropertyGetInputSchema,
195
+ annotations: { title: 'Get Contact Property', readOnlyHint: true, openWorldHint: true },
196
+ }, wrap(contactPropertyGet));
197
+ server.registerTool('resend_contactproperty_update', {
198
+ description: 'Update a contact property\'s fallback value.',
199
+ inputSchema: contactPropertyUpdateInputSchema,
200
+ annotations: { title: 'Update Contact Property', readOnlyHint: false, openWorldHint: true },
201
+ }, wrap(contactPropertyUpdate));
202
+ server.registerTool('resend_contactproperty_delete', {
203
+ description: 'Delete one or more contact properties. α-gated. Supports bulk via ids[].',
204
+ inputSchema: contactPropertyDeleteInputSchema,
205
+ annotations: { title: 'Delete Contact Property', readOnlyHint: false, openWorldHint: true },
206
+ }, wrap(contactPropertyDelete));
207
+ // ---- Segments ----
208
+ server.registerTool('resend_segment_create', {
209
+ description: 'Create a new audience segment with optional filter conditions.',
210
+ inputSchema: segmentCreateInputSchema,
211
+ annotations: { title: 'Create Segment', readOnlyHint: false, openWorldHint: true },
212
+ }, wrap(segmentCreate));
213
+ server.registerTool('resend_segment_list', {
214
+ description: 'List all segments.',
215
+ inputSchema: segmentListInputSchema,
216
+ annotations: { title: 'List Segments', readOnlyHint: true, openWorldHint: true },
217
+ }, wrap(segmentList));
218
+ server.registerTool('resend_segment_get', {
219
+ description: 'Get a segment by ID.',
220
+ inputSchema: segmentGetInputSchema,
221
+ annotations: { title: 'Get Segment', readOnlyHint: true, openWorldHint: true },
222
+ }, wrap(segmentGet));
223
+ server.registerTool('resend_segment_list_contacts', {
224
+ description: 'List contacts in a segment (uses GET /contacts?segment_id).',
225
+ inputSchema: segmentListContactsInputSchema,
226
+ annotations: { title: 'List Segment Contacts', readOnlyHint: true, openWorldHint: true },
227
+ }, wrap(segmentListContacts));
228
+ server.registerTool('resend_segment_delete', {
229
+ description: 'Delete one or more segments. α-gated. Supports bulk via ids[].',
230
+ inputSchema: segmentDeleteInputSchema,
231
+ annotations: { title: 'Delete Segment', readOnlyHint: false, openWorldHint: true },
232
+ }, wrap(segmentDelete));
233
+ // ---- Templates ----
234
+ server.registerTool('resend_template_create', {
235
+ description: 'Create a new email template with HTML content and optional default fields.',
236
+ inputSchema: templateCreateInputSchema,
237
+ annotations: { title: 'Create Template', readOnlyHint: false, openWorldHint: true },
238
+ }, wrap(templateCreate));
239
+ server.registerTool('resend_template_list', {
240
+ description: 'List all email templates.',
241
+ inputSchema: templateListInputSchema,
242
+ annotations: { title: 'List Templates', readOnlyHint: true, openWorldHint: true },
243
+ }, wrap(templateList));
244
+ server.registerTool('resend_template_get', {
245
+ description: 'Get a template by ID.',
246
+ inputSchema: templateGetInputSchema,
247
+ annotations: { title: 'Get Template', readOnlyHint: true, openWorldHint: true },
248
+ }, wrap(templateGet));
249
+ server.registerTool('resend_template_update', {
250
+ description: 'Update a template\'s content or settings.',
251
+ inputSchema: templateUpdateInputSchema,
252
+ annotations: { title: 'Update Template', readOnlyHint: false, openWorldHint: true },
253
+ }, wrap(templateUpdate));
254
+ server.registerTool('resend_template_delete', {
255
+ description: 'Delete one or more templates. α-gated. Supports bulk via ids[].',
256
+ inputSchema: templateDeleteInputSchema,
257
+ annotations: { title: 'Delete Template', readOnlyHint: false, openWorldHint: true },
258
+ }, wrap(templateDelete));
259
+ server.registerTool('resend_template_duplicate', {
260
+ description: 'Create a copy of an existing template.',
261
+ inputSchema: templateDuplicateInputSchema,
262
+ annotations: { title: 'Duplicate Template', readOnlyHint: false, openWorldHint: true },
263
+ }, wrap(templateDuplicate));
264
+ server.registerTool('resend_template_publish', {
265
+ description: 'Publish a template draft so it can be used in resend_email_send via its alias.',
266
+ inputSchema: templatePublishInputSchema,
267
+ annotations: { title: 'Publish Template', readOnlyHint: false, openWorldHint: true },
268
+ }, wrap(templatePublish));
269
+ // ---- Topics ----
270
+ server.registerTool('resend_topic_create', {
271
+ description: 'Create a new topic for managing contact subscriptions (e.g. "Weekly Newsletter").',
272
+ inputSchema: topicCreateInputSchema,
273
+ annotations: { title: 'Create Topic', readOnlyHint: false, openWorldHint: true },
274
+ }, wrap(topicCreate));
275
+ server.registerTool('resend_topic_list', {
276
+ description: 'List all topics.',
277
+ inputSchema: topicListInputSchema,
278
+ annotations: { title: 'List Topics', readOnlyHint: true, openWorldHint: true },
279
+ }, wrap(topicList));
280
+ server.registerTool('resend_topic_get', {
281
+ description: 'Get a topic by ID.',
282
+ inputSchema: topicGetInputSchema,
283
+ annotations: { title: 'Get Topic', readOnlyHint: true, openWorldHint: true },
284
+ }, wrap(topicGet));
285
+ server.registerTool('resend_topic_update', {
286
+ description: 'Update a topic\'s name, description, or visibility.',
287
+ inputSchema: topicUpdateInputSchema,
288
+ annotations: { title: 'Update Topic', readOnlyHint: false, openWorldHint: true },
289
+ }, wrap(topicUpdate));
290
+ server.registerTool('resend_topic_delete', {
291
+ description: 'Delete one or more topics. α-gated. Supports bulk via ids[].',
292
+ inputSchema: topicDeleteInputSchema,
293
+ annotations: { title: 'Delete Topic', readOnlyHint: false, openWorldHint: true },
294
+ }, wrap(topicDelete));
295
+ // ---- Broadcasts ----
296
+ server.registerTool('resend_broadcast_create', {
297
+ description: 'Create a broadcast email to a segment. Always creates a DRAFT (send:false enforced). Use resend_broadcast_send to send.',
298
+ inputSchema: broadcastCreateInputSchema,
299
+ annotations: { title: 'Create Broadcast', readOnlyHint: false, openWorldHint: true },
300
+ }, wrap(broadcastCreate));
301
+ server.registerTool('resend_broadcast_list', {
302
+ description: 'List all broadcasts.',
303
+ inputSchema: broadcastListInputSchema,
304
+ annotations: { title: 'List Broadcasts', readOnlyHint: true, openWorldHint: true },
305
+ }, wrap(broadcastList));
306
+ server.registerTool('resend_broadcast_get', {
307
+ description: 'Get a broadcast by ID (includes status, stats if sent).',
308
+ inputSchema: broadcastGetInputSchema,
309
+ annotations: { title: 'Get Broadcast', readOnlyHint: true, openWorldHint: true },
310
+ }, wrap(broadcastGet));
311
+ server.registerTool('resend_broadcast_update', {
312
+ description: 'Update a draft broadcast\'s content or targeting.',
313
+ inputSchema: broadcastUpdateInputSchema,
314
+ annotations: { title: 'Update Broadcast', readOnlyHint: false, openWorldHint: true },
315
+ }, wrap(broadcastUpdate));
316
+ server.registerTool('resend_broadcast_delete', {
317
+ description: 'Delete one or more draft broadcasts. α-gated. Supports bulk via ids[].',
318
+ inputSchema: broadcastDeleteInputSchema,
319
+ annotations: { title: 'Delete Broadcast', readOnlyHint: false, openWorldHint: true },
320
+ }, wrap(broadcastDelete));
321
+ server.registerTool('resend_broadcast_send', {
322
+ description: 'Send or schedule a broadcast. α-gated — high blast radius (sends to entire segment). First call returns confirm_token; second call executes.',
323
+ inputSchema: broadcastSendInputSchema,
324
+ annotations: { title: 'Send Broadcast', readOnlyHint: false, openWorldHint: true },
325
+ }, wrap(broadcastSend));
326
+ // ---- Webhooks ----
327
+ server.registerTool('resend_webhook_create', {
328
+ description: 'Create a webhook endpoint to receive real-time email event notifications.',
329
+ inputSchema: webhookCreateInputSchema,
330
+ annotations: { title: 'Create Webhook', readOnlyHint: false, openWorldHint: true },
331
+ }, wrap(webhookCreate));
332
+ server.registerTool('resend_webhook_list', {
333
+ description: 'List all webhook endpoints.',
334
+ inputSchema: webhookListInputSchema,
335
+ annotations: { title: 'List Webhooks', readOnlyHint: true, openWorldHint: true },
336
+ }, wrap(webhookList));
337
+ server.registerTool('resend_webhook_get', {
338
+ description: 'Get a webhook by ID.',
339
+ inputSchema: webhookGetInputSchema,
340
+ annotations: { title: 'Get Webhook', readOnlyHint: true, openWorldHint: true },
341
+ }, wrap(webhookGet));
342
+ server.registerTool('resend_webhook_update', {
343
+ description: 'Update a webhook\'s endpoint, subscribed events, or enable/disable it.',
344
+ inputSchema: webhookUpdateInputSchema,
345
+ annotations: { title: 'Update Webhook', readOnlyHint: false, openWorldHint: true },
346
+ }, wrap(webhookUpdate));
347
+ server.registerTool('resend_webhook_delete', {
348
+ description: 'Delete one or more webhook endpoints. α-gated. Supports bulk via webhook_ids[].',
349
+ inputSchema: webhookDeleteInputSchema,
350
+ annotations: { title: 'Delete Webhook', readOnlyHint: false, openWorldHint: true },
351
+ }, wrap(webhookDelete));
352
+ // ---- Logs ----
353
+ server.registerTool('resend_log_list', {
354
+ description: 'List API request logs. Availability may be plan-gated on free tier.',
355
+ inputSchema: logListInputSchema,
356
+ annotations: { title: 'List Logs', readOnlyHint: true, openWorldHint: true },
357
+ }, wrap(logList));
358
+ server.registerTool('resend_log_get', {
359
+ description: 'Get a specific API log entry by ID.',
360
+ inputSchema: logGetInputSchema,
361
+ annotations: { title: 'Get Log', readOnlyHint: true, openWorldHint: true },
362
+ }, wrap(logGet));
363
+ // ---- API Keys ----
364
+ server.registerTool('resend_apikey_create', {
365
+ description: 'Create a new API key. The returned token (re_...) is shown ONLY ONCE — store it immediately. NOT α-gated (creating a key is not destructive), but the response includes a clear warning.',
366
+ inputSchema: apikeyCreateInputSchema,
367
+ annotations: { title: 'Create API Key', readOnlyHint: false, openWorldHint: true },
368
+ }, wrap(apikeyCreate));
369
+ server.registerTool('resend_apikey_list', {
370
+ description: 'List all API keys (IDs and names only — tokens are never returned after creation).',
371
+ inputSchema: apikeyListInputSchema,
372
+ annotations: { title: 'List API Keys', readOnlyHint: true, openWorldHint: true },
373
+ }, wrap(apikeyList));
374
+ server.registerTool('resend_apikey_delete', {
375
+ description: 'Revoke one or more API keys. α-gated. Irreversible — integrations using the key break immediately.',
376
+ inputSchema: apikeyDeleteInputSchema,
377
+ annotations: { title: 'Delete API Key', readOnlyHint: false, openWorldHint: true },
378
+ }, wrap(apikeyDelete));
379
+ return server;
380
+ }
381
+ async function main() {
382
+ const server = createServer();
383
+ await server.connect(new StdioServerTransport());
384
+ }
385
+ export function isCliEntry(moduleUrl = import.meta.url, argv1 = process.argv[1]) {
386
+ if (!argv1)
387
+ return false;
388
+ try {
389
+ return realpathSync(fileURLToPath(moduleUrl)) === realpathSync(argv1);
390
+ }
391
+ catch {
392
+ return false;
393
+ }
394
+ }
395
+ if (isCliEntry()) {
396
+ main().catch((err) => {
397
+ console.error('[mcp-server-resend] fatal:', err);
398
+ process.exit(1);
399
+ });
400
+ }
@@ -0,0 +1,37 @@
1
+ import { z } from 'zod';
2
+ import { type ConfirmationRequired } from '../lib/destructive-gate.js';
3
+ export declare const apikeyCreateInputSchema: {
4
+ name: z.ZodString;
5
+ permission: z.ZodOptional<z.ZodEnum<["full_access", "sending_access"]>>;
6
+ domain_id: z.ZodOptional<z.ZodString>;
7
+ };
8
+ export declare function apikeyCreate(args: {
9
+ name: string;
10
+ permission?: 'full_access' | 'sending_access';
11
+ domain_id?: string;
12
+ }): Promise<{
13
+ id: string;
14
+ token: string;
15
+ _warning: string;
16
+ }>;
17
+ export declare const apikeyListInputSchema: {
18
+ limit: z.ZodOptional<z.ZodNumber>;
19
+ after: z.ZodOptional<z.ZodString>;
20
+ before: z.ZodOptional<z.ZodString>;
21
+ };
22
+ export declare function apikeyList(args: {
23
+ limit?: number;
24
+ after?: string;
25
+ before?: string;
26
+ }): Promise<unknown>;
27
+ export declare const apikeyDeleteInputSchema: {
28
+ api_key_id: z.ZodOptional<z.ZodString>;
29
+ api_key_ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
30
+ confirm_token: z.ZodOptional<z.ZodString>;
31
+ };
32
+ export declare function apikeyDelete(args: {
33
+ api_key_id?: string;
34
+ api_key_ids?: string[];
35
+ confirm_token?: string;
36
+ }): Promise<unknown | ConfirmationRequired>;
37
+ //# sourceMappingURL=apikeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apikeys.d.ts","sourceRoot":"","sources":["../../../src/tools/apikeys.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAA2C,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAMhH,eAAO,MAAM,uBAAuB;;;;CAMnC,CAAC;AAOF,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,IAAI,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,aAAa,GAAG,gBAAgB,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CACjF;;;;GASA;AAID,eAAO,MAAM,qBAAqB;;;;CAIjC,CAAC;AAEF,wBAAsB,UAAU,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,oBAEzF;AAID,eAAO,MAAM,uBAAuB;;;;CAInC,CAAC;AAEF,wBAAsB,YAAY,CAAC,IAAI,EAAE;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACrE,GAAG,OAAO,CAAC,OAAO,GAAG,oBAAoB,CAAC,CAoC1C"}
@@ -0,0 +1,72 @@
1
+ import { z } from 'zod';
2
+ import { resendPost, resendGet, resendDelete } from '../lib/resend-client.js';
3
+ import { requireConfirmation, validateAndConsume } from '../lib/destructive-gate.js';
4
+ // --- resend_apikey_create ---
5
+ // The response contains a secret token (re_...) shown ONLY ONCE.
6
+ // We include a prominent warning in the return value.
7
+ export const apikeyCreateInputSchema = {
8
+ name: z.string().describe('Human-readable name for the API key.'),
9
+ permission: z.enum(['full_access', 'sending_access']).optional()
10
+ .describe('Key permission level. full_access: all operations. sending_access: send emails only (default: full_access).'),
11
+ domain_id: z.string().optional()
12
+ .describe('Restrict the key to a specific sending domain. Only used with sending_access permission.'),
13
+ };
14
+ export async function apikeyCreate(args) {
15
+ const result = await resendPost('/api-keys', args);
16
+ // Return with a prominent warning — the token is shown only once.
17
+ // Never log the token (it starts with re_).
18
+ return {
19
+ id: result.id,
20
+ token: result.token,
21
+ _warning: 'STORE THIS TOKEN NOW — it will NOT be shown again. It starts with re_ and grants API access.',
22
+ };
23
+ }
24
+ // --- resend_apikey_list ---
25
+ export const apikeyListInputSchema = {
26
+ limit: z.number().int().min(1).max(100).optional().describe('Max items to return (max 100).'),
27
+ after: z.string().optional().describe('Cursor — return items after this value.'),
28
+ before: z.string().optional().describe('Cursor — return items before this value.'),
29
+ };
30
+ export async function apikeyList(args) {
31
+ return resendGet('/api-keys', args);
32
+ }
33
+ // --- resend_apikey_delete (α-gated, bulk) ---
34
+ export const apikeyDeleteInputSchema = {
35
+ api_key_id: z.string().optional().describe('API key ID to revoke. Provide api_key_id or api_key_ids.'),
36
+ api_key_ids: z.array(z.string()).optional().describe('Multiple API key IDs to revoke.'),
37
+ confirm_token: z.string().optional().describe('Confirmation token from a previous call.'),
38
+ };
39
+ export async function apikeyDelete(args) {
40
+ const { confirm_token, ...rest } = args;
41
+ const tool = 'resend_apikey_delete';
42
+ if (!rest.api_key_id && (!rest.api_key_ids || rest.api_key_ids.length === 0)) {
43
+ throw new Error('Provide api_key_id (single) or api_key_ids (bulk).');
44
+ }
45
+ const isBulk = !!rest.api_key_ids;
46
+ const ids = isBulk ? rest.api_key_ids : undefined;
47
+ const id = !isBulk ? rest.api_key_id : undefined;
48
+ if (!confirm_token) {
49
+ const count = isBulk ? ids.length : 1;
50
+ const preview = isBulk ? ids.slice(0, 3).join(', ') : rest.api_key_id;
51
+ return requireConfirmation({
52
+ tool, id, ids, payload: rest,
53
+ summary: `Revoke ${count} API key(s): ${preview}. This permanently revokes API access — any integrations using this key will break immediately.`,
54
+ });
55
+ }
56
+ const result = validateAndConsume({ confirm_token, tool, id, ids, payload: rest });
57
+ if (!result.valid)
58
+ throw new Error(result.reason);
59
+ if (isBulk) {
60
+ const results = await Promise.allSettled(ids.map(kid => resendDelete(`/api-keys/${kid}`)));
61
+ const succeeded = [];
62
+ const failed = [];
63
+ results.forEach((r, idx) => {
64
+ if (r.status === 'fulfilled')
65
+ succeeded.push(ids[idx]);
66
+ else
67
+ failed.push({ id: ids[idx], error: String(r.reason) });
68
+ });
69
+ return { succeeded, failed, summary: `${succeeded.length}/${ids.length} succeeded` };
70
+ }
71
+ return resendDelete(`/api-keys/${rest.api_key_id}`);
72
+ }
@@ -0,0 +1,85 @@
1
+ import { z } from 'zod';
2
+ import { type ConfirmationRequired } from '../lib/destructive-gate.js';
3
+ export declare const broadcastCreateInputSchema: {
4
+ from: z.ZodString;
5
+ subject: z.ZodString;
6
+ segment_id: z.ZodString;
7
+ name: z.ZodOptional<z.ZodString>;
8
+ reply_to: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
9
+ preview_text: z.ZodOptional<z.ZodString>;
10
+ html: z.ZodOptional<z.ZodString>;
11
+ text: z.ZodOptional<z.ZodString>;
12
+ topic_id: z.ZodOptional<z.ZodString>;
13
+ };
14
+ export declare function broadcastCreate(args: {
15
+ from: string;
16
+ subject: string;
17
+ segment_id: string;
18
+ name?: string;
19
+ reply_to?: string[];
20
+ preview_text?: string;
21
+ html?: string;
22
+ text?: string;
23
+ topic_id?: string;
24
+ }): Promise<unknown>;
25
+ export declare const broadcastListInputSchema: {
26
+ limit: z.ZodOptional<z.ZodNumber>;
27
+ after: z.ZodOptional<z.ZodString>;
28
+ before: z.ZodOptional<z.ZodString>;
29
+ };
30
+ export declare function broadcastList(args: {
31
+ limit?: number;
32
+ after?: string;
33
+ before?: string;
34
+ }): Promise<unknown>;
35
+ export declare const broadcastGetInputSchema: {
36
+ id: z.ZodString;
37
+ };
38
+ export declare function broadcastGet(args: {
39
+ id: string;
40
+ }): Promise<unknown>;
41
+ export declare const broadcastUpdateInputSchema: {
42
+ id: z.ZodString;
43
+ name: z.ZodOptional<z.ZodString>;
44
+ segment_id: z.ZodOptional<z.ZodString>;
45
+ from: z.ZodOptional<z.ZodString>;
46
+ subject: z.ZodOptional<z.ZodString>;
47
+ reply_to: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
48
+ preview_text: z.ZodOptional<z.ZodString>;
49
+ html: z.ZodOptional<z.ZodString>;
50
+ text: z.ZodOptional<z.ZodString>;
51
+ topic_id: z.ZodOptional<z.ZodString>;
52
+ };
53
+ export declare function broadcastUpdate(args: {
54
+ id: string;
55
+ name?: string;
56
+ segment_id?: string;
57
+ from?: string;
58
+ subject?: string;
59
+ reply_to?: string[];
60
+ preview_text?: string;
61
+ html?: string;
62
+ text?: string;
63
+ topic_id?: string;
64
+ }): Promise<unknown>;
65
+ export declare const broadcastDeleteInputSchema: {
66
+ id: z.ZodOptional<z.ZodString>;
67
+ ids: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
68
+ confirm_token: z.ZodOptional<z.ZodString>;
69
+ };
70
+ export declare function broadcastDelete(args: {
71
+ id?: string;
72
+ ids?: string[];
73
+ confirm_token?: string;
74
+ }): Promise<unknown | ConfirmationRequired>;
75
+ export declare const broadcastSendInputSchema: {
76
+ id: z.ZodString;
77
+ scheduled_at: z.ZodOptional<z.ZodString>;
78
+ confirm_token: z.ZodOptional<z.ZodString>;
79
+ };
80
+ export declare function broadcastSend(args: {
81
+ id: string;
82
+ scheduled_at?: string;
83
+ confirm_token?: string;
84
+ }): Promise<unknown | ConfirmationRequired>;
85
+ //# sourceMappingURL=broadcasts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"broadcasts.d.ts","sourceRoot":"","sources":["../../../src/tools/broadcasts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAA2C,KAAK,oBAAoB,EAAE,MAAM,4BAA4B,CAAC;AAYhH,eAAO,MAAM,0BAA0B;;;;;;;;;;CAUtC,CAAC;AAEF,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACjE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACzE,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,oBAGA;AAID,eAAO,MAAM,wBAAwB;;;;CAAmB,CAAC;AAEzD,wBAAsB,aAAa,CAAC,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,oBAE5F;AAID,eAAO,MAAM,uBAAuB;;CAEnC,CAAC;AAEF,wBAAsB,YAAY,CAAC,IAAI,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,oBAEtD;AAID,eAAO,MAAM,0BAA0B;;;;;;;;;;;CAWtC,CAAC;AAEF,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,EAAE,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAChF,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC7F,oBAGA;AAID,eAAO,MAAM,0BAA0B;;;;CAItC,CAAC;AAEF,wBAAsB,eAAe,CAAC,IAAI,EAAE;IAC1C,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACrD,GAAG,OAAO,CAAC,OAAO,GAAG,oBAAoB,CAAC,CAoC1C;AAID,eAAO,MAAM,wBAAwB;;;;CAIpC,CAAC;AAEF,wBAAsB,aAAa,CAAC,IAAI,EAAE;IACxC,EAAE,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CAC3D,GAAG,OAAO,CAAC,OAAO,GAAG,oBAAoB,CAAC,CAkB1C"}