@sellable/mcp 0.1.342 → 0.1.344

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. When safe-yolo needs normal setup work, the parent skill may ask for bounded delegated approval: one approval over the current planRevision, selected action ids, caps, allowed side-effect classes, and stop conditions lets lane workers execute without per-substep approval while staying inside that packet. 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.344",
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,78 @@ 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
+ When safe-yolo is blocked only because the plan needs normal setup work, ask
87
+ for a **bounded delegated approval** over the rendered current plan instead of
88
+ asking for each substep. The approval packet must name the workspace id, sender
89
+ ids, lane keys, campaign/table ids or create intents, source/import caps,
90
+ generate-message caps, route-proof approval policy, selectedActionIds,
91
+ planRevision, allowed side-effect classes, and stop conditions. After that one
92
+ approval covers the current planRevision, the parent may act on behalf of the
93
+ operator and execute the selectedActionIds end to end. Do not ask again for
94
+ substep approvals for source import, create-campaign choices, Message Drafting,
95
+ sequence attach/precheck, or exactly one route-proof approval when those actions
96
+ are within the lane packet, approved caps, approved side-effect classes, and
97
+ route-proof approval policy. Stop and re-plan if any fresh reread changes the
98
+ workspace id, sender ids, source/list id, campaign/table id, new campaign/table
99
+ id, planRevision, actionId, selectedActionIds, allowed side effects, caps,
100
+ status, or blocker set outside the approved packet.
101
+ The short rule: one approval covers the current planRevision and selectedActionIds.
102
+ The execution rule: act on behalf of the operator; do not ask again for substep approvals while work stays within the lane packet, approved caps, approved side-effect classes, and route-proof approval policy.
103
+ The drift rule: stop and re-plan when ids, caps, blockers, side-effect classes, or any new campaign/table id leave the approved packet.
104
+
105
+ After bounded delegated approval or safe `--yolo`, fan out lane worker tasks
106
+ only from the current plan. Each worker receives the complete lane packet:
107
+ workspace id, sender ids, lane key/type, existing campaign/table binding or
108
+ create intent, source hints, caps, planRevision, actionId, allowed side effects,
109
+ and postconditions. Workers must use the create-campaign workflow and
110
+ create-campaign workflow/subskills for campaign creation/setup, source/import,
111
+ filter choice, Message Drafting, packaged `generate-messages`, sequence
112
+ attachment, and send-review readiness. Do not rebuild those steps ad hoc in the
113
+ parent thread.
114
+
115
+ Workers return worker receipts. The parent then calls
116
+ `setup_evergreen_campaigns({ mode:"verify", planRevision, selectedActionIds,
117
+ receipts })` and report only verified completion. If verify blocks, report the
118
+ blocker and repair only the missing postconditions from the same current plan.
119
+
120
+ Message proof for every customer-visible lane must come from the current
121
+ campaign/table basis and include at least 3 generated review rows from the
122
+ packaged `generate-messages` path before completion is reported.
123
+ Parent-thread handwritten copy or setting `currentStep` is not proof.
124
+ </command_backed_workflow>
125
+
54
126
  <objective>
55
127
  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
128
  2. **Create only the missing slots** with `create_on_demand_campaign({ name, senderIds, campaignBrief })`: