supersendtx-mcp 0.6.36 → 0.6.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +3 -4
  2. package/dist/index.js +43 -143
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -138,12 +138,11 @@ These call the app host and need a full-access key (OAuth sign-in and dashboard
138
138
  | `arc_get_thread` | Read a thread's messages, artifacts and waiting approvals |
139
139
  | `arc_message` | Send text to Ranla and wait for the turn — returns `text`, `artifacts`, and `pendingApproval` |
140
140
  | `arc_list_approvals` / `arc_approve` / `arc_reject` | Answer approvals |
141
- | `arc_request_connection` | Connect link for a missing integration |
142
- | `arc_<tool>` | Read-only Ranla tools, listed from the account with input schemas. Results say in `forCaller` when a step needs `arc_message` |
141
+ | `arc_ranla` | Run one of Ranla's commands (`campaign list`, `page propose`, `connection request`). The description lists them from the account; `help <noun>` gives every field. What needs sign-off comes back as `pendingApproval` |
143
142
 
144
- Flow: `arc_ensure_thread` → `arc_message` → if a step needs approval, `arc_list_approvals` → `arc_approve` or `arc_reject`.
143
+ Flow: `arc_ensure_thread`, then `arc_message` or `arc_ranla`, and if a step needs approval, `arc_list_approvals`, then `arc_approve` or `arc_reject`.
145
144
 
146
- An SEO page approval also carries `actionId`: pass it to `arc_seo_page_package` to read the draft, for example to build the page in your own repo.
145
+ An SEO page approval also carries `actionId`: run `arc_ranla` with `page get` and that `actionId` to read the draft, for example to build the page in your own repo.
147
146
 
148
147
  Every tool has a `title` and `readOnlyHint` / `destructiveHint` annotations, so clients can run reads without asking and confirm writes.
149
148
 
package/dist/index.js CHANGED
@@ -84,7 +84,7 @@ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprot
84
84
  import { SuperSendTX } from "supersendtx";
85
85
 
86
86
  // package.json
87
- var version = "0.6.36";
87
+ var version = "0.6.39";
88
88
 
89
89
  // src/annotations.ts
90
90
  var READ = { readOnlyHint: true };
@@ -118,6 +118,8 @@ var FIXED_TOOL_HINTS = {
118
118
  arc_get_thread: { ...READ, title: "Read a Ranla thread" },
119
119
  arc_ensure_thread: { ...WRITE, title: "Open a Ranla thread", idempotentHint: true },
120
120
  arc_message: { ...WRITE, title: "Message Ranla" },
121
+ /** A command can change the account; what reaches people or spends stops at an approval. */
122
+ arc_ranla: { ...WRITE, title: "Run a Ranla command", openWorldHint: true },
121
123
  arc_list_approvals: { ...READ, title: "List pending approvals" },
122
124
  /** Approving can send email to real people. */
123
125
  arc_approve: { ...WRITE, title: "Approve a Ranla step", openWorldHint: true },
@@ -142,102 +144,6 @@ function mcpToolAnnotations(tool) {
142
144
  return tool.readOnly === true ? { title, ...READ } : { title, ...WRITE };
143
145
  }
144
146
 
145
- // src/arc-talk-tools.ts
146
- var ARC_MCP_TALK_GROWTH_TOOL_NAMES = [
147
- // Cold-start SEO plan: a minute of vendor data and a planning call. Read-only, so MCP carries it.
148
- "seo_plan",
149
- "get_business_brief",
150
- "get_brand",
151
- "read_url",
152
- // A canvas document Ranla wrote, as it stands now. Writing to one mutates
153
- // and goes through `arc_message` like every other write.
154
- "read_canvas",
155
- // The ids read_canvas takes; nothing else an external agent can call returns them.
156
- "list_documents",
157
- // Every image in the account, with public URLs an external agent can use as they are.
158
- "list_library_images",
159
- "get_setup_status",
160
- "get_growth_health",
161
- "get_revenue_health",
162
- "list_product_events",
163
- "list_product_analytics",
164
- "inspect_analytics_person",
165
- "inspect_attribute_values",
166
- "list_campaigns",
167
- "list_goals",
168
- "list_lists",
169
- "list_segments",
170
- "get_segment",
171
- "get_tracking_snippet",
172
- "verify_tracking_install",
173
- "list_automations",
174
- "get_automation",
175
- "get_setup_run",
176
- "propose_lifecycle_events",
177
- "list_plan",
178
- "get_job_schedule",
179
- "list_opportunities",
180
- "list_ideas",
181
- "get_comms_prefs",
182
- "list_audience",
183
- "preview_segment",
184
- "estimate_reach",
185
- "list_templates",
186
- "get_template",
187
- "list_on_site_messages",
188
- "get_on_site_message",
189
- "get_on_site_theme",
190
- "show_install",
191
- "get_campaign_performance",
192
- "get_campaign_evaluation",
193
- "list_ad_campaigns",
194
- "get_ad_spend",
195
- "get_ad_delivery_diagnostics",
196
- "get_search_term_evidence",
197
- "list_proposed_ad_campaigns",
198
- "read_campaign_design",
199
- "list_campaign_replies",
200
- // The inbox as conversations, with the replies Ranla drafted.
201
- "read_inbox",
202
- "get_learnings",
203
- "read_repo_activity",
204
- "get_codebase_digest",
205
- "read_product_context",
206
- "search_codebase",
207
- "read_repo_file",
208
- "site_traffic_summary",
209
- "site_traffic_breakdown",
210
- "site_traffic_efforts",
211
- // Search reads. Read-only on the customer's own data, same as the analytics
212
- // reads above: the role that owns them recommends and never publishes.
213
- "seo_search_performance",
214
- "seo_opportunities",
215
- "seo_site_authority",
216
- "seo_site_structure",
217
- "seo_page_detail",
218
- "seo_page_package",
219
- // Reddit reads. Our own tables only: the inbox, a thread with its drafts,
220
- // what ranks on Google, post ideas, connected accounts, the activity log.
221
- "reddit_desk_status",
222
- "reddit_opportunities",
223
- "reddit_thread_detail",
224
- "reddit_rankings",
225
- "reddit_ai_citations",
226
- "reddit_post_ideas",
227
- "reddit_accounts",
228
- "reddit_activity",
229
- "reddit_leads",
230
- "seo_competitors",
231
- "seo_lift_queue",
232
- // AI search reads. Our own tables only: how the answer engines see the product.
233
- "aeo_status",
234
- "aeo_prompts",
235
- "aeo_answer",
236
- "aeo_sources",
237
- "aeo_competitors",
238
- "aeo_actions"
239
- ];
240
-
241
147
  // src/arc-mcp.ts
242
148
  var DEFAULT_APP_ORIGIN = "https://app.supersendtx.com";
243
149
  var DEFAULT_RANLA_APP_ORIGIN = "https://app.ranla.ai";
@@ -251,31 +157,27 @@ function openObjectSchema(description) {
251
157
  };
252
158
  }
253
159
  var RANLA_AGENT_NAME = "Ranla";
254
- var TALK_READ_TOOLS = ARC_MCP_TALK_GROWTH_TOOL_NAMES.map((growthTool) => ({
255
- name: `arc_${growthTool}`,
256
- description: `Read-only Ranla tool: ${growthTool.replace(/_/g, " ")}.`,
257
- inputSchema: openObjectSchema("Pass the tool arguments as fields."),
258
- kind: "growth",
259
- growthTool,
260
- readOnly: true
261
- }));
262
- var SETUP_TOOLS = [
263
- {
264
- name: "arc_request_connection",
265
- description: "Surface a Connect card for a missing integration slot.",
160
+ var RANLA_COMMAND_TOOL_NAME = "arc_ranla";
161
+ var RANLA_COMMAND_INTRO = `Run one of ${RANLA_AGENT_NAME}'s commands in this account. command is "<noun> <verb>", such as "campaign list" or "page propose", and input holds its fields. "help <noun>" lists a noun's commands with every field. A command that needs sign-off does nothing yet: it comes back with pendingApproval, and arc_approve with its messageId applies it. For strategy, or anything that needs ${RANLA_AGENT_NAME}'s judgement or research, use arc_message.`;
162
+ function ranlaCommandTool(index) {
163
+ return {
164
+ name: RANLA_COMMAND_TOOL_NAME,
165
+ description: index ? `${RANLA_COMMAND_INTRO}
166
+
167
+ Commands:
168
+ ${index}` : RANLA_COMMAND_INTRO,
266
169
  inputSchema: {
267
170
  type: "object",
268
171
  properties: {
269
- slot: { type: "string" },
270
- reason: { type: "string" }
172
+ command: { type: "string", description: '"<noun> <verb>", or "help <noun>".' },
173
+ input: { type: "object", description: "The command's fields.", additionalProperties: true },
174
+ threadId: { type: "string", description: "Ranla thread to record it in (from arc_ensure_thread). Optional." }
271
175
  },
272
- required: ["slot"]
176
+ required: ["command"]
273
177
  },
274
- kind: "growth",
275
- growthTool: "request_connection",
276
- readOnly: false
277
- }
278
- ];
178
+ kind: "meta"
179
+ };
180
+ }
279
181
  var META_TOOLS = [
280
182
  {
281
183
  name: "arc_list_threads",
@@ -364,7 +266,7 @@ var META_TOOLS = [
364
266
  kind: "meta"
365
267
  }
366
268
  ];
367
- var ARC_MCP_TOOLS = [...META_TOOLS, ...SETUP_TOOLS, ...TALK_READ_TOOLS];
269
+ var ARC_MCP_TOOLS = [...META_TOOLS, ranlaCommandTool()];
368
270
  function resolveArcAppOrigin(options = {}) {
369
271
  const fromEnv = process.env.RANLA_APP_URL?.trim() || process.env.SUPERSENDTX_APP_URL?.trim() || "";
370
272
  if (fromEnv) return fromEnv.replace(/\/$/, "");
@@ -394,40 +296,22 @@ var CATALOG_TIMEOUT_MS = 4e3;
394
296
  function isArcMcpToolName(name) {
395
297
  return name.startsWith(ARC_TOOL_PREFIX);
396
298
  }
397
- function isCatalogEntry(value) {
398
- if (!value || typeof value !== "object") return false;
399
- const entry = value;
400
- const schema = entry.inputSchema;
401
- return typeof entry.name === "string" && /^[a-z0-9_]+$/.test(entry.name) && typeof entry.description === "string" && !!schema && typeof schema === "object" && schema.type === "object";
402
- }
403
- async function fetchGrowthToolCatalog(apiKey, baseUrl) {
299
+ async function fetchCommandIndex(apiKey, baseUrl) {
404
300
  try {
405
301
  const origin = resolveArcAppOrigin({ baseUrl, apiKey });
406
- const response = await arcFetch(origin, apiKey, "/api/growth/tools", {
302
+ const response = await arcFetch(origin, apiKey, "/api/growth/commands", {
407
303
  signal: AbortSignal.timeout(CATALOG_TIMEOUT_MS)
408
304
  });
409
- if (!response.ok || !Array.isArray(response.body.tools)) return null;
410
- const entries = response.body.tools.filter(isCatalogEntry);
411
- return entries.length > 0 ? entries : null;
305
+ const index = response.body.index;
306
+ return response.ok && typeof index === "string" && index.trim() ? index : null;
412
307
  } catch {
413
308
  return null;
414
309
  }
415
310
  }
416
311
  async function loadArcMcpTools(apiKey, baseUrl) {
417
- const catalog = await fetchGrowthToolCatalog(apiKey, baseUrl);
418
- if (!catalog) return ARC_MCP_TOOLS;
419
- return [
420
- ...META_TOOLS,
421
- ...catalog.map((entry) => ({
422
- name: `${ARC_TOOL_PREFIX}${entry.name}`,
423
- description: `${entry.mutates ? RANLA_AGENT_NAME : `${RANLA_AGENT_NAME}, read-only`}: ${entry.description}`,
424
- inputSchema: entry.inputSchema,
425
- kind: "growth",
426
- growthTool: entry.name,
427
- // Missing `mutates` is not a promise of read-only: only an explicit false is.
428
- readOnly: entry.mutates === false
429
- }))
430
- ];
312
+ const index = await fetchCommandIndex(apiKey, baseUrl);
313
+ if (!index) return ARC_MCP_TOOLS;
314
+ return [...META_TOOLS, ranlaCommandTool(index)];
431
315
  }
432
316
  function optString(args, key) {
433
317
  if (args[key] == null) return void 0;
@@ -437,6 +321,22 @@ function optString(args, key) {
437
321
  async function callArcMcpTool(apiKey, name, args, baseUrl) {
438
322
  if (!isArcMcpToolName(name)) return null;
439
323
  const origin = resolveArcAppOrigin({ baseUrl, apiKey });
324
+ if (name === RANLA_COMMAND_TOOL_NAME) {
325
+ const command = optString(args, "command");
326
+ if (!command) throw new Error("Missing required argument: command");
327
+ const input = args.input && typeof args.input === "object" && !Array.isArray(args.input) ? args.input : {};
328
+ const threadId = optString(args, "threadId");
329
+ const response = await arcFetch(origin, apiKey, "/api/growth/commands", {
330
+ method: "POST",
331
+ body: JSON.stringify({ command, input, ...threadId ? { threadId } : {} })
332
+ });
333
+ if (!response.ok) {
334
+ throw new Error(
335
+ typeof response.body.error === "string" ? response.body.error : `Ranla command failed (${response.status})`
336
+ );
337
+ }
338
+ return response.body;
339
+ }
440
340
  if (!META_TOOLS.some((tool) => tool.name === name)) {
441
341
  const growthTool = name.slice(ARC_TOOL_PREFIX.length);
442
342
  const response = await arcFetch(origin, apiKey, `/api/growth/tools/${encodeURIComponent(growthTool)}`, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "supersendtx-mcp",
3
- "version": "0.6.36",
3
+ "version": "0.6.39",
4
4
  "description": "SuperSend TX MCP server — mail tools plus Ranla agent-first tools for Cursor and Claude",
5
5
  "license": "MIT",
6
6
  "type": "module",