@sellable/mcp 0.1.70 → 0.1.72

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.
@@ -24,7 +24,8 @@
24
24
  },
25
25
  "codex": {
26
26
  "description": "Sellable lead-source scout for LinkedIn post engagement and active conversation signals.",
27
- "modelReasoningEffort": "medium",
27
+ "model": "gpt-5.5",
28
+ "modelReasoningEffort": "high",
28
29
  "sandboxMode": "read-only",
29
30
  "nicknameCandidates": [
30
31
  "LinkedIn Engagement Scout",
@@ -71,7 +72,8 @@
71
72
  },
72
73
  "codex": {
73
74
  "description": "Sellable lead-source scout for Sales Navigator role, company, and activity filters.",
74
- "modelReasoningEffort": "medium",
75
+ "model": "gpt-5.5",
76
+ "modelReasoningEffort": "high",
75
77
  "sandboxMode": "read-only",
76
78
  "nicknameCandidates": [
77
79
  "Sales Nav Scout",
@@ -118,7 +120,8 @@
118
120
  },
119
121
  "codex": {
120
122
  "description": "Sellable lead-source scout for Prospeo account/domain and broad contact expansion.",
121
- "modelReasoningEffort": "medium",
123
+ "model": "gpt-5.5",
124
+ "modelReasoningEffort": "high",
122
125
  "sandboxMode": "read-only",
123
126
  "nicknameCandidates": [
124
127
  "Prospeo Contact Scout",
@@ -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
  }
@@ -71,6 +71,8 @@ export interface SourceScoutRegistryResponse {
71
71
  codex: {
72
72
  filename: string;
73
73
  description: string;
74
+ model: string;
75
+ modelReasoningEffort: string;
74
76
  };
75
77
  claude: {
76
78
  filename: string;
@@ -211,6 +211,8 @@ export function getSourceScoutRegistry() {
211
211
  codex: {
212
212
  filename: String(agent.codex?.filename || `${agent.name}.toml`),
213
213
  description: String(agent.codex?.description || ""),
214
+ model: String(agent.codex?.model || "gpt-5.5"),
215
+ modelReasoningEffort: String(agent.codex?.modelReasoningEffort || "high"),
214
216
  },
215
217
  claude: {
216
218
  filename: String(agent.claude?.filename || `${agent.name}.md`),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.70",
3
+ "version": "0.1.72",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",