sendcraft-mcp 2.0.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 (2) hide show
  1. package/index.js +671 -0
  2. package/package.json +21 -0
package/index.js ADDED
@@ -0,0 +1,671 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * SendCraft MCP Server v2.0.0
4
+ * 22 tools + 4 resources for AI agents (Claude, Cursor, Windsurf, etc.)
5
+ *
6
+ * Claude Desktop config:
7
+ * {
8
+ * "mcpServers": {
9
+ * "sendcraft": {
10
+ * "command": "npx",
11
+ * "args": ["sendcraft-mcp"],
12
+ * "env": { "SENDCRAFT_API_KEY": "sc_live_..." }
13
+ * }
14
+ * }
15
+ * }
16
+ */
17
+
18
+ const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
19
+ const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
20
+ const {
21
+ CallToolRequestSchema,
22
+ ListToolsRequestSchema,
23
+ ListResourcesRequestSchema,
24
+ ReadResourceRequestSchema,
25
+ } = require('@modelcontextprotocol/sdk/types.js');
26
+ const axios = require('axios');
27
+
28
+ const API_KEY = process.env.SENDCRAFT_API_KEY;
29
+ const BASE_URL = (process.env.SENDCRAFT_BASE_URL || 'https://api.sendcraft.online/api').replace(/\/$/, '');
30
+
31
+ if (!API_KEY) {
32
+ process.stderr.write('[sendcraft-mcp] SENDCRAFT_API_KEY is required\n');
33
+ process.exit(1);
34
+ }
35
+
36
+ const http = axios.create({
37
+ baseURL: BASE_URL,
38
+ headers: { 'x-api-key': API_KEY, 'Content-Type': 'application/json' },
39
+ timeout: 30_000,
40
+ });
41
+
42
+ const api = {
43
+ get: (path, params) => http({ method: 'get', url: path, params }).then(r => r.data),
44
+ post: (path, data) => http({ method: 'post', url: path, data }).then(r => r.data),
45
+ put: (path, data) => http({ method: 'put', url: path, data }).then(r => r.data),
46
+ patch: (path, data) => http({ method: 'patch', url: path, data }).then(r => r.data),
47
+ delete: (path) => http({ method: 'delete', url: path }).then(r => r.data),
48
+ };
49
+
50
+ // ─── Tool Definitions ─────────────────────────────────────────────────────────
51
+
52
+ const TOOLS = [
53
+ // ── Emails ──────────────────────────────────────────────────────────────────
54
+ {
55
+ name: 'sendcraft_send_email',
56
+ description: 'Send a single transactional email immediately. Use this for order confirmations, password resets, notifications, and any one-off email triggered by a user action.',
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: {
60
+ to: { type: 'string', description: 'Recipient email address' },
61
+ subject: { type: 'string', description: 'Email subject line' },
62
+ html: { type: 'string', description: 'HTML email body. Include inline styles for best compatibility.' },
63
+ text: { type: 'string', description: 'Plain text fallback body (recommended for deliverability)' },
64
+ from: { type: 'string', description: 'Sender email address (uses account default if omitted)' },
65
+ from_name: { type: 'string', description: 'Sender display name, e.g. "Acme Support"' },
66
+ reply_to: { type: 'string', description: 'Reply-To address if different from sender' },
67
+ cc: { type: 'string', description: 'CC address (single email)' },
68
+ bcc: { type: 'string', description: 'BCC address (single email)' },
69
+ idempotency_key: { type: 'string', description: 'Unique key to prevent duplicate sends on retry (e.g. "order-123-confirm")' },
70
+ },
71
+ required: ['to', 'subject', 'html'],
72
+ },
73
+ },
74
+ {
75
+ name: 'sendcraft_schedule_email',
76
+ description: 'Schedule an email for future delivery. Use this for reminders, follow-ups, drip messages, or any email that should go out at a specific time.',
77
+ inputSchema: {
78
+ type: 'object',
79
+ properties: {
80
+ to: { type: 'string', description: 'Recipient email' },
81
+ subject: { type: 'string', description: 'Subject line' },
82
+ html: { type: 'string', description: 'HTML body' },
83
+ scheduled_at: { type: 'string', description: 'ISO 8601 datetime, e.g. "2026-04-01T09:00:00Z"' },
84
+ from: { type: 'string', description: 'Sender email (optional)' },
85
+ },
86
+ required: ['to', 'subject', 'html', 'scheduled_at'],
87
+ },
88
+ },
89
+ {
90
+ name: 'sendcraft_cancel_scheduled_email',
91
+ description: 'Cancel a scheduled email before it is sent. Use when a user cancels an order, changes their preferences, or the scheduled action is no longer needed.',
92
+ inputSchema: {
93
+ type: 'object',
94
+ properties: {
95
+ email_id: { type: 'string', description: 'The ID of the scheduled email to cancel' },
96
+ },
97
+ required: ['email_id'],
98
+ },
99
+ },
100
+ {
101
+ name: 'sendcraft_batch_send',
102
+ description: 'Send up to 100 distinct emails in a single API call. Each email can have a different recipient, subject, and body. More efficient than calling send_email 100 times.',
103
+ inputSchema: {
104
+ type: 'object',
105
+ properties: {
106
+ emails: {
107
+ type: 'array',
108
+ description: 'Array of email objects. Each must have to, subject, and html.',
109
+ items: {
110
+ type: 'object',
111
+ properties: {
112
+ to: { type: 'string' },
113
+ subject: { type: 'string' },
114
+ html: { type: 'string' },
115
+ text: { type: 'string' },
116
+ from: { type: 'string' },
117
+ },
118
+ required: ['to', 'subject', 'html'],
119
+ },
120
+ },
121
+ },
122
+ required: ['emails'],
123
+ },
124
+ },
125
+ {
126
+ name: 'sendcraft_get_email',
127
+ description: 'Retrieve details and delivery status for a single email by its ID. Use to check whether a specific email was delivered, opened, or bounced.',
128
+ inputSchema: {
129
+ type: 'object',
130
+ properties: {
131
+ email_id: { type: 'string', description: 'Email ID returned from send or list calls' },
132
+ },
133
+ required: ['email_id'],
134
+ },
135
+ },
136
+ {
137
+ name: 'sendcraft_list_emails',
138
+ description: 'List recently sent emails with delivery status. Use to audit what was sent, find a specific email, or monitor deliverability.',
139
+ inputSchema: {
140
+ type: 'object',
141
+ properties: {
142
+ limit: { type: 'number', description: 'Number of emails to return (default 20, max 100)' },
143
+ page: { type: 'number', description: 'Page number for pagination (default 1)' },
144
+ status: { type: 'string', description: 'Filter: sent, delivered, failed, bounced, opened, scheduled, cancelled' },
145
+ campaign_id: { type: 'string', description: 'Filter emails belonging to a specific campaign' },
146
+ },
147
+ },
148
+ },
149
+ {
150
+ name: 'sendcraft_get_stats',
151
+ description: 'Get aggregate email statistics: total sent, open rate, click rate, bounce rate, and complaint rate. Use to understand overall sending health.',
152
+ inputSchema: { type: 'object', properties: {} },
153
+ },
154
+
155
+ // ── Campaigns ───────────────────────────────────────────────────────────────
156
+ {
157
+ name: 'sendcraft_list_campaigns',
158
+ description: 'List email marketing campaigns with their status and performance metrics.',
159
+ inputSchema: {
160
+ type: 'object',
161
+ properties: {
162
+ limit: { type: 'number', description: 'Number to return (default 20)' },
163
+ status: { type: 'string', description: 'Filter: draft, scheduled, sent, sending' },
164
+ },
165
+ },
166
+ },
167
+ {
168
+ name: 'sendcraft_create_campaign',
169
+ description: 'Create a new email marketing campaign. After creating, call sendcraft_send_campaign to send it.',
170
+ inputSchema: {
171
+ type: 'object',
172
+ properties: {
173
+ name: { type: 'string', description: 'Internal campaign name (not shown to recipients)' },
174
+ subject: { type: 'string', description: 'Email subject line recipients will see' },
175
+ html: { type: 'string', description: 'HTML email body' },
176
+ from_email: { type: 'string', description: 'Sender email address' },
177
+ from_name: { type: 'string', description: 'Sender display name' },
178
+ recipients: {
179
+ type: 'array',
180
+ description: 'Array of recipient email addresses or subscriber list IDs',
181
+ items: { type: 'string' },
182
+ },
183
+ },
184
+ required: ['name', 'subject', 'html', 'recipients'],
185
+ },
186
+ },
187
+ {
188
+ name: 'sendcraft_send_campaign',
189
+ description: 'Send an existing draft campaign immediately, or schedule it for a future time.',
190
+ inputSchema: {
191
+ type: 'object',
192
+ properties: {
193
+ campaign_id: { type: 'string', description: 'Campaign ID to send' },
194
+ scheduled_at: { type: 'string', description: 'ISO 8601 datetime to schedule (omit for immediate send)' },
195
+ },
196
+ required: ['campaign_id'],
197
+ },
198
+ },
199
+ {
200
+ name: 'sendcraft_get_campaign_analytics',
201
+ description: 'Get detailed analytics for a specific campaign: opens, clicks, bounces, unsubscribes, and click heatmap data.',
202
+ inputSchema: {
203
+ type: 'object',
204
+ properties: {
205
+ campaign_id: { type: 'string', description: 'Campaign ID' },
206
+ },
207
+ required: ['campaign_id'],
208
+ },
209
+ },
210
+
211
+ // ── Subscribers ─────────────────────────────────────────────────────────────
212
+ {
213
+ name: 'sendcraft_list_subscribers',
214
+ description: 'List subscribers/contacts with their status and tags.',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ limit: { type: 'number', description: 'Number to return (default 20, max 100)' },
219
+ page: { type: 'number', description: 'Page number' },
220
+ status: { type: 'string', description: 'Filter: active, pending, unsubscribed' },
221
+ list_id: { type: 'string', description: 'Filter by email list ID' },
222
+ },
223
+ },
224
+ },
225
+ {
226
+ name: 'sendcraft_add_subscriber',
227
+ description: 'Add a new subscriber/contact to a list. If the list has double opt-in enabled, a confirmation email is sent automatically.',
228
+ inputSchema: {
229
+ type: 'object',
230
+ properties: {
231
+ email: { type: 'string', description: 'Subscriber email address' },
232
+ list_id: { type: 'string', description: 'Email list ID to add them to' },
233
+ first_name: { type: 'string', description: 'First name (optional)' },
234
+ last_name: { type: 'string', description: 'Last name (optional)' },
235
+ tags: { type: 'array', items: { type: 'string' }, description: 'Tags to attach, e.g. ["customer", "trial"]' },
236
+ },
237
+ required: ['email', 'list_id'],
238
+ },
239
+ },
240
+ {
241
+ name: 'sendcraft_unsubscribe',
242
+ description: 'Unsubscribe a contact from all marketing emails. Use when processing manual unsubscribe requests or GDPR deletion requests.',
243
+ inputSchema: {
244
+ type: 'object',
245
+ properties: {
246
+ email: { type: 'string', description: 'Email address to unsubscribe' },
247
+ },
248
+ required: ['email'],
249
+ },
250
+ },
251
+
252
+ // ── Templates ────────────────────────────────────────────────────────────────
253
+ {
254
+ name: 'sendcraft_list_templates',
255
+ description: 'List saved email templates. Templates can be reused across campaigns and transactional emails.',
256
+ inputSchema: {
257
+ type: 'object',
258
+ properties: {
259
+ limit: { type: 'number', description: 'Number to return (default 20)' },
260
+ },
261
+ },
262
+ },
263
+ {
264
+ name: 'sendcraft_create_template',
265
+ description: 'Save an email template for reuse. Templates support {{variable}} placeholders for personalization.',
266
+ inputSchema: {
267
+ type: 'object',
268
+ properties: {
269
+ name: { type: 'string', description: 'Template name (internal)' },
270
+ subject: { type: 'string', description: 'Default subject line (can include {{variables}})' },
271
+ html: { type: 'string', description: 'HTML body (can include {{firstName}}, {{companyName}}, etc.)' },
272
+ text: { type: 'string', description: 'Plain text version (optional)' },
273
+ },
274
+ required: ['name', 'subject', 'html'],
275
+ },
276
+ },
277
+
278
+ // ── Domains ──────────────────────────────────────────────────────────────────
279
+ {
280
+ name: 'sendcraft_list_domains',
281
+ description: 'List sender domains and their DNS verification status (SPF, DKIM, DMARC). Emails from unverified domains may land in spam.',
282
+ inputSchema: { type: 'object', properties: {} },
283
+ },
284
+ {
285
+ name: 'sendcraft_add_domain',
286
+ description: 'Add a new sender domain and get the DNS records (SPF, DKIM, DMARC, BIMI) to configure at your DNS provider.',
287
+ inputSchema: {
288
+ type: 'object',
289
+ properties: {
290
+ domain: { type: 'string', description: 'Domain name to add, e.g. "mystore.com"' },
291
+ },
292
+ required: ['domain'],
293
+ },
294
+ },
295
+ {
296
+ name: 'sendcraft_verify_domain',
297
+ description: 'Check if the DNS records for a domain are correctly configured. Call this after updating DNS records to confirm verification.',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ domain_id: { type: 'string', description: 'Domain ID from list_domains' },
302
+ },
303
+ required: ['domain_id'],
304
+ },
305
+ },
306
+ {
307
+ name: 'sendcraft_analyze_dmarc',
308
+ description: 'Analyze the DMARC record for a verified domain. Returns a score (0-100), policy strength, and specific issues to fix.',
309
+ inputSchema: {
310
+ type: 'object',
311
+ properties: {
312
+ domain_id: { type: 'string', description: 'Domain ID from list_domains' },
313
+ },
314
+ required: ['domain_id'],
315
+ },
316
+ },
317
+
318
+ // ── Segments ─────────────────────────────────────────────────────────────────
319
+ {
320
+ name: 'sendcraft_list_segments',
321
+ description: 'List subscriber segments. Segments are dynamic groups of subscribers matching specific criteria (country, tags, activity, etc.).',
322
+ inputSchema: { type: 'object', properties: {} },
323
+ },
324
+
325
+ // ── SMTP Warmup ──────────────────────────────────────────────────────────────
326
+ {
327
+ name: 'sendcraft_get_warmup_status',
328
+ description: 'Check the IP warmup status for the self-hosted SMTP server. Returns current day, daily send limit, emails sent today, and remaining quota. Important to check before sending large batches.',
329
+ inputSchema: { type: 'object', properties: {} },
330
+ },
331
+
332
+ // ── API Keys ─────────────────────────────────────────────────────────────────
333
+ {
334
+ name: 'sendcraft_list_api_keys',
335
+ description: 'List API keys for the account. Returns masked keys (never the full value), permissions scope, and last-used date.',
336
+ inputSchema: { type: 'object', properties: {} },
337
+ },
338
+ ];
339
+
340
+ // ─── Resources ────────────────────────────────────────────────────────────────
341
+ // Resources let Claude load context into its window before answering questions
342
+
343
+ const RESOURCES = [
344
+ {
345
+ uri: 'sendcraft://stats',
346
+ name: 'Email Statistics',
347
+ description: 'Live email sending stats: open rate, click rate, total sent, bounces',
348
+ mimeType: 'application/json',
349
+ },
350
+ {
351
+ uri: 'sendcraft://domains',
352
+ name: 'Verified Domains',
353
+ description: 'List of sender domains and their SPF/DKIM/DMARC verification status',
354
+ mimeType: 'application/json',
355
+ },
356
+ {
357
+ uri: 'sendcraft://warmup',
358
+ name: 'SMTP Warmup Status',
359
+ description: 'Current IP warmup day, daily limit, and emails sent today',
360
+ mimeType: 'application/json',
361
+ },
362
+ {
363
+ uri: 'sendcraft://segments',
364
+ name: 'Subscriber Segments',
365
+ description: 'All subscriber segments with names and subscriber counts',
366
+ mimeType: 'application/json',
367
+ },
368
+ ];
369
+
370
+ // ─── Response Helpers ─────────────────────────────────────────────────────────
371
+
372
+ function ok(data, summary) {
373
+ const text = summary
374
+ ? `${summary}\n\n${JSON.stringify(data, null, 2)}`
375
+ : JSON.stringify(data, null, 2);
376
+ return { content: [{ type: 'text', text }] };
377
+ }
378
+
379
+ function fail(err) {
380
+ const msg = err.response?.data?.error || err.message || String(err);
381
+ return { content: [{ type: 'text', text: `Error: ${msg}` }], isError: true };
382
+ }
383
+
384
+ function emailSummary(e) {
385
+ return `Status: ${e.status} | To: ${e.toEmail} | Subject: ${e.subject} | Sent: ${e.createdAt || '—'}`;
386
+ }
387
+
388
+ function domainSummary(d) {
389
+ const checks = [
390
+ d.spfVerified ? '✓ SPF' : '✗ SPF',
391
+ d.dkimVerified ? '✓ DKIM' : '✗ DKIM',
392
+ d.dmarcVerified? '✓ DMARC': '✗ DMARC',
393
+ ];
394
+ return `${d.domain} — ${d.status} (${checks.join(', ')})`;
395
+ }
396
+
397
+ // ─── Server ───────────────────────────────────────────────────────────────────
398
+
399
+ const server = new Server(
400
+ { name: 'sendcraft', version: '2.0.0' },
401
+ { capabilities: { tools: {}, resources: {} } }
402
+ );
403
+
404
+ // List tools
405
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
406
+
407
+ // List resources
408
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({ resources: RESOURCES }));
409
+
410
+ // Read resource
411
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
412
+ const { uri } = req.params;
413
+ try {
414
+ let data;
415
+ if (uri === 'sendcraft://stats') data = await api.get('/emails/stats/summary');
416
+ else if (uri === 'sendcraft://domains') data = await api.get('/domains');
417
+ else if (uri === 'sendcraft://warmup') data = await api.get('/smtp/warmup');
418
+ else if (uri === 'sendcraft://segments') data = await api.get('/segments');
419
+ else throw new Error(`Unknown resource: ${uri}`);
420
+
421
+ return {
422
+ contents: [{
423
+ uri,
424
+ mimeType: 'application/json',
425
+ text: JSON.stringify(data, null, 2),
426
+ }],
427
+ };
428
+ } catch (err) {
429
+ throw new Error(err.response?.data?.error || err.message);
430
+ }
431
+ });
432
+
433
+ // Call tool
434
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
435
+ const { name, arguments: a } = request.params;
436
+
437
+ try {
438
+ switch (name) {
439
+
440
+ // ── Emails ──────────────────────────────────────────────────────────────
441
+ case 'sendcraft_send_email': {
442
+ const headers = a.idempotency_key ? { 'X-Idempotency-Key': a.idempotency_key } : {};
443
+ const res = await http({
444
+ method: 'post', url: '/emails/send',
445
+ data: {
446
+ toEmail: a.to, subject: a.subject,
447
+ htmlContent: a.html, plainTextContent: a.text,
448
+ fromEmail: a.from, fromName: a.from_name, replyTo: a.reply_to,
449
+ cc: a.cc, bcc: a.bcc,
450
+ },
451
+ headers,
452
+ }).then(r => r.data);
453
+ return ok(res, `Email queued for delivery to ${a.to}`);
454
+ }
455
+
456
+ case 'sendcraft_schedule_email': {
457
+ const res = await api.post('/emails/schedule', {
458
+ toEmail: a.to, subject: a.subject,
459
+ htmlContent: a.html, fromEmail: a.from,
460
+ scheduledTime: a.scheduled_at,
461
+ });
462
+ return ok(res, `Email scheduled for ${a.scheduled_at} → ${a.to}`);
463
+ }
464
+
465
+ case 'sendcraft_cancel_scheduled_email': {
466
+ const res = await api.delete(`/emails/${a.email_id}/schedule`);
467
+ return ok(res, `Scheduled email ${a.email_id} cancelled.`);
468
+ }
469
+
470
+ case 'sendcraft_batch_send': {
471
+ const mapped = a.emails.map(e => ({
472
+ toEmail: e.to, subject: e.subject,
473
+ htmlContent: e.html, plainTextContent: e.text, fromEmail: e.from,
474
+ }));
475
+ const res = await api.post('/emails/batch', { emails: mapped });
476
+ const total = a.emails.length;
477
+ const ok_count = res.results?.filter(r => r.success)?.length ?? total;
478
+ return ok(res, `Batch send: ${ok_count}/${total} emails queued.`);
479
+ }
480
+
481
+ case 'sendcraft_get_email': {
482
+ const res = await api.get(`/emails/${a.email_id}`);
483
+ const e = res.email || res;
484
+ return ok(res, emailSummary(e));
485
+ }
486
+
487
+ case 'sendcraft_list_emails': {
488
+ const res = await api.get('/emails', {
489
+ limit: a.limit || 20, page: a.page || 1,
490
+ status: a.status, campaignId: a.campaign_id,
491
+ });
492
+ const emails = res.emails || [];
493
+ const summary = `Found ${emails.length} of ${res.total || '?'} emails.`;
494
+ return ok(res, summary);
495
+ }
496
+
497
+ case 'sendcraft_get_stats': {
498
+ const res = await api.get('/emails/stats/summary');
499
+ const s = res.stats || res;
500
+ const summary = [
501
+ `Total sent: ${s.totalSent ?? '—'}`,
502
+ `Open rate: ${s.openRate != null ? (s.openRate * 100).toFixed(1) + '%' : '—'}`,
503
+ `Click rate: ${s.clickRate != null ? (s.clickRate * 100).toFixed(1) + '%' : '—'}`,
504
+ `Bounce rate: ${s.bounceRate != null ? (s.bounceRate * 100).toFixed(1) + '%' : '—'}`,
505
+ ].join(' | ');
506
+ return ok(res, summary);
507
+ }
508
+
509
+ // ── Campaigns ────────────────────────────────────────────────────────────
510
+ case 'sendcraft_list_campaigns': {
511
+ const res = await api.get('/campaigns', { limit: a.limit || 20, status: a.status });
512
+ const list = res.campaigns || [];
513
+ const summary = `Found ${list.length} campaigns.`;
514
+ return ok(res, summary);
515
+ }
516
+
517
+ case 'sendcraft_create_campaign': {
518
+ const res = await api.post('/campaigns', {
519
+ name: a.name, subject: a.subject,
520
+ htmlContent: a.html, fromEmail: a.from_email, fromName: a.from_name,
521
+ recipients: a.recipients,
522
+ });
523
+ const id = res.campaign?._id || '—';
524
+ return ok(res, `Campaign created. ID: ${id}. Call sendcraft_send_campaign to send it.`);
525
+ }
526
+
527
+ case 'sendcraft_send_campaign': {
528
+ const body = a.scheduled_at ? { scheduledAt: a.scheduled_at } : {};
529
+ const res = await api.post(`/campaigns/${a.campaign_id}/send`, body);
530
+ const msg = a.scheduled_at
531
+ ? `Campaign scheduled for ${a.scheduled_at}.`
532
+ : 'Campaign is sending now.';
533
+ return ok(res, msg);
534
+ }
535
+
536
+ case 'sendcraft_get_campaign_analytics': {
537
+ const [analytics, heatmap] = await Promise.allSettled([
538
+ api.get(`/analytics/campaign/${a.campaign_id}`),
539
+ api.get(`/analytics/campaign/${a.campaign_id}/heatmap`),
540
+ ]);
541
+ const data = {
542
+ analytics: analytics.status === 'fulfilled' ? analytics.value : null,
543
+ heatmap: heatmap.status === 'fulfilled' ? heatmap.value : null,
544
+ };
545
+ return ok(data, `Analytics for campaign ${a.campaign_id}`);
546
+ }
547
+
548
+ // ── Subscribers ──────────────────────────────────────────────────────────
549
+ case 'sendcraft_list_subscribers': {
550
+ const res = await api.get('/subscribers', {
551
+ limit: a.limit || 20, page: a.page || 1,
552
+ status: a.status, listId: a.list_id,
553
+ });
554
+ const subs = res.subscribers || [];
555
+ return ok(res, `Found ${subs.length} of ${res.total || '?'} subscribers.`);
556
+ }
557
+
558
+ case 'sendcraft_add_subscriber': {
559
+ const res = await api.post('/subscribers/add', {
560
+ email: a.email, listId: a.list_id,
561
+ firstName: a.first_name, lastName: a.last_name, tags: a.tags,
562
+ });
563
+ return ok(res, `Subscriber ${a.email} added.`);
564
+ }
565
+
566
+ case 'sendcraft_unsubscribe': {
567
+ const res = await api.post('/compliance/unsubscribe', { email: a.email });
568
+ return ok(res, `${a.email} unsubscribed from all marketing emails.`);
569
+ }
570
+
571
+ // ── Templates ────────────────────────────────────────────────────────────
572
+ case 'sendcraft_list_templates': {
573
+ const res = await api.get('/templates', { limit: a.limit || 20 });
574
+ const list = res.templates || [];
575
+ return ok(res, `Found ${list.length} templates.`);
576
+ }
577
+
578
+ case 'sendcraft_create_template': {
579
+ const res = await api.post('/templates', {
580
+ name: a.name, subject: a.subject,
581
+ htmlContent: a.html, plainTextContent: a.text,
582
+ });
583
+ const id = res.template?._id || '—';
584
+ return ok(res, `Template "${a.name}" created. ID: ${id}`);
585
+ }
586
+
587
+ // ── Domains ──────────────────────────────────────────────────────────────
588
+ case 'sendcraft_list_domains': {
589
+ const res = await api.get('/domains');
590
+ const domains = res.domains || [];
591
+ const lines = domains.map(domainSummary).join('\n');
592
+ return ok(res, `${domains.length} domain(s):\n${lines}`);
593
+ }
594
+
595
+ case 'sendcraft_add_domain': {
596
+ const res = await api.post('/domains', { domain: a.domain });
597
+ const records = (res.dnsRecords || [])
598
+ .map(r => `${r.purpose}: ${r.name} → ${r.value}`)
599
+ .join('\n');
600
+ return ok(res, `Domain ${a.domain} added. Add these DNS records:\n${records}`);
601
+ }
602
+
603
+ case 'sendcraft_verify_domain': {
604
+ const res = await api.post(`/domains/${a.domain_id}/verify`);
605
+ const r = res.results || {};
606
+ const status = [
607
+ `SPF: ${r.spf ? '✓' : '✗'}`,
608
+ `DKIM: ${r.dkim ? '✓' : '✗'}`,
609
+ `DMARC: ${r.dmarc ? '✓' : '✗'}`,
610
+ ].join(' | ');
611
+ const msg = res.verified
612
+ ? `Domain fully verified and ready to send. ${status}`
613
+ : `Verification pending. ${status}. DNS changes can take up to 48h.`;
614
+ return ok(res, msg);
615
+ }
616
+
617
+ case 'sendcraft_analyze_dmarc': {
618
+ const res = await api.get(`/domains/${a.domain_id}/dmarc-report`);
619
+ const summary = res.isValid
620
+ ? `DMARC score: ${res.score}/100 | Policy: ${res.policy} | Issues: ${res.issues?.length ? res.issues.join('; ') : 'none'}`
621
+ : `No valid DMARC record found. ${(res.issues || []).join('; ')}`;
622
+ return ok(res, summary);
623
+ }
624
+
625
+ // ── Segments ─────────────────────────────────────────────────────────────
626
+ case 'sendcraft_list_segments': {
627
+ const res = await api.get('/segments');
628
+ const segs = res.segments || [];
629
+ const lines = segs.map(s => `${s.name} (${s.subscriberCount ?? '?'} subscribers)`).join('\n');
630
+ return ok(res, `${segs.length} segment(s):\n${lines}`);
631
+ }
632
+
633
+ // ── SMTP Warmup ──────────────────────────────────────────────────────────
634
+ case 'sendcraft_get_warmup_status': {
635
+ const res = await api.get('/smtp/warmup');
636
+ const msg = res.isWarmedUp
637
+ ? 'IP is fully warmed up — no daily sending limits.'
638
+ : `Warmup day ${res.warmupDay}: sent ${res.todayCount}/${res.dailyLimit} today. Remaining: ${res.remainingToday}.`;
639
+ return ok(res, msg);
640
+ }
641
+
642
+ // ── API Keys ─────────────────────────────────────────────────────────────
643
+ case 'sendcraft_list_api_keys': {
644
+ const res = await api.get('/user/keys');
645
+ const keys = res.keys || [];
646
+ const lines = keys.map(k =>
647
+ `${k.name} (${k.permissions}) — last used: ${k.lastUsedAt ? new Date(k.lastUsedAt).toLocaleDateString() : 'never'}`
648
+ ).join('\n');
649
+ return ok(res, `${keys.length} key(s):\n${lines}`);
650
+ }
651
+
652
+ default:
653
+ throw new Error(`Unknown tool: ${name}`);
654
+ }
655
+ } catch (err) {
656
+ return fail(err);
657
+ }
658
+ });
659
+
660
+ // ─── Start ────────────────────────────────────────────────────────────────────
661
+
662
+ async function main() {
663
+ const transport = new StdioServerTransport();
664
+ await server.connect(transport);
665
+ process.stderr.write('[sendcraft-mcp] v2.0.0 running — 22 tools, 4 resources\n');
666
+ }
667
+
668
+ main().catch(err => {
669
+ process.stderr.write(`[sendcraft-mcp] Fatal: ${err.message}\n`);
670
+ process.exit(1);
671
+ });
package/package.json ADDED
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "sendcraft-mcp",
3
+ "version": "2.0.0",
4
+ "description": "Model Context Protocol (MCP) server for SendCraft — lets AI agents send emails natively",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "sendcraft-mcp": "./index.js"
8
+ },
9
+ "scripts": {
10
+ "start": "node index.js"
11
+ },
12
+ "dependencies": {
13
+ "@modelcontextprotocol/sdk": "^1.0.0",
14
+ "axios": "^1.6.0"
15
+ },
16
+ "engines": {
17
+ "node": ">=18"
18
+ },
19
+ "keywords": ["mcp", "sendcraft", "email", "ai-agent", "claude", "cursor"],
20
+ "license": "MIT"
21
+ }