supersendtx-mcp 0.5.9 → 0.6.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/dist/index.js +299 -67
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -82,63 +82,295 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
82
82
  import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
83
83
  import { SuperSendTX } from "supersendtx";
84
84
 
85
+ // src/arc-talk-tools.ts
86
+ var ARC_MCP_TALK_GROWTH_TOOL_NAMES = [
87
+ "get_business_brief",
88
+ "research_marketing_site",
89
+ "get_setup_status",
90
+ "get_growth_health",
91
+ "list_product_events",
92
+ "inspect_attribute_values",
93
+ "list_campaigns",
94
+ "list_goals",
95
+ "list_lists",
96
+ "list_segments",
97
+ "get_tracking_snippet",
98
+ "verify_tracking_install",
99
+ "list_automations",
100
+ "get_setup_run",
101
+ "propose_lifecycle_events",
102
+ "list_plan",
103
+ "get_job_schedule",
104
+ "list_opportunities",
105
+ "list_ideas",
106
+ "get_comms_prefs",
107
+ "list_audience",
108
+ "preview_segment",
109
+ "estimate_reach",
110
+ "list_templates",
111
+ "get_campaign_performance",
112
+ "list_campaign_replies",
113
+ "get_learnings",
114
+ "read_repo_activity",
115
+ "get_codebase_digest",
116
+ "read_product_context",
117
+ "search_codebase",
118
+ "read_repo_file",
119
+ "query_product_analytics"
120
+ ];
121
+
85
122
  // src/arc-mcp.ts
86
- var ARC_MCP_TOOLS = [
123
+ var DEFAULT_APP_ORIGIN = "https://app.supersendtx.com";
124
+ var DEFAULT_RANLA_APP_ORIGIN = "https://app.ranla.ai";
125
+ var MCP_THREAD_TITLE = "MCP";
126
+ function openObjectSchema(description) {
127
+ return {
128
+ type: "object",
129
+ properties: {},
130
+ additionalProperties: true,
131
+ ...description ? { description } : {}
132
+ };
133
+ }
134
+ var TALK_READ_TOOLS = ARC_MCP_TALK_GROWTH_TOOL_NAMES.map((growthTool) => ({
135
+ name: `arc_${growthTool}`,
136
+ description: `Read-only Arc tool: ${growthTool.replace(/_/g, " ")}.`,
137
+ inputSchema: openObjectSchema("Tool input fields (see Ranla Arc tool docs)."),
138
+ kind: "growth",
139
+ growthTool
140
+ }));
141
+ var SETUP_TOOLS = [
142
+ {
143
+ name: "arc_request_connection",
144
+ description: "Surface a Connect card for a missing integration slot.",
145
+ inputSchema: {
146
+ type: "object",
147
+ properties: {
148
+ slot: { type: "string" },
149
+ reason: { type: "string" }
150
+ },
151
+ required: ["slot"]
152
+ },
153
+ kind: "growth",
154
+ growthTool: "request_connection"
155
+ }
156
+ ];
157
+ var META_TOOLS = [
87
158
  {
88
- name: "arc_get_business_brief",
89
- description: "Read the business brief and lifecycle map.",
90
- inputSchema: { type: "object", properties: {} },
91
- growthTool: "get_business_brief"
159
+ name: "arc_list_threads",
160
+ description: "List Arc chat threads for this account.",
161
+ inputSchema: openObjectSchema(),
162
+ kind: "meta"
92
163
  },
93
164
  {
94
- name: "arc_get_setup_status",
95
- description: "Connection inventory and compounding blockers.",
96
- inputSchema: { type: "object", properties: {} },
97
- growthTool: "get_setup_status"
165
+ name: "arc_ensure_thread",
166
+ description: "Find or create a durable Arc thread for MCP (default title \u201CMCP\u201D). Returns { threadId, title }.",
167
+ inputSchema: {
168
+ type: "object",
169
+ properties: {
170
+ title: { type: "string", description: `Thread title (default ${MCP_THREAD_TITLE})` },
171
+ threadId: { type: "string", description: "Reuse this thread id if it still exists" }
172
+ }
173
+ },
174
+ kind: "meta"
98
175
  },
99
176
  {
100
- name: "arc_get_growth_health",
101
- description: "Signup \u2192 activation \u2192 pay health over a window.",
177
+ name: "arc_message",
178
+ description: "Send a message to Arc and wait for the turn. Use for strategy and any mutating work. Returns text plus pendingApproval when HITL is required.",
102
179
  inputSchema: {
103
180
  type: "object",
104
- properties: { days: { type: "number" } }
181
+ properties: {
182
+ text: { type: "string", description: "Message for Arc" },
183
+ threadId: { type: "string", description: "Arc thread id (from arc_ensure_thread)" }
184
+ },
185
+ required: ["text"]
105
186
  },
106
- growthTool: "get_growth_health"
187
+ kind: "meta"
107
188
  },
108
189
  {
109
- name: "arc_request_connection",
110
- description: "Surface a Connect card for a missing integration slot.",
190
+ name: "arc_list_approvals",
191
+ description: "List pending Arc approval cards across threads.",
192
+ inputSchema: openObjectSchema(),
193
+ kind: "meta"
194
+ },
195
+ {
196
+ name: "arc_approve",
197
+ description: "Approve a pending Arc HITL card and resume the run.",
111
198
  inputSchema: {
112
199
  type: "object",
113
200
  properties: {
114
- slot: { type: "string" },
115
- reason: { type: "string" }
201
+ messageId: { type: "string" }
116
202
  },
117
- required: ["slot"]
203
+ required: ["messageId"]
118
204
  },
119
- growthTool: "request_connection"
205
+ kind: "meta"
206
+ },
207
+ {
208
+ name: "arc_reject",
209
+ description: "Reject a pending Arc HITL card and resume the run.",
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ messageId: { type: "string" }
214
+ },
215
+ required: ["messageId"]
216
+ },
217
+ kind: "meta"
120
218
  }
121
219
  ];
122
- async function callArcMcpTool(apiKey, name, args, baseUrl) {
123
- const def = ARC_MCP_TOOLS.find((tool) => tool.name === name);
124
- if (!def) return null;
125
- const origin = (baseUrl ?? process.env.SUPERSENDTX_API_URL ?? "https://app.supersendtx.com").replace(
126
- /\/$/,
127
- ""
128
- );
129
- const response = await fetch(`${origin}/api/growth/tools/${encodeURIComponent(def.growthTool)}`, {
130
- method: "POST",
220
+ var ARC_MCP_TOOLS = [...META_TOOLS, ...SETUP_TOOLS, ...TALK_READ_TOOLS];
221
+ function resolveArcAppOrigin(options = {}) {
222
+ const fromEnv = process.env.RANLA_APP_URL?.trim() || process.env.SUPERSENDTX_APP_URL?.trim() || "";
223
+ if (fromEnv) return fromEnv.replace(/\/$/, "");
224
+ const key = options.apiKey ?? process.env.SUPERSENDTX_API_KEY ?? process.env.RANLA_API_KEY ?? "";
225
+ if (key.startsWith("rnl_")) return DEFAULT_RANLA_APP_ORIGIN;
226
+ const apiUrl = (options.baseUrl ?? process.env.SUPERSENDTX_API_URL ?? "").replace(/\/$/, "");
227
+ if (apiUrl.includes("api.ranla.ai")) return DEFAULT_RANLA_APP_ORIGIN;
228
+ if (apiUrl.includes("app.ranla.ai")) return apiUrl;
229
+ if (apiUrl.includes("app.supersendtx.com")) return apiUrl;
230
+ return DEFAULT_APP_ORIGIN;
231
+ }
232
+ async function arcFetch(origin, apiKey, path, init = {}) {
233
+ const response = await fetch(`${origin}${path}`, {
234
+ ...init,
131
235
  headers: {
132
236
  Authorization: `Bearer ${apiKey}`,
133
- "Content-Type": "application/json"
134
- },
135
- body: JSON.stringify({ input: args })
237
+ "Content-Type": "application/json",
238
+ Accept: "application/json",
239
+ ...init.headers ?? {}
240
+ }
136
241
  });
137
242
  const body = await response.json().catch(() => ({}));
138
- if (!response.ok) {
139
- throw new Error(body.error ?? `Arc tool failed (${response.status})`);
243
+ return { ok: response.ok, status: response.status, body };
244
+ }
245
+ function optString(args, key) {
246
+ if (args[key] == null) return void 0;
247
+ const value = String(args[key]).trim();
248
+ return value || void 0;
249
+ }
250
+ async function callArcMcpTool(apiKey, name, args, baseUrl) {
251
+ const def = ARC_MCP_TOOLS.find((tool) => tool.name === name);
252
+ if (!def) return null;
253
+ const origin = resolveArcAppOrigin({ baseUrl, apiKey });
254
+ if (def.kind === "growth" && def.growthTool) {
255
+ const response = await arcFetch(origin, apiKey, `/api/growth/tools/${encodeURIComponent(def.growthTool)}`, {
256
+ method: "POST",
257
+ body: JSON.stringify({ input: args })
258
+ });
259
+ if (!response.ok) {
260
+ throw new Error(
261
+ typeof response.body.error === "string" ? response.body.error : `Arc tool failed (${response.status})`
262
+ );
263
+ }
264
+ return response.body.result ?? response.body;
265
+ }
266
+ switch (name) {
267
+ case "arc_list_threads": {
268
+ const response = await arcFetch(origin, apiKey, "/api/growth/threads");
269
+ if (!response.ok) {
270
+ throw new Error(
271
+ typeof response.body.error === "string" ? response.body.error : `List threads failed (${response.status})`
272
+ );
273
+ }
274
+ return response.body;
275
+ }
276
+ case "arc_ensure_thread": {
277
+ const existingId = optString(args, "threadId");
278
+ if (existingId) {
279
+ const byId = await arcFetch(
280
+ origin,
281
+ apiKey,
282
+ `/api/growth/threads/${encodeURIComponent(existingId)}`
283
+ );
284
+ if (byId.ok) {
285
+ const thread2 = byId.body.thread;
286
+ if (thread2?.id) {
287
+ return { threadId: thread2.id, title: thread2.title ?? null };
288
+ }
289
+ }
290
+ }
291
+ const title = optString(args, "title") ?? MCP_THREAD_TITLE;
292
+ const byTitle = await arcFetch(
293
+ origin,
294
+ apiKey,
295
+ `/api/growth/threads?title=${encodeURIComponent(title)}`
296
+ );
297
+ if (byTitle.ok) {
298
+ const threads = Array.isArray(byTitle.body.threads) ? byTitle.body.threads : [];
299
+ const hit = threads.find(
300
+ (row) => row && typeof row === "object" && row.title === title
301
+ );
302
+ if (hit) return { threadId: hit.id, title: hit.title ?? title };
303
+ }
304
+ const created = await arcFetch(origin, apiKey, "/api/growth/threads", {
305
+ method: "POST",
306
+ body: JSON.stringify({ title })
307
+ });
308
+ if (!created.ok) {
309
+ throw new Error(
310
+ typeof created.body.error === "string" ? created.body.error : `Create thread failed (${created.status})`
311
+ );
312
+ }
313
+ const thread = created.body.thread;
314
+ if (!thread?.id) throw new Error("Create thread returned no id");
315
+ return { threadId: thread.id, title: thread.title ?? title };
316
+ }
317
+ case "arc_message": {
318
+ const text = optString(args, "text");
319
+ if (!text) throw new Error("Missing required argument: text");
320
+ let threadId = optString(args, "threadId");
321
+ if (!threadId) {
322
+ const ensured = await callArcMcpTool(apiKey, "arc_ensure_thread", {}, baseUrl);
323
+ threadId = ensured.threadId;
324
+ }
325
+ const response = await arcFetch(
326
+ origin,
327
+ apiKey,
328
+ `/api/growth/threads/${encodeURIComponent(threadId)}/messages`,
329
+ {
330
+ method: "POST",
331
+ body: JSON.stringify({ text, sync: true })
332
+ }
333
+ );
334
+ if (!response.ok && response.status !== 500) {
335
+ throw new Error(
336
+ typeof response.body.error === "string" ? response.body.error : `Arc message failed (${response.status})`
337
+ );
338
+ }
339
+ return { threadId, ...response.body };
340
+ }
341
+ case "arc_list_approvals": {
342
+ const response = await arcFetch(origin, apiKey, "/api/growth/approvals");
343
+ if (!response.ok) {
344
+ throw new Error(
345
+ typeof response.body.error === "string" ? response.body.error : `List approvals failed (${response.status})`
346
+ );
347
+ }
348
+ return response.body;
349
+ }
350
+ case "arc_approve":
351
+ case "arc_reject": {
352
+ const messageId = optString(args, "messageId");
353
+ if (!messageId) throw new Error("Missing required argument: messageId");
354
+ const decision = name === "arc_approve" ? "approve" : "reject";
355
+ const response = await arcFetch(
356
+ origin,
357
+ apiKey,
358
+ `/api/growth/approvals/${encodeURIComponent(messageId)}`,
359
+ {
360
+ method: "POST",
361
+ body: JSON.stringify({ decision, sync: true })
362
+ }
363
+ );
364
+ if (!response.ok && response.status !== 500) {
365
+ throw new Error(
366
+ typeof response.body.error === "string" ? response.body.error : `Approval ${decision} failed (${response.status})`
367
+ );
368
+ }
369
+ return response.body;
370
+ }
371
+ default:
372
+ throw new Error(`Unknown Arc MCP tool: ${name}`);
140
373
  }
141
- return body.result;
142
374
  }
143
375
 
144
376
  // src/tools.ts
@@ -148,7 +380,7 @@ function textResult(value, isError = false) {
148
380
  ...isError ? { isError: true } : {}
149
381
  };
150
382
  }
151
- function optString(args, key) {
383
+ function optString2(args, key) {
152
384
  if (args[key] == null) return void 0;
153
385
  const value = String(args[key]).trim();
154
386
  return value || void 0;
@@ -195,10 +427,10 @@ async function callMcpTool(client, name, args, options = {}) {
195
427
  }
196
428
  switch (name) {
197
429
  case "send_email": {
198
- const from = optString(args, "from");
199
- const to = optString(args, "to");
200
- const subject = optString(args, "subject");
201
- const templateId = optString(args, "template_id") || optString(args, "template_alias");
430
+ const from = optString2(args, "from");
431
+ const to = optString2(args, "to");
432
+ const subject = optString2(args, "subject");
433
+ const templateId = optString2(args, "template_id") || optString2(args, "template_alias");
202
434
  if (!from) return textResult("Missing required argument: from", true);
203
435
  if (!to) return textResult("Missing required argument: to", true);
204
436
  if (!templateId && !subject) {
@@ -209,9 +441,9 @@ async function callMcpTool(client, name, args, options = {}) {
209
441
  from,
210
442
  to,
211
443
  ...subject ? { subject } : {},
212
- html: optString(args, "html"),
213
- text: optString(args, "text"),
214
- reply_to: optString(args, "reply_to"),
444
+ html: optString2(args, "html"),
445
+ text: optString2(args, "text"),
446
+ reply_to: optString2(args, "reply_to"),
215
447
  ...templateId ? { template: { id: templateId, ...variables ? { variables } : {} } } : {}
216
448
  });
217
449
  return textResult(result);
@@ -219,12 +451,12 @@ async function callMcpTool(client, name, args, options = {}) {
219
451
  case "list_emails": {
220
452
  const result = await client.emails.list({
221
453
  limit: optNumber(args, "limit"),
222
- cursor: optString(args, "cursor")
454
+ cursor: optString2(args, "cursor")
223
455
  });
224
456
  return textResult(result);
225
457
  }
226
458
  case "get_email": {
227
- const id = optString(args, "id");
459
+ const id = optString2(args, "id");
228
460
  if (!id) return textResult("Missing required argument: id", true);
229
461
  return textResult(await client.emails.get(id));
230
462
  }
@@ -248,7 +480,7 @@ async function callMcpTool(client, name, args, options = {}) {
248
480
  return textResult(result);
249
481
  }
250
482
  case "verify_domain": {
251
- const domain = optString(args, "domain");
483
+ const domain = optString2(args, "domain");
252
484
  if (!domain) return textResult("Missing required argument: domain", true);
253
485
  const result = await client.domains.verify(domain);
254
486
  return textResult(result);
@@ -256,12 +488,12 @@ async function callMcpTool(client, name, args, options = {}) {
256
488
  case "list_domains": {
257
489
  const result = await client.domains.list({
258
490
  limit: optNumber(args, "limit"),
259
- cursor: optString(args, "cursor")
491
+ cursor: optString2(args, "cursor")
260
492
  });
261
493
  return textResult(result);
262
494
  }
263
495
  case "create_domain": {
264
- const domainName = optString(args, "name");
496
+ const domainName = optString2(args, "name");
265
497
  if (!domainName) return textResult("Missing required argument: name", true);
266
498
  const inbound = optBoolean(args, "inbound_enabled");
267
499
  return textResult(
@@ -275,12 +507,12 @@ async function callMcpTool(client, name, args, options = {}) {
275
507
  return textResult(
276
508
  await client.webhooks.list({
277
509
  limit: optNumber(args, "limit"),
278
- cursor: optString(args, "cursor")
510
+ cursor: optString2(args, "cursor")
279
511
  })
280
512
  );
281
513
  }
282
514
  case "create_webhook": {
283
- const url = optString(args, "url");
515
+ const url = optString2(args, "url");
284
516
  if (!url) return textResult("Missing required argument: url", true);
285
517
  const events = optStringArray(args, "events");
286
518
  return textResult(
@@ -291,7 +523,7 @@ async function callMcpTool(client, name, args, options = {}) {
291
523
  );
292
524
  }
293
525
  case "delete_webhook": {
294
- const id = optString(args, "id");
526
+ const id = optString2(args, "id");
295
527
  if (!id) return textResult("Missing required argument: id", true);
296
528
  return textResult(await client.webhooks.delete(id));
297
529
  }
@@ -299,67 +531,67 @@ async function callMcpTool(client, name, args, options = {}) {
299
531
  return textResult(
300
532
  await client.suppressions.list({
301
533
  limit: optNumber(args, "limit"),
302
- cursor: optString(args, "cursor"),
303
- email: optString(args, "email")
534
+ cursor: optString2(args, "cursor"),
535
+ email: optString2(args, "email")
304
536
  })
305
537
  );
306
538
  }
307
539
  case "add_suppression": {
308
- const email = optString(args, "email");
540
+ const email = optString2(args, "email");
309
541
  if (!email) return textResult("Missing required argument: email", true);
310
542
  return textResult(
311
543
  await client.suppressions.create({
312
544
  email,
313
- reason: optString(args, "reason")
545
+ reason: optString2(args, "reason")
314
546
  })
315
547
  );
316
548
  }
317
549
  case "remove_suppression": {
318
- const idOrEmail = optString(args, "id") ?? optString(args, "email");
550
+ const idOrEmail = optString2(args, "id") ?? optString2(args, "email");
319
551
  if (!idOrEmail) {
320
552
  return textResult("Missing required argument: id or email", true);
321
553
  }
322
554
  return textResult(await client.suppressions.remove(idOrEmail));
323
555
  }
324
556
  case "list_templates": {
325
- const status = optString(args, "status");
557
+ const status = optString2(args, "status");
326
558
  return textResult(
327
559
  await client.templates.list({
328
560
  limit: optNumber(args, "limit"),
329
- cursor: optString(args, "cursor"),
561
+ cursor: optString2(args, "cursor"),
330
562
  ...status === "draft" || status === "published" ? { status } : {}
331
563
  })
332
564
  );
333
565
  }
334
566
  case "get_template": {
335
- const idOrAlias = optString(args, "id") ?? optString(args, "alias");
567
+ const idOrAlias = optString2(args, "id") ?? optString2(args, "alias");
336
568
  if (!idOrAlias) return textResult("Missing required argument: id or alias", true);
337
569
  return textResult(await client.templates.get(idOrAlias));
338
570
  }
339
571
  case "create_template": {
340
- const name2 = optString(args, "name");
341
- const subjectArg = optString(args, "subject");
572
+ const name2 = optString2(args, "name");
573
+ const subjectArg = optString2(args, "subject");
342
574
  if (!name2) return textResult("Missing required argument: name", true);
343
575
  if (!subjectArg) return textResult("Missing required argument: subject", true);
344
- const format = optString(args, "format");
576
+ const format = optString2(args, "format");
345
577
  return textResult(
346
578
  await client.templates.create({
347
579
  name: name2,
348
580
  subject: subjectArg,
349
581
  ...format === "blocks" || format === "html" ? { format } : {},
350
- html: optString(args, "html"),
351
- text: optString(args, "text"),
352
- alias: optString(args, "alias")
582
+ html: optString2(args, "html"),
583
+ text: optString2(args, "text"),
584
+ alias: optString2(args, "alias")
353
585
  })
354
586
  );
355
587
  }
356
588
  case "publish_template": {
357
- const idOrAlias = optString(args, "id") ?? optString(args, "alias");
589
+ const idOrAlias = optString2(args, "id") ?? optString2(args, "alias");
358
590
  if (!idOrAlias) return textResult("Missing required argument: id or alias", true);
359
591
  return textResult(await client.templates.publish(idOrAlias));
360
592
  }
361
593
  case "send_test_webhook_event": {
362
- const event = optString(args, "event");
594
+ const event = optString2(args, "event");
363
595
  if (!event || !EMAIL_WEBHOOK_EVENTS.has(event)) {
364
596
  return textResult(
365
597
  `Missing or invalid event. Use one of: ${[...EMAIL_WEBHOOK_EVENTS].join(", ")}`,
@@ -369,7 +601,7 @@ async function callMcpTool(client, name, args, options = {}) {
369
601
  return textResult(
370
602
  await client.emails.testWebhook({
371
603
  event,
372
- email_id: optString(args, "email_id"),
604
+ email_id: optString2(args, "email_id"),
373
605
  deliver: optBoolean(args, "deliver")
374
606
  })
375
607
  );
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "supersendtx-mcp",
3
- "version": "0.5.9",
4
- "description": "SuperSend TX MCP server — stdio or local HTTP (--http) for Cursor and Claude",
3
+ "version": "0.6.0",
4
+ "description": "SuperSend TX MCP server — mail tools plus Arc agent-first tools for Cursor and Claude",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "bin": {
@@ -40,7 +40,7 @@
40
40
  },
41
41
  "dependencies": {
42
42
  "@modelcontextprotocol/sdk": "^1.30.0",
43
- "supersendtx": "0.15.2"
43
+ "supersendtx": "0.15.3"
44
44
  },
45
45
  "devDependencies": {
46
46
  "tsup": "^8.5.0",