supersendtx-mcp 0.5.10 → 0.6.1

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 +329 -77
  2. package/package.json +2 -2
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 = [
158
+ {
159
+ name: "arc_list_threads",
160
+ description: "List Arc chat threads for this account.",
161
+ inputSchema: openObjectSchema(),
162
+ kind: "meta"
163
+ },
164
+ {
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"
175
+ },
87
176
  {
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"
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.",
179
+ inputSchema: {
180
+ type: "object",
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"]
186
+ },
187
+ kind: "meta"
92
188
  },
93
189
  {
94
- name: "arc_get_setup_status",
95
- description: "Connection inventory and compounding blockers.",
96
- inputSchema: { type: "object", properties: {} },
97
- growthTool: "get_setup_status"
190
+ name: "arc_list_approvals",
191
+ description: "List pending Arc approval cards across threads.",
192
+ inputSchema: openObjectSchema(),
193
+ kind: "meta"
98
194
  },
99
195
  {
100
- name: "arc_get_growth_health",
101
- description: "Signup \u2192 activation \u2192 pay health over a window.",
196
+ name: "arc_approve",
197
+ description: "Approve a pending Arc HITL card and resume the run.",
102
198
  inputSchema: {
103
199
  type: "object",
104
- properties: { days: { type: "number" } }
200
+ properties: {
201
+ messageId: { type: "string" }
202
+ },
203
+ required: ["messageId"]
105
204
  },
106
- growthTool: "get_growth_health"
205
+ kind: "meta"
107
206
  },
108
207
  {
109
- name: "arc_request_connection",
110
- description: "Surface a Connect card for a missing integration slot.",
208
+ name: "arc_reject",
209
+ description: "Reject a pending Arc HITL card and resume the run.",
111
210
  inputSchema: {
112
211
  type: "object",
113
212
  properties: {
114
- slot: { type: "string" },
115
- reason: { type: "string" }
213
+ messageId: { type: "string" }
116
214
  },
117
- required: ["slot"]
215
+ required: ["messageId"]
118
216
  },
119
- growthTool: "request_connection"
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
  );
@@ -675,12 +907,21 @@ async function runStdioServer() {
675
907
 
676
908
  // src/oauth-metadata.ts
677
909
  var MCP_HTTP_PATH = "/mcp";
910
+ function appOriginForMcpOrigin(mcpOrigin) {
911
+ try {
912
+ const host = new URL(mcpOrigin).hostname.toLowerCase();
913
+ if (host === "mcp.ranla.ai") return "https://app.ranla.ai";
914
+ if (host === "mcp.supersendtx.com") return "https://app.supersendtx.com";
915
+ } catch {
916
+ }
917
+ return (process.env.SUPERSENDTX_OAUTH_ISSUER?.trim() || process.env.SUPERSENDTX_APP_URL?.trim() || "https://app.supersendtx.com").replace(/\/$/, "");
918
+ }
678
919
  function buildProtectedResourceMetadata(origin) {
679
920
  const resource = `${origin.replace(/\/$/, "")}${MCP_HTTP_PATH}`;
680
- const appOrigin = process.env.SUPERSENDTX_OAUTH_ISSUER?.trim() || process.env.SUPERSENDTX_APP_URL?.trim() || "https://app.supersendtx.com";
921
+ const appOrigin = appOriginForMcpOrigin(origin);
681
922
  return {
682
923
  resource,
683
- authorization_servers: [appOrigin.replace(/\/$/, "")],
924
+ authorization_servers: [appOrigin],
684
925
  scopes_supported: ["full"],
685
926
  bearer_methods_supported: ["header"]
686
927
  };
@@ -691,14 +932,24 @@ function buildWwwAuthenticateChallenge(origin) {
691
932
  }
692
933
 
693
934
  // src/oauth-resolve.ts
694
- function getOAuthResolveUrl() {
695
- const appBase = (process.env.SUPERSENDTX_APP_URL?.trim() || process.env.SUPERSENDTX_OAUTH_ISSUER?.trim() || "https://app.supersendtx.com").replace(/\/$/, "");
696
- return `${appBase}/api/mcp/oauth/resolve`;
935
+ function appOriginForMcpOrigin2(mcpOrigin) {
936
+ if (mcpOrigin) {
937
+ try {
938
+ const host = new URL(mcpOrigin).hostname.toLowerCase();
939
+ if (host === "mcp.ranla.ai") return "https://app.ranla.ai";
940
+ if (host === "mcp.supersendtx.com") return "https://app.supersendtx.com";
941
+ } catch {
942
+ }
943
+ }
944
+ return (process.env.SUPERSENDTX_APP_URL?.trim() || process.env.SUPERSENDTX_OAUTH_ISSUER?.trim() || "https://app.supersendtx.com").replace(/\/$/, "");
945
+ }
946
+ function getOAuthResolveUrl(mcpOrigin) {
947
+ return `${appOriginForMcpOrigin2(mcpOrigin)}/api/mcp/oauth/resolve`;
697
948
  }
698
- async function resolveOAuthAccessToken(accessToken) {
949
+ async function resolveOAuthAccessToken(accessToken, mcpOrigin) {
699
950
  const resolveSecret = process.env.SUPERSENDTX_MCP_RESOLVE_SECRET?.trim() || process.env.SUPERSENDTX_CREDENTIALS_ENCRYPTION_KEY?.trim() || (process.env.NODE_ENV === "production" ? "" : "supersendtx-dev-mcp-resolve");
700
951
  if (!resolveSecret) return null;
701
- const response = await fetch(getOAuthResolveUrl(), {
952
+ const response = await fetch(getOAuthResolveUrl(mcpOrigin), {
702
953
  method: "POST",
703
954
  headers: {
704
955
  authorization: `Bearer ${accessToken}`,
@@ -733,11 +984,11 @@ function getRequestOrigin(req) {
733
984
  const proto = firstHeaderValue(req.headers["x-forwarded-proto"]) || "http";
734
985
  return `${proto}://${host}`;
735
986
  }
736
- async function resolveApiKeyFromAuthorization(authorization, baseUrl) {
987
+ async function resolveApiKeyFromAuthorization(authorization, baseUrl, mcpOrigin) {
737
988
  const bearer = parseBearerToken(authorization);
738
989
  if (!bearer) return null;
739
990
  if (isSuperSendTxApiKey(bearer)) return bearer;
740
- return resolveOAuthAccessToken(bearer);
991
+ return resolveOAuthAccessToken(bearer, mcpOrigin);
741
992
  }
742
993
  async function handleHttpRequest(req, res, options = {}) {
743
994
  const origin = getRequestOrigin(req);
@@ -767,7 +1018,8 @@ async function handleHttpRequest(req, res, options = {}) {
767
1018
  }
768
1019
  const apiKey = await resolveApiKeyFromAuthorization(
769
1020
  typeof req.headers.authorization === "string" ? req.headers.authorization : void 0,
770
- options.baseUrl
1021
+ options.baseUrl,
1022
+ origin
771
1023
  );
772
1024
  if (!apiKey) {
773
1025
  sendJson(
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "supersendtx-mcp",
3
- "version": "0.5.10",
4
- "description": "SuperSend TX MCP server — stdio or local HTTP (--http) for Cursor and Claude",
3
+ "version": "0.6.1",
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": {