@sellable/mcp 0.1.69 → 0.1.71

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/README.md CHANGED
@@ -240,13 +240,16 @@ Provider preflight contract:
240
240
 
241
241
  Parallel execution contract:
242
242
 
243
- - Claude host: use the installed `lead-explorer-signals`,
244
- `lead-explorer-sales-nav`, and `lead-explorer-prospeo` Task/Agent subagents
243
+ - Source scout names come from `agents/registry.json` and are exposed at runtime
244
+ through `get_source_scout_registry`. Add new scouts there first; installer,
245
+ Codex config, Claude agent files, and prompts should consume the registry.
246
+ - Claude host: use the installed `source-scout-linkedin-engagement`,
247
+ `source-scout-sales-nav`, and `source-scout-prospeo-contact` Task/Agent subagents
245
248
  for parallel lead-source scouting. Launch all credible lanes in one assistant
246
249
  message so Claude Code can run them concurrently/background.
247
250
  - Codex host: use named custom scouts for source-angle work when subagents are
248
- available: `linkedin_engagement_scout`, `sales_nav_scout`, and
249
- `prospeo_contact_scout`. Use `multi_tool_use.parallel` for independent
251
+ available: `source-scout-linkedin-engagement`, `source-scout-sales-nav`, and
252
+ `source-scout-prospeo-contact`. Use `multi_tool_use.parallel` for independent
250
253
  tool-call batches when subagents are unavailable or the work is only simple
251
254
  batched lookups.
252
255
  - If neither backend is available, run sequentially with the same output schema.
@@ -2,12 +2,27 @@
2
2
  "version": 1,
3
3
  "agents": [
4
4
  {
5
- "id": "linkedin-engagement-scout",
6
- "promptFile": "linkedin-engagement-scout.md",
5
+ "id": "linkedin-engagement",
6
+ "name": "source-scout-linkedin-engagement",
7
+ "promptFile": "source-scout-linkedin-engagement.md",
7
8
  "displayName": "LinkedIn Engagement Scout",
9
+ "provider": "signal-discovery",
10
+ "lane": "linkedin-engagement",
11
+ "legacy": {
12
+ "codex": [
13
+ {
14
+ "name": "linkedin_engagement_scout",
15
+ "filename": "linkedin-engagement-scout.toml"
16
+ }
17
+ ],
18
+ "claude": [
19
+ {
20
+ "name": "lead-explorer-signals",
21
+ "filename": "lead-explorer-signals.md"
22
+ }
23
+ ]
24
+ },
8
25
  "codex": {
9
- "name": "linkedin_engagement_scout",
10
- "filename": "linkedin-engagement-scout.toml",
11
26
  "description": "Sellable lead-source scout for LinkedIn post engagement and active conversation signals.",
12
27
  "modelReasoningEffort": "medium",
13
28
  "sandboxMode": "read-only",
@@ -18,8 +33,6 @@
18
33
  ]
19
34
  },
20
35
  "claude": {
21
- "name": "lead-explorer-signals",
22
- "filename": "lead-explorer-signals.md",
23
36
  "description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs LinkedIn post engagement, Signals, or active conversation evidence.",
24
37
  "model": "inherit",
25
38
  "background": true,
@@ -36,12 +49,27 @@
36
49
  }
37
50
  },
38
51
  {
39
- "id": "sales-nav-scout",
40
- "promptFile": "sales-nav-scout.md",
52
+ "id": "sales-nav",
53
+ "name": "source-scout-sales-nav",
54
+ "promptFile": "source-scout-sales-nav.md",
41
55
  "displayName": "Sales Nav Scout",
56
+ "provider": "sales-nav",
57
+ "lane": "sales-nav",
58
+ "legacy": {
59
+ "codex": [
60
+ {
61
+ "name": "sales_nav_scout",
62
+ "filename": "sales-nav-scout.toml"
63
+ }
64
+ ],
65
+ "claude": [
66
+ {
67
+ "name": "lead-explorer-sales-nav",
68
+ "filename": "lead-explorer-sales-nav.md"
69
+ }
70
+ ]
71
+ },
42
72
  "codex": {
43
- "name": "sales_nav_scout",
44
- "filename": "sales-nav-scout.toml",
45
73
  "description": "Sellable lead-source scout for Sales Navigator role, company, and activity filters.",
46
74
  "modelReasoningEffort": "medium",
47
75
  "sandboxMode": "read-only",
@@ -52,8 +80,6 @@
52
80
  ]
53
81
  },
54
82
  "claude": {
55
- "name": "lead-explorer-sales-nav",
56
- "filename": "lead-explorer-sales-nav.md",
57
83
  "description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs Sales Navigator title, company, geography, or activity-filter evidence.",
58
84
  "model": "inherit",
59
85
  "background": true,
@@ -70,12 +96,27 @@
70
96
  }
71
97
  },
72
98
  {
73
- "id": "prospeo-contact-scout",
74
- "promptFile": "prospeo-contact-scout.md",
99
+ "id": "prospeo-contact",
100
+ "name": "source-scout-prospeo-contact",
101
+ "promptFile": "source-scout-prospeo-contact.md",
75
102
  "displayName": "Prospeo Contact Scout",
103
+ "provider": "prospeo",
104
+ "lane": "prospeo-contact",
105
+ "legacy": {
106
+ "codex": [
107
+ {
108
+ "name": "prospeo_contact_scout",
109
+ "filename": "prospeo-contact-scout.toml"
110
+ }
111
+ ],
112
+ "claude": [
113
+ {
114
+ "name": "lead-explorer-prospeo",
115
+ "filename": "lead-explorer-prospeo.md"
116
+ }
117
+ ]
118
+ },
76
119
  "codex": {
77
- "name": "prospeo_contact_scout",
78
- "filename": "prospeo-contact-scout.toml",
79
120
  "description": "Sellable lead-source scout for Prospeo account/domain and broad contact expansion.",
80
121
  "modelReasoningEffort": "medium",
81
122
  "sandboxMode": "read-only",
@@ -86,8 +127,6 @@
86
127
  ]
87
128
  },
88
129
  "claude": {
89
- "name": "lead-explorer-prospeo",
90
- "filename": "lead-explorer-prospeo.md",
91
130
  "description": "Use proactively as a background Sellable source scout when find-leads or create-campaign needs Prospeo account, domain-list, CSV-domain, or verified-contact evidence.",
92
131
  "model": "inherit",
93
132
  "background": true,
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
package/dist/server.js CHANGED
@@ -21,7 +21,7 @@ import { fetchCompany, fetchCompanyPosts, fetchLinkedInPosts, fetchLinkedInProfi
21
21
  import { getCampaignNavigationState, navigationToolDefinitions, } from "./tools/navigation.js";
22
22
  import { addOnDemandLeads, createOnDemandCampaign, createOnDemandTable, initOnDemandSequence, onDemandToolDefinitions, pauseOnDemandCampaign, startOnDemandCampaign, } from "./tools/one-off.js";
23
23
  import { processingToolDefinitions, upsertRubric } from "./tools/processing.js";
24
- import { completeSenderResearch, getMessagePrompt, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, promptToolDefinitions, searchSubskillPrompts, } from "./tools/prompts.js";
24
+ import { completeSenderResearch, getMessagePrompt, getSourceScoutRegistry, getSubskillAsset, getSubskillPrompt, listSubskillPrompts, promptToolDefinitions, searchSubskillPrompts, } from "./tools/prompts.js";
25
25
  import { readinessToolDefinitions, waitForCampaignTableReady, waitForLeadListReady, } from "./tools/readiness.js";
26
26
  import { getRows, getTableRows, getTableRowsMinimal, rowToolDefinitions, } from "./tools/rows.js";
27
27
  import { addRubricItem, checkRubric, deleteRubricItem, draftRubrics, rubricToolDefinitions, saveRubrics, selectNecessaryRubrics, updateRubricItem, waitForRubricResults, } from "./tools/rubrics.js";
@@ -408,6 +408,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
408
408
  case "search_subskill_prompts":
409
409
  result = searchSubskillPrompts(args?.query, args?.limit, args?.includePublic, args?.includeInternal);
410
410
  break;
411
+ case "get_source_scout_registry":
412
+ result = getSourceScoutRegistry();
413
+ break;
411
414
  case "complete_sender_research":
412
415
  result = completeSenderResearch(args);
413
416
  break;
@@ -1,6 +1,6 @@
1
1
  import { getApi } from "../api.js";
2
2
  import { getConfig } from "../auth.js";
3
- import { assertCreateCampaignPromptLoaded, assertNetNewCreateCampaignResearchReady, } from "./flow-preflight.js";
3
+ import { assertCreateCampaignPromptLoaded } from "./flow-preflight.js";
4
4
  import { setCampaignInteractionMode, } from "./interaction-mode.js";
5
5
  import { fetchCampaignRubrics } from "./processing.js";
6
6
  const LEAD_SOURCE_PROVIDERS = {
@@ -123,7 +123,9 @@ export const campaignToolDefinitions = [
123
123
  },
124
124
  {
125
125
  name: "create_campaign",
126
- description: 'Create a new campaign offer OR resume an existing one. Low-level write tool: load create-campaign workflow instructions first via get_subskill_prompt({ subskillName: "create-campaign" }) (or bootstrap_create_campaign + nextStep). If campaignId is provided, this tool returns the watchUrl + state for that campaign instead of creating a new campaign.\n\nINPUTS:\n- `clientProspectId` is REQUIRED for net-new campaigns. It is the EnrichedProspect row ID for the campaign sender (used by ICP scoring, message generation, brief rendering — every downstream consumer reads sender context from `campaign.clientProspect`).\n- To obtain a `clientProspectId`, run the sender-enrichment flow first: this materializes an EnrichedProspect row from the sender\'s LinkedIn profile URL and returns its ID.\n- `senderLinkedinUrl` may be passed as informational metadata but is NOT a substitute for `clientProspectId` — the API will reject the create if `clientProspectId` is missing.\n\nPREREQUISITES:\n- The /research-sender skill must have been run for this sender (produces enrichment data and the EnrichedProspect row whose ID is `clientProspectId`).',
126
+ description: 'Create a new campaign offer OR resume an existing one. Low-level write tool: load create-campaign workflow instructions first via get_subskill_prompt({ subskillName: "create-campaign" }) (or bootstrap_create_campaign + nextStep). If campaignId is provided, this tool returns the watchUrl + state for that campaign instead of creating a new campaign.\n\n' +
127
+ "INPUTS:\n- Pass either `clientProspectId` (preferred) or `senderLinkedinUrl` for net-new campaigns.\n- `clientProspectId` is the EnrichedProspect row ID for the campaign sender, used by ICP scoring, message generation, and brief rendering.\n- `senderLinkedinUrl` may be passed when `clientProspectId` is not yet materialized; the backend will attempt to resolve a sender prospect from it.\n\n" +
128
+ "PREREQUISITES:\n- If you provide `senderLinkedinUrl`, it is used as a sender bootstrap signal.\n- `clientProspectId` is optional when sender URL is available and allows a direct materialized sender context.\n- `clientProspectId` is preferred for deterministic sender resolution.",
127
129
  inputSchema: {
128
130
  type: "object",
129
131
  properties: {
@@ -137,13 +139,13 @@ export const campaignToolDefinitions = [
137
139
  },
138
140
  clientProspectId: {
139
141
  type: "string",
140
- description: "REQUIRED for net-new campaigns. EnrichedProspect row ID for the campaign sender. Produced by the sender-enrichment flow (research-sender). The API will reject the create with a 400 if this is missing.",
142
+ description: "Use this for existing sender prospects. For brand-new senders, pass `senderLinkedinUrl` and the backend will attempt sender lookup/creation during campaign mint.",
141
143
  },
142
144
  // `senderLinkedinUrl` (not `linkedinUrl`) to disambiguate from
143
145
  // prospect-side `linkedinUrl` fields used in other tools (research-prospect etc.).
144
146
  senderLinkedinUrl: {
145
147
  type: "string",
146
- description: "Optional informational metadata: the sender's LinkedIn profile URL. NOT a substitute for clientProspectId to materialize a prospect from a URL, run the sender-enrichment flow first.",
148
+ description: "Optional sender LinkedIn profile URL. Use when sender prospect ID is not yet materialized and backend bootstrap should be used.",
147
149
  },
148
150
  offerPositioning: {
149
151
  type: "object",
@@ -597,32 +599,32 @@ export async function createCampaign(input) {
597
599
  missing.push("name");
598
600
  if (!input.campaignBrief)
599
601
  missing.push("campaignBrief");
600
- // clientProspectId is required for net-new campaigns. Every downstream
601
- // consumer (ICP scoring, message generation, brief rendering) reads sender
602
- // context from `campaign.clientProspect`. senderLinkedinUrl is informational
603
- // metadata only — it is NOT a substitute and the API will reject the create
604
- // if clientProspectId is missing.
605
- if (!input.clientProspectId) {
606
- missing.push("clientProspectId");
602
+ const hasClientProspectId = typeof input.clientProspectId === "string" &&
603
+ input.clientProspectId.trim().length > 0;
604
+ const senderLinkedinUrl = typeof input.senderLinkedinUrl === "string"
605
+ ? input.senderLinkedinUrl.trim()
606
+ : "";
607
+ // For net-new campaigns, require at least one sender identity signal.
608
+ if (!hasClientProspectId && !senderLinkedinUrl) {
609
+ missing.push("clientProspectId or senderLinkedinUrl");
607
610
  }
608
611
  // Cheap URL sanity check on senderLinkedinUrl when supplied.
609
- if (input.senderLinkedinUrl &&
610
- typeof input.senderLinkedinUrl === "string" &&
611
- !input.senderLinkedinUrl.includes("linkedin.com")) {
612
+ if (senderLinkedinUrl && !senderLinkedinUrl.includes("linkedin.com")) {
612
613
  throw new Error("VALIDATION_ERROR: senderLinkedinUrl must be a LinkedIn URL (must contain 'linkedin.com'). Got: " +
613
- input.senderLinkedinUrl);
614
+ senderLinkedinUrl);
614
615
  }
615
616
  if (missing.length > 0) {
616
617
  throw new Error("VALIDATION_ERROR: create_campaign requires either campaignId (resume) " +
617
618
  `or all create fields. Missing: ${missing.join(", ")}.\n\n` +
618
619
  "Remediation:\n" +
619
620
  '- For full workflow, call get_subskill_prompt({ subskillName: "create-campaign" }) and follow it.\n' +
620
- "- For net-new campaign creation: run the `research-sender` flow first to materialize an EnrichedProspect for the sender from their LinkedIn profile URL, then pass the resulting prospect ID as `clientProspectId`. The campaign sender context (used by ICP scoring, message generation, brief rendering) is read from `campaign.clientProspect`, so this row must exist before the campaign is minted.\n" +
621
+ "- For net-new campaign creation: pass either `clientProspectId` (preferred) or `senderLinkedinUrl` and let the backend resolve sender context at mint time.\n" +
621
622
  "- For resume, call create_campaign with campaignId only.");
622
623
  }
623
- assertNetNewCreateCampaignResearchReady();
624
624
  const name = input.name;
625
- const clientProspectId = input.clientProspectId ?? null;
625
+ const clientProspectId = hasClientProspectId
626
+ ? input.clientProspectId?.trim()
627
+ : null;
626
628
  const campaignBrief = input.campaignBrief;
627
629
  // Validate required campaignBrief - must be non-empty markdown
628
630
  // Normalize escaped newlines - MCP tool calls pass literal "\n" text (backslash + n)
@@ -677,15 +679,13 @@ export async function createCampaign(input) {
677
679
  ...apiInput,
678
680
  name,
679
681
  clientProspectId,
680
- // Forward senderLinkedinUrl alongside clientProspectId. Downstream API may
681
- // ignore it for now; future phase wires it.
682
- senderLinkedinUrl: input.senderLinkedinUrl ?? null,
683
682
  offerPositioning,
684
683
  ...(input.leadSourceProvider !== undefined
685
684
  ? { leadSourceProvider: normalizedLeadSourceProvider }
686
685
  : {}),
687
686
  currentStep: currentStep ?? "create-offer",
688
687
  leadSourceType: input.leadSourceType ?? "new",
688
+ senderLinkedinUrl,
689
689
  campaignBrief: {
690
690
  name, // Use campaign name as brief name
691
691
  content: briefContent, // The markdown content
@@ -29,7 +29,7 @@ export type CreateOnDemandCampaignInput = {
29
29
  name: string;
30
30
  clientProspectId?: string;
31
31
  senderLinkedinUrl?: string;
32
- senderIds: string[];
32
+ senderIds?: string[];
33
33
  campaignBrief?: unknown;
34
34
  offerPositioning?: Record<string, unknown>;
35
35
  sequenceTemplate?: Record<string, unknown>;
@@ -111,7 +111,7 @@ export const onDemandToolDefinitions = [
111
111
  description: "Set true to overwrite existing sequence columns",
112
112
  },
113
113
  },
114
- required: ["name", "senderIds"],
114
+ required: ["name"],
115
115
  },
116
116
  },
117
117
  {
@@ -194,7 +194,15 @@ export async function createOnDemandTable(input) {
194
194
  throw new Error("VALIDATION_ERROR: create_on_demand_table requires either clientProspectId (existing prospect ID) or senderLinkedinUrl (sender's LinkedIn profile URL). Pass one.");
195
195
  }
196
196
  const api = getApi();
197
- return api.post("/api/v3/on-demand-campaigns", input);
197
+ const payload = { ...input };
198
+ const senderIds = Array.isArray(input.senderIds) ? input.senderIds : [];
199
+ if (senderIds.length > 0) {
200
+ payload.senderIds = senderIds;
201
+ }
202
+ else {
203
+ delete payload.senderIds;
204
+ }
205
+ return api.post("/api/v3/on-demand-campaigns", payload);
198
206
  }
199
207
  export async function addOnDemandLeads(input) {
200
208
  const api = getApi();
@@ -237,9 +245,6 @@ export async function createOnDemandCampaign(input) {
237
245
  if (!input.clientProspectId && !input.senderLinkedinUrl) {
238
246
  throw new Error("VALIDATION_ERROR: create_on_demand_campaign requires either clientProspectId (existing prospect ID) or senderLinkedinUrl (sender's LinkedIn profile URL). Pass one.");
239
247
  }
240
- if (!Array.isArray(input.senderIds) || input.senderIds.length === 0) {
241
- throw new Error("senderIds must be a non-empty array");
242
- }
243
248
  const campaign = await createOnDemandTable({
244
249
  name: input.name,
245
250
  senderIds: input.senderIds,
@@ -284,7 +289,7 @@ export async function createOnDemandCampaign(input) {
284
289
  return {
285
290
  tableId,
286
291
  campaignOfferId,
287
- senderIds: input.senderIds,
292
+ senderIds: input.senderIds ?? [],
288
293
  sequenceInitialized: true,
289
294
  };
290
295
  }
@@ -60,6 +60,40 @@ export interface CompleteSenderResearchInput {
60
60
  credibilitySignalsFound?: number;
61
61
  notes?: string;
62
62
  }
63
+ export interface SourceScoutRegistryResponse {
64
+ version: number;
65
+ agents: Array<{
66
+ id: string;
67
+ name: string;
68
+ displayName: string;
69
+ provider: string;
70
+ lane: string;
71
+ codex: {
72
+ filename: string;
73
+ description: string;
74
+ };
75
+ claude: {
76
+ filename: string;
77
+ description: string;
78
+ tools: string[];
79
+ };
80
+ legacy?: {
81
+ codex?: Array<{
82
+ name: string;
83
+ filename: string;
84
+ }>;
85
+ claude?: Array<{
86
+ name: string;
87
+ filename: string;
88
+ }>;
89
+ };
90
+ }>;
91
+ usage: {
92
+ codex: string;
93
+ claude: string;
94
+ parentThreadRule: string;
95
+ };
96
+ }
63
97
  export declare const DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS = 48000;
64
98
  export declare const MAX_SUBSKILL_PROMPT_CHUNK_CHARS = 48000;
65
99
  export declare const promptToolDefinitions: ({
@@ -223,6 +257,29 @@ export declare const promptToolDefinitions: ({
223
257
  additionalProperties: boolean;
224
258
  };
225
259
  _meta?: undefined;
260
+ } | {
261
+ name: string;
262
+ description: string;
263
+ inputSchema: {
264
+ type: string;
265
+ properties: {
266
+ limit?: undefined;
267
+ includePublic?: undefined;
268
+ includeInternal?: undefined;
269
+ subskillName?: undefined;
270
+ offset?: undefined;
271
+ assetPath?: undefined;
272
+ query?: undefined;
273
+ depth?: undefined;
274
+ proofItemsFound?: undefined;
275
+ caseStudyItemsFound?: undefined;
276
+ credibilitySignalsFound?: undefined;
277
+ notes?: undefined;
278
+ };
279
+ required: never[];
280
+ additionalProperties: boolean;
281
+ };
282
+ _meta?: undefined;
226
283
  } | {
227
284
  name: string;
228
285
  description: string;
@@ -263,6 +320,7 @@ export declare const promptToolDefinitions: ({
263
320
  };
264
321
  _meta?: undefined;
265
322
  })[];
323
+ export declare function getSourceScoutRegistry(): SourceScoutRegistryResponse;
266
324
  export declare function getMessagePrompt(): Promise<PromptResponse>;
267
325
  export declare function listSubskillPrompts(limit?: number, includePublic?: boolean, includeInternal?: boolean): ListSubskillPromptsResponse;
268
326
  export declare function getSubskillPrompt(subskillName: string, offset?: number, limit?: number): SubskillPromptResponse;
@@ -130,6 +130,16 @@ export const promptToolDefinitions = [
130
130
  additionalProperties: false,
131
131
  },
132
132
  },
133
+ {
134
+ name: "get_source_scout_registry",
135
+ description: "Return the canonical Sellable source-scout agent names and host filenames. Use this before lead-source scouting so Codex and Claude launch the same named scouts from one registry.",
136
+ inputSchema: {
137
+ type: "object",
138
+ properties: {},
139
+ required: [],
140
+ additionalProperties: false,
141
+ },
142
+ },
133
143
  {
134
144
  name: "complete_sender_research",
135
145
  description: "Mark sender research as completed for create-campaign preflight. Call this after research-sender synthesis and before create_campaign.",
@@ -162,6 +172,60 @@ export const promptToolDefinitions = [
162
172
  },
163
173
  },
164
174
  ];
175
+ function resolveAgentsDir() {
176
+ const candidates = [];
177
+ if (process.env.SELLABLE_AGENTS_DIR) {
178
+ candidates.push(path.resolve(process.env.SELLABLE_AGENTS_DIR));
179
+ }
180
+ if (process.argv[1]) {
181
+ candidates.push(path.resolve(path.dirname(process.argv[1]), "../agents"));
182
+ try {
183
+ const realEntryPath = fs.realpathSync(process.argv[1]);
184
+ candidates.push(path.resolve(path.dirname(realEntryPath), "../agents"));
185
+ }
186
+ catch {
187
+ // Module-relative and cwd fallbacks remain.
188
+ }
189
+ }
190
+ candidates.push(path.resolve(process.cwd(), "mcp/sellable/agents"));
191
+ candidates.push(path.resolve(process.cwd(), "agents"));
192
+ for (const candidate of candidates) {
193
+ if (fs.existsSync(path.join(candidate, "registry.json"))) {
194
+ return candidate;
195
+ }
196
+ }
197
+ return candidates[0] ?? path.resolve(process.cwd(), "mcp/sellable/agents");
198
+ }
199
+ export function getSourceScoutRegistry() {
200
+ const registryPath = path.join(resolveAgentsDir(), "registry.json");
201
+ const registry = JSON.parse(fs.readFileSync(registryPath, "utf8"));
202
+ const agents = Array.isArray(registry.agents) ? registry.agents : [];
203
+ return {
204
+ version: Number(registry.version || 1),
205
+ agents: agents.map((agent) => ({
206
+ id: String(agent.id || ""),
207
+ name: String(agent.name || ""),
208
+ displayName: String(agent.displayName || agent.name || ""),
209
+ provider: String(agent.provider || ""),
210
+ lane: String(agent.lane || agent.id || ""),
211
+ codex: {
212
+ filename: String(agent.codex?.filename || `${agent.name}.toml`),
213
+ description: String(agent.codex?.description || ""),
214
+ },
215
+ claude: {
216
+ filename: String(agent.claude?.filename || `${agent.name}.md`),
217
+ description: String(agent.claude?.description || ""),
218
+ tools: Array.isArray(agent.claude?.tools) ? agent.claude.tools : [],
219
+ },
220
+ legacy: agent.legacy,
221
+ })),
222
+ usage: {
223
+ codex: "Spawn credible scouts by the returned `name` values in one assistant turn when the user approved background source scouts.",
224
+ claude: "Invoke Claude Code Task/Agent subagents with subagent_type equal to the returned `name` values.",
225
+ parentThreadRule: "Do not preload every provider prompt in the parent; each scout loads only the provider prompt for its lane.",
226
+ },
227
+ };
228
+ }
165
229
  export async function getMessagePrompt() {
166
230
  const api = getApi();
167
231
  return api.get("/api/v3/prompts/edit-message");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.69",
3
+ "version": "0.1.71",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -11,6 +11,7 @@ allowed-tools:
11
11
  - mcp__sellable__get_subskill_asset
12
12
  - mcp__sellable__search_subskill_prompts
13
13
  - mcp__sellable__get_provider_prompt
14
+ - mcp__sellable__get_source_scout_registry
14
15
  - mcp__sellable__get_message_prompt
15
16
  - mcp__sellable__get_active_workspace
16
17
  - mcp__sellable__list_senders
@@ -102,12 +103,14 @@ scout those angles as independent branches when the host can actually do it:
102
103
  LinkedIn Engagement / active post engagers (internal `signal-discovery`
103
104
  provider prompt), Sales Nav / title + company filters, and Prospeo Contact /
104
105
  domains only when relevant. In Codex, explicitly spawn the named custom scouts
105
- `linkedin_engagement_scout`, `sales_nav_scout`, and `prospeo_contact_scout` for
106
+ `source-scout-linkedin-engagement`, `source-scout-sales-nav`, and `source-scout-prospeo-contact` for
106
107
  the credible lanes; Codex does not infer subagent fan-out from generic source
107
- comparison wording. In Claude Code, invoke the generated `lead-explorer-*`
108
+ comparison wording. In Claude Code, invoke the generated `source-scout-*`
108
109
  Task/Agent subagents for all credible lanes in one assistant message; the
109
110
  installer writes them from the same canonical Sellable agent registry with
110
- explicit Sellable MCP tool allowlists. If the host runs them sequentially, do not
111
+ explicit Sellable MCP tool allowlists. The create-campaign-v2 subskill calls
112
+ `get_source_scout_registry` before dispatch so the current registry, not this
113
+ copy, is the runtime source of truth. If the host runs them sequentially, do not
111
114
  claim they ran in parallel. In chat, call the downstream copy stage `message generation`;
112
115
  `message-validation.md` is only an internal proof artifact.
113
116
 
@@ -360,6 +360,11 @@ me`, `I’ll paste a different sender profile`, and `Other / custom`.
360
360
  user-facing choice should be approve/revise language, not "looks good".
361
361
  Approval options should refer to what the user just read, e.g. `Approve this
362
362
  brief`, `Revise target`, `Revise offer/proof`, and `Other / custom`.
363
+ When the user chose `Find people for me` and the host supports subagents,
364
+ make the recommended approval label explicit: `Approve brief + use background
365
+ source scouts`. That gives Codex/Claude a user-visible request to launch
366
+ source-scout subagents after approval instead of silently falling back to
367
+ parent-thread provider probes.
363
368
  Include an `Open artifact:` link to `brief.md` before the approval question.
364
369
  The visible brief must come before local persistence chrome. After the brief
365
370
  is synthesized, render the approval-ready brief in chat before running visible
@@ -433,15 +438,18 @@ should test for this campaign. Those can run in parallel and usually take
433
438
  `message-validation.md`, then reconciles that the selected message basis rows
434
439
  still pass the final filter.
435
440
  - Parallel means real parallel execution, not optimistic progress copy. For the
436
- lead-source scout in Codex, explicitly spawn one named custom scout per
437
- credible source lane when subagents are available: `linkedin_engagement_scout`
441
+ lead-source scout, first call `get_source_scout_registry` and use the
442
+ returned canonical `name` values. In Codex, explicitly spawn one named custom scout per
443
+ credible source lane when subagents are available: `source-scout-linkedin-engagement`
438
444
  (display: LinkedIn Engagement Scout, powered by the `signal-discovery`
439
- provider prompt), `sales_nav_scout` (Sales Nav Scout), and
440
- `prospeo_contact_scout` (Prospeo Contact Scout). For Claude Code, explicitly
441
- invoke the generated `.claude/agents/lead-explorer-*.md` Task/Agent subagents
445
+ provider prompt), `source-scout-sales-nav` (Sales Nav Scout), and
446
+ `source-scout-prospeo-contact` (Prospeo Contact Scout). For Claude Code, explicitly
447
+ invoke the generated `.claude/agents/source-scout-*.md` Task/Agent subagents
442
448
  for all credible lanes in one assistant message; they are installed from the
443
449
  same canonical Sellable agent registry and carry explicit Sellable MCP tool
444
- allowlists. If host subagents are unavailable, use independent MCP/tool calls
450
+ allowlists. The parent thread should not preload every provider prompt before
451
+ spawning scouts; each scout loads only its own provider prompt. If host
452
+ subagents are unavailable, use independent MCP/tool calls
445
453
  in the same model turn or dedicated Sellable MCP tools that perform
446
454
  server-side `Promise.all` fan-out. If real parallel execution is not available
447
455
  or not allowed, run the same DAG sequentially and use honest copy: `I’ll tighten the filter first,
@@ -603,15 +611,17 @@ Required behavior:
603
611
  - do not mutate DB-backed campaign state
604
612
  - run a real parallel source-angle scout when the source is not supplied and at
605
613
  least two viable source angles exist. Treat source scouting as independent
606
- branches, then compare the outputs in `lead-review.md`. In Codex, explicitly
607
- spawn named custom subagents in the same turn: `linkedin_engagement_scout`,
608
- `sales_nav_scout`, and `prospeo_contact_scout` for the credible lanes. Codex
614
+ branches, then compare the outputs in `lead-review.md`. Call
615
+ `get_source_scout_registry` first so new scouts can be added without prompt
616
+ rewrites. In Codex, explicitly
617
+ spawn named custom subagents in the same turn: `source-scout-linkedin-engagement`,
618
+ `source-scout-sales-nav`, and `source-scout-prospeo-contact` for the credible lanes. Codex
609
619
  does not infer this from generic "compare paths" wording. If the realistic
610
620
  source set is LinkedIn Engagement + Sales Nav (Signals + Sales Nav), run both.
611
621
  If it is LinkedIn Engagement + Prospeo Contact (Signals + Prospeo), run both.
612
622
  If all three are credible, run all three when the host/runtime supports it.
613
- In Claude Code, launch the matching `lead-explorer-signals`,
614
- `lead-explorer-sales-nav`, and/or `lead-explorer-prospeo` Task/Agent subagents
623
+ In Claude Code, launch the matching `source-scout-linkedin-engagement`,
624
+ `source-scout-sales-nav`, and/or `source-scout-prospeo-contact` Task/Agent subagents
615
625
  in the same assistant message, not as sequential scout turns.
616
626
  - Branch A: LinkedIn Engagement / active LinkedIn posts (internal provider:
617
627
  Signals / `signal-discovery`). Search relevant keyword lanes, review
@@ -224,17 +224,24 @@ setting: `~/.codex/config.toml` with
224
224
  Source-angle comparison should be real, not implied. When the source is not
225
225
  supplied and multiple source angles are viable, scout Signals, Sales Nav, and
226
226
  relevant domain/contact paths as independent branches when the host can do so.
227
- In Codex, explicitly spawn named custom scouts in the same turn:
228
- `linkedin_engagement_scout` (LinkedIn Engagement Scout, backed by the
229
- `signal-discovery` provider prompt), `sales_nav_scout` (Sales Nav Scout), and
230
- `prospeo_contact_scout` (Prospeo Contact Scout) for the credible lanes. Codex
227
+ Call `get_source_scout_registry` first and use the returned canonical `name`
228
+ values; the names below are the current registry entries. In Codex, explicitly
229
+ spawn named custom scouts in the same turn:
230
+ `source-scout-linkedin-engagement` (LinkedIn Engagement Scout, backed by the
231
+ `signal-discovery` provider prompt), `source-scout-sales-nav` (Sales Nav Scout), and
232
+ `source-scout-prospeo-contact` (Prospeo Contact Scout) for the credible lanes. Codex
231
233
  does not infer subagent fan-out from generic "compare paths" wording. In Claude
232
- Code, invoke the generated `lead-explorer-signals`, `lead-explorer-sales-nav`,
233
- and/or `lead-explorer-prospeo` Task/Agent subagents in one assistant message so
234
+ Code, invoke the generated `source-scout-linkedin-engagement`, `source-scout-sales-nav`,
235
+ and/or `source-scout-prospeo-contact` Task/Agent subagents in one assistant message so
234
236
  Claude can run the source lanes concurrently/background. These agents come from
235
237
  the same canonical Sellable registry and must load their matching provider
236
- prompt before searching. If the host runs them sequentially, keep the output
237
- numeric but do not claim the source scout was parallel.
238
+ prompt before searching. The parent thread should not fetch every provider
239
+ prompt first; that burns time and often degenerates into parallel tool calls
240
+ instead of true subagents. When asking the user to approve a brief with
241
+ Sellable finding leads, use an explicit label like `Approve brief + use
242
+ background source scouts` so the next turn has a user-visible request to spawn
243
+ scouts. If the host runs them sequentially, keep the output numeric but do not
244
+ claim the source scout was parallel.
238
245
 
239
246
  Only promise parallel post-lead work when parallel work actually started. If the
240
247
  host cannot or should not launch background branches, say the real sequence:
@@ -341,7 +341,7 @@
341
341
  "action": "run_subskill",
342
342
  "target": "find-leads",
343
343
  "mode": "campaignless-preview",
344
- "sourceScoutRule": "When source is not user-supplied and at least two viable source angles exist, scout independent source angles with real parallelism when host/tooling permits. In Codex, explicitly spawn one named custom scout per credible lane in the same turn: linkedin_engagement_scout for LinkedIn Engagement / active post engagers (internal signal-discovery provider prompt), sales_nav_scout for Sales Nav title/company/activity filters, and prospeo_contact_scout for Prospeo account/domain/contact expansion. In Claude Code, explicitly invoke the generated lead-explorer-signals, lead-explorer-sales-nav, and/or lead-explorer-prospeo Task agents / Agent subagents in one assistant message so the lanes can run concurrently/background; those agents are installed from the canonical Sellable agent registry and carry explicit Sellable MCP tool allowlists. Fallback to independent MCP/tool calls in the same model turn or dedicated Sellable MCP tools that perform server-side fan-out. Run LinkedIn Engagement + Sales Nav, LinkedIn Engagement + Prospeo Contact, or all three when credible. If real parallel execution is unavailable, run the same scouts sequentially and do not claim parallel execution. Compare outputs by raw volume, n/N sampled fit, estimated good-fit range, expected reply range, and tradeoff. Keep Signals/LinkedIn Engagement as viable when selected posts can produce ~150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable; when both are viable, present the choice and recommend the stronger default."
344
+ "sourceScoutRule": "When source is not user-supplied and at least two viable source angles exist, call get_source_scout_registry and use the returned canonical agent names, then scout independent source angles with real parallelism when host/tooling permits. In Codex, explicitly spawn one named custom scout per credible lane in the same turn: source-scout-linkedin-engagement for LinkedIn Engagement / active post engagers (internal signal-discovery provider prompt), source-scout-sales-nav for Sales Nav title/company/activity filters, and source-scout-prospeo-contact for Prospeo account/domain/contact expansion. In Claude Code, explicitly invoke the generated source-scout-linkedin-engagement, source-scout-sales-nav, and/or source-scout-prospeo-contact Task agents / Agent subagents in one assistant message so the lanes can run concurrently/background; those agents are installed from the canonical Sellable agent registry and carry explicit Sellable MCP tool allowlists. Fallback to independent MCP/tool calls in the same model turn or dedicated Sellable MCP tools that perform server-side fan-out. Run LinkedIn Engagement + Sales Nav, LinkedIn Engagement + Prospeo Contact, or all three when credible. If real parallel execution is unavailable, run the same scouts sequentially and do not claim parallel execution. Compare outputs by raw volume, n/N sampled fit, estimated good-fit range, expected reply range, and tradeoff. Keep Signals/LinkedIn Engagement as viable when selected posts can produce ~150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable; when both are viable, present the choice and recommend the stronger default."
345
345
  },
346
346
  {
347
347
  "action": "write_artifacts",
@@ -65,16 +65,19 @@ The kickoff doc is the resume surface. Re-open it before repeating discovery wor
65
65
 
66
66
  ## Execution Backend Routing
67
67
 
68
+ - Before source-scout dispatch, call `get_source_scout_registry` and use the
69
+ returned `name` values. The current canonical names are listed below for
70
+ readability, but the registry is the source of truth for install/runtime.
68
71
  - If Codex subagents are available, run the scout lanes with named custom
69
72
  agents. Spawn one agent per credible lane in the same turn, then wait for all
70
73
  lane results before synthesizing `lead-review.md`:
71
- - `linkedin_engagement_scout` (display: LinkedIn Engagement Scout) for
74
+ - `source-scout-linkedin-engagement` (display: LinkedIn Engagement Scout) for
72
75
  active LinkedIn posts and engagers; internally this uses the
73
76
  `signal-discovery` provider prompt plus `search_signals` /
74
77
  `fetch_post_engagers`.
75
- - `sales_nav_scout` (display: Sales Nav Scout) for Sales Navigator title,
78
+ - `source-scout-sales-nav` (display: Sales Nav Scout) for Sales Navigator title,
76
79
  company, geography, and activity filters.
77
- - `prospeo_contact_scout` (display: Prospeo Contact Scout) for Prospeo
80
+ - `source-scout-prospeo-contact` (display: Prospeo Contact Scout) for Prospeo
78
81
  account/domain and verified-contact expansion.
79
82
  - Common parallel comparisons are LinkedIn Engagement + Sales Nav, LinkedIn
80
83
  Engagement + Prospeo Contact, or all three when each lane is credible.
@@ -82,11 +85,14 @@ The kickoff doc is the resume surface. Re-open it before repeating discovery wor
82
85
  Claude explorer agents installed from the same canonical Sellable agent
83
86
  registry. Launch every credible lane in the same assistant message so Claude
84
87
  Code can run them concurrently/background:
85
- - `./.claude/agents/lead-explorer-signals.md`
86
- - `./.claude/agents/lead-explorer-sales-nav.md`
87
- - `./.claude/agents/lead-explorer-prospeo.md`
88
+ - `./.claude/agents/source-scout-linkedin-engagement.md`
89
+ - `./.claude/agents/source-scout-sales-nav.md`
90
+ - `./.claude/agents/source-scout-prospeo-contact.md`
88
91
  - These Claude agents must have explicit Sellable MCP tool allowlists and must
89
92
  load the matching provider prompt before searching.
93
+ - The parent thread should not preload every provider prompt before launching
94
+ scouts. Spawn the credible scouts first; each scout loads only the provider
95
+ prompt for its lane and returns structured evidence.
90
96
  - Else if `multi_tool_use.parallel` is available, run independent discovery calls in parallel and synthesize locally.
91
97
  - Else run sequentially.
92
98
  - Never claim agents were used when they were not.
@@ -257,7 +263,7 @@ When two or more source angles are viable, run the provider probes in real
257
263
  parallel when the host/runtime allows it. For Codex, explicitly spawn the
258
264
  named source scout agents above; Codex will not infer subagent fan-out from
259
265
  general "compare sources" wording. For Claude Code, explicitly invoke the
260
- matching `lead-explorer-*` Task/Agent subagents in one message instead of
266
+ matching `source-scout-*` Task/Agent subagents in one message instead of
261
267
  probing one lane, waiting, then probing the next. Examples: LinkedIn Engagement
262
268
 
263
269
  - Sales Nav, LinkedIn Engagement + Prospeo Contact, or Sales Nav + Prospeo
@@ -424,12 +430,12 @@ Show the light-touch results and ask whether to deep-dispatch or iterate.
424
430
  ## Layer 3: Selective deep exploration
425
431
 
426
432
  Spawn only the validated paths from Gate 1. In Claude Code, invoke all selected
427
- `lead-explorer-*` agents in one turn so source scouting is concurrent when the
433
+ `source-scout-*` agents in one turn so source scouting is concurrent when the
428
434
  runtime supports it.
429
435
 
430
- - `lead-explorer-signals`
431
- - `lead-explorer-sales-nav`
432
- - `lead-explorer-prospeo`
436
+ - `source-scout-linkedin-engagement`
437
+ - `source-scout-sales-nav`
438
+ - `source-scout-prospeo-contact`
433
439
 
434
440
  Each explorer must return:
435
441
 
@@ -1,9 +0,0 @@
1
- {
2
- "parallelMode": "wide",
3
- "agentCount": 6,
4
- "maxToolCallsPerAgent": 2,
5
- "senderMaxAgents": 2,
6
- "senderMaxToolCallsPerAgent": 3,
7
- "progressMode": true,
8
- "debugMode": true
9
- }