@sellable/mcp 0.1.342 → 0.1.343

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.
package/dist/server.js CHANGED
@@ -43,6 +43,7 @@ import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, saveRubrics
43
43
  import { getSenderRoutingTool, setSenderRoutingTool, } from "./tools/sender-routing.js";
44
44
  import { getSender, listSenders } from "./tools/senders.js";
45
45
  import { attachRecommendedSequence, attachSequence, createWorkflowTable, } from "./tools/sequencer.js";
46
+ import { setupEvergreenCampaigns } from "./tools/setup-evergreen-campaigns.js";
46
47
  import { exportTableCsv, listTables } from "./tools/tables.js";
47
48
  import { handleVerifyTableRow } from "./tools/verify-row.js";
48
49
  import { sanitizeWatchUrlsForMcpResult } from "./tools/watch-url-security.js";
@@ -68,6 +69,32 @@ function parseOptionalNumber(value) {
68
69
  }
69
70
  return undefined;
70
71
  }
72
+ function markEvergreenSetupCampaignsDirty(args) {
73
+ if (!args || args.mode !== "verify")
74
+ return;
75
+ const campaignIds = new Set();
76
+ const bindings = Array.isArray(args.bindings) ? args.bindings : [];
77
+ for (const binding of bindings) {
78
+ if (!binding || typeof binding !== "object" || Array.isArray(binding))
79
+ continue;
80
+ const campaignId = binding.campaignId;
81
+ if (typeof campaignId === "string" && campaignId.trim()) {
82
+ campaignIds.add(campaignId.trim());
83
+ }
84
+ }
85
+ const receipts = Array.isArray(args.receipts) ? args.receipts : [];
86
+ for (const receipt of receipts) {
87
+ if (!receipt || typeof receipt !== "object" || Array.isArray(receipt))
88
+ continue;
89
+ const campaignId = receipt.campaignId;
90
+ if (typeof campaignId === "string" && campaignId.trim()) {
91
+ campaignIds.add(campaignId.trim());
92
+ }
93
+ }
94
+ for (const campaignId of campaignIds) {
95
+ markCampaignContextDirty(campaignId, "setup_evergreen_campaigns");
96
+ }
97
+ }
71
98
  function formatSubskillPromptText(result) {
72
99
  const header = result.chunkCount && result.chunkCount > 1
73
100
  ? [
@@ -187,6 +214,10 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
187
214
  case "get_campaign_refill_state":
188
215
  result = await getCampaignRefillState(args);
189
216
  break;
217
+ case "setup_evergreen_campaigns":
218
+ result = await setupEvergreenCampaigns(args);
219
+ markEvergreenSetupCampaignsDirty(args);
220
+ break;
190
221
  case "fill_campaign_horizon":
191
222
  result = await fillCampaignHorizon(args);
192
223
  if (args?.campaignId) {
@@ -38,6 +38,7 @@ import { rubricToolDefinitions } from "./rubrics.js";
38
38
  import { senderRoutingToolDefinitions } from "./sender-routing.js";
39
39
  import { senderToolDefinitions } from "./senders.js";
40
40
  import { sequencerToolDefinitions } from "./sequencer.js";
41
+ import { setupEvergreenCampaignsToolDefinitions } from "./setup-evergreen-campaigns.js";
41
42
  import { tableToolDefinitions } from "./tables.js";
42
43
  import { verifyRowToolDefinitions } from "./verify-row.js";
43
44
  import { waterfallToolDefinitions } from "./waterfalls.js";
@@ -47,6 +48,7 @@ export const allTools = [
47
48
  ...campaignAbTestToolDefinitions,
48
49
  ...campaignFillRoutingToolDefinitions,
49
50
  ...campaignRefillStateToolDefinitions,
51
+ ...setupEvergreenCampaignsToolDefinitions,
50
52
  ...campaignHorizonFillToolDefinitions,
51
53
  ...campaignMessagePreparationToolDefinitions,
52
54
  ...campaignProcessingToolDefinitions,
@@ -0,0 +1,95 @@
1
+ type SetupEvergreenCampaignsInput = {
2
+ mode?: "plan" | "verify";
3
+ depth?: "structure_only" | "customer_visible";
4
+ yolo?: boolean;
5
+ allConnectedSenders?: boolean;
6
+ selectedSenderIds?: string[];
7
+ bindings?: Array<{
8
+ laneKey: string;
9
+ campaignId: string;
10
+ tableId: string;
11
+ }>;
12
+ planRevision?: string;
13
+ selectedActionIds?: string[];
14
+ receipts?: unknown[];
15
+ };
16
+ export declare const setupEvergreenCampaignsToolDefinitions: {
17
+ name: string;
18
+ description: string;
19
+ inputSchema: {
20
+ type: string;
21
+ properties: {
22
+ mode: {
23
+ type: string;
24
+ enum: string[];
25
+ description: string;
26
+ };
27
+ depth: {
28
+ type: string;
29
+ enum: string[];
30
+ description: string;
31
+ };
32
+ yolo: {
33
+ type: string;
34
+ description: string;
35
+ };
36
+ allConnectedSenders: {
37
+ type: string;
38
+ description: string;
39
+ };
40
+ selectedSenderIds: {
41
+ type: string;
42
+ items: {
43
+ type: string;
44
+ };
45
+ maxItems: number;
46
+ description: string;
47
+ };
48
+ bindings: {
49
+ type: string;
50
+ maxItems: number;
51
+ items: {
52
+ type: string;
53
+ properties: {
54
+ laneKey: {
55
+ type: string;
56
+ };
57
+ campaignId: {
58
+ type: string;
59
+ };
60
+ tableId: {
61
+ type: string;
62
+ };
63
+ };
64
+ required: string[];
65
+ additionalProperties: boolean;
66
+ };
67
+ description: string;
68
+ };
69
+ planRevision: {
70
+ type: string;
71
+ description: string;
72
+ };
73
+ selectedActionIds: {
74
+ type: string;
75
+ items: {
76
+ type: string;
77
+ };
78
+ maxItems: number;
79
+ description: string;
80
+ };
81
+ receipts: {
82
+ type: string;
83
+ maxItems: number;
84
+ items: {
85
+ type: string;
86
+ };
87
+ description: string;
88
+ };
89
+ };
90
+ required: never[];
91
+ additionalProperties: boolean;
92
+ };
93
+ }[];
94
+ export declare function setupEvergreenCampaigns(input: SetupEvergreenCampaignsInput): Promise<unknown>;
95
+ export {};
@@ -0,0 +1,86 @@
1
+ import { getApi } from "../api.js";
2
+ async function postSetupEvergreenCampaigns(body) {
3
+ const api = getApi();
4
+ return api.post("/api/v3/mcp/setup-evergreen-campaigns", body);
5
+ }
6
+ export const setupEvergreenCampaignsToolDefinitions = [
7
+ {
8
+ name: "setup_evergreen_campaigns",
9
+ description: "Evergreen campaign setup plan/verify command. Use plan mode first to inspect exact workspace/sender/campaign/table/source state and receive immutable lane packets for one Post Engagers lane per selected sender plus shared Signal Discovery and Shared Cold Fallback lanes. yolo is only a parent-skill auto-execution hint for safe lane packets; this backend command remains read-only in plan mode and verifies receipts in verify mode. Lane workers must execute creation, source import, create-campaign workflow steps, generate-messages, sequence attachment, and review readiness through existing create-campaign workflow/subskills, then return receipts here for verification. This command does not launch campaigns, does not schedule sends, does not assign scheduler-owned send fields, does not archive/delete cleanup targets, and does not spend paid credits.",
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ mode: {
14
+ type: "string",
15
+ enum: ["plan", "verify"],
16
+ description: 'Defaults to "plan". Verify only validates worker receipts.',
17
+ },
18
+ depth: {
19
+ type: "string",
20
+ enum: ["structure_only", "customer_visible"],
21
+ description: 'Use "customer_visible" for full setup proof or "structure_only" when only ensuring lane shells/config.',
22
+ },
23
+ yolo: {
24
+ type: "boolean",
25
+ description: "plan mode only. Requests auto-executable lane packets when state is safe; never authorizes cleanup, launch, scheduling, sending, or ambiguous target choice.",
26
+ },
27
+ allConnectedSenders: {
28
+ type: "boolean",
29
+ description: "Plan sender-owned Post Engagers lanes for all eligible connected senders in the active workspace.",
30
+ },
31
+ selectedSenderIds: {
32
+ type: "array",
33
+ items: { type: "string" },
34
+ maxItems: 25,
35
+ description: "Exact OutboundSenderIdentity ids for sender-owned Post Engagers lanes and shared-lane sender set.",
36
+ },
37
+ bindings: {
38
+ type: "array",
39
+ maxItems: 20,
40
+ items: {
41
+ type: "object",
42
+ properties: {
43
+ laneKey: { type: "string" },
44
+ campaignId: { type: "string" },
45
+ tableId: { type: "string" },
46
+ },
47
+ required: ["laneKey", "campaignId", "tableId"],
48
+ additionalProperties: false,
49
+ },
50
+ description: "Exact existing campaign/table bindings to reuse for lane keys. Do not pass names.",
51
+ },
52
+ planRevision: {
53
+ type: "string",
54
+ description: "Required for verify. Copy from the immediately preceding plan.",
55
+ },
56
+ selectedActionIds: {
57
+ type: "array",
58
+ items: { type: "string" },
59
+ maxItems: 20,
60
+ description: "Required for verify. Immutable action ids selected from the current plan.",
61
+ },
62
+ receipts: {
63
+ type: "array",
64
+ maxItems: 20,
65
+ items: { type: "object" },
66
+ description: "Required for verify. Structured lane worker receipts from create-campaign/source/message/sequence execution.",
67
+ },
68
+ },
69
+ required: [],
70
+ additionalProperties: false,
71
+ },
72
+ },
73
+ ];
74
+ export function setupEvergreenCampaigns(input) {
75
+ return postSetupEvergreenCampaigns({
76
+ mode: input.mode,
77
+ yolo: input.yolo,
78
+ depth: input.depth,
79
+ allConnectedSenders: input.allConnectedSenders,
80
+ selectedSenderIds: input.selectedSenderIds,
81
+ bindings: input.bindings,
82
+ planRevision: input.planRevision,
83
+ selectedActionIds: input.selectedActionIds,
84
+ receipts: input.receipts,
85
+ });
86
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.342",
3
+ "version": "0.1.343",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -51,6 +51,58 @@ Default evergreen plan per workspace (override only if the prompt specifies diff
51
51
  3. **`<Workspace/Team> - Shared Cold Fallback`** — one shared cold/fallback campaign lane across senders
52
52
  </inputs>
53
53
 
54
+ <command_backed_workflow>
55
+ For full/customer-visible setup, the first operational command is always
56
+ `setup_evergreen_campaigns({ mode:"plan", yolo })`. Inventory can happen first
57
+ for operator understanding, but do not create/reuse/repair campaign shells,
58
+ source rows, messages, sequence, approvals, or cleanup before the command-backed
59
+ plan returns lane packets.
60
+
61
+ Plan shape:
62
+
63
+ - one Post Engagers lane per selected sender;
64
+ - one shared Signal Discovery lane;
65
+ - one shared Cold Fallback lane;
66
+ - explicit existing campaign/table bindings when the user points at a canonical
67
+ lane. Pass those exact ids to the plan and reuse/verify them instead of
68
+ creating duplicates.
69
+ - If a user supplies canonical ids, reuse/verify them instead of creating duplicates.
70
+
71
+ When senders are ambiguous, ask which sender ids need Post Engagers lanes before
72
+ planning. If the operator says all connected senders, pass `allConnectedSenders`
73
+ and let the command return exact selected sender ids or blockers.
74
+
75
+ `--yolo` is supported, but it is a safety-scoped execution mode, not a cleanup
76
+ or launch permission. With `--yolo`, pass `yolo:true` to
77
+ `setup_evergreen_campaigns({ mode:"plan", yolo })` and proceed without
78
+ intermediate approval only when every selected lane packet returns
79
+ `autoExecutable:true`. `--yolo` does not authorize archive/delete cleanup,
80
+ launch/start, schedule/send, broad approve-all, wrong workspace work, stale
81
+ plan execution, paid InMail spend, or ambiguous target choice. Stop on wrong
82
+ workspace, cleanup/archive/delete need, stale plan, disconnected sender,
83
+ duplicate candidate, unsafe side effect, missing source proof, or missing
84
+ message proof.
85
+
86
+ After approval or safe `--yolo`, fan out lane worker tasks only from the current
87
+ plan. Each worker receives the complete lane packet: workspace id, sender ids,
88
+ lane key/type, existing campaign/table binding or create intent, source hints,
89
+ caps, planRevision, actionId, allowed side effects, and postconditions. Workers
90
+ must use the create-campaign workflow and create-campaign workflow/subskills for
91
+ campaign creation/setup, source/import, filter choice, Message Drafting,
92
+ packaged `generate-messages`, sequence attachment, and send-review readiness.
93
+ Do not rebuild those steps ad hoc in the parent thread.
94
+
95
+ Workers return worker receipts. The parent then calls
96
+ `setup_evergreen_campaigns({ mode:"verify", planRevision, selectedActionIds,
97
+ receipts })` and report only verified completion. If verify blocks, report the
98
+ blocker and repair only the missing postconditions from the same current plan.
99
+
100
+ Message proof for every customer-visible lane must come from the current
101
+ campaign/table basis and include at least 3 generated review rows from the
102
+ packaged `generate-messages` path before completion is reported.
103
+ Parent-thread handwritten copy or setting `currentStep` is not proof.
104
+ </command_backed_workflow>
105
+
54
106
  <objective>
55
107
  1. **Inventory first**: `get_campaigns` + `list_tables` + `get_campaign_waterfall` in the active workspace. Treat `list_tables` and the managed waterfall as authoritative for older managed slots; `get_campaigns` is a recent campaign page and may miss canonical evergreen lanes. Match existing campaigns/tables/waterfall slots to the plan by name (case-insensitive, ignore suffixes like "(Copy)") and stored slot identity. `list_tables.campaignStatus` and `list_tables.dashboardBucket` are part of the identity check: a matching `ARCHIVED` table/campaign is not a plain `REUSE`. If it is the canonical prod slot and the invocation explicitly allows dashboard visibility repair, repair it to `PAUSED`; otherwise mark it `flagged`/`blocked` and do not create a duplicate. A matching non-archived campaign/table/waterfall slot = REUSE; record it and move on. Never create a second campaign for a slot that already has one in any of those inventories.
56
108
  2. **Create only the missing slots** with `create_on_demand_campaign({ name, senderIds, campaignBrief })`: