@sellable/mcp 0.1.31 → 0.1.32
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/auth.d.ts +25 -0
- package/dist/auth.js +30 -0
- package/dist/server.js +10 -4
- package/dist/tools/campaigns.d.ts +10 -97
- package/dist/tools/campaigns.js +26 -150
- package/dist/tools/cli-login.d.ts +80 -0
- package/dist/tools/cli-login.js +207 -0
- package/dist/tools/flow-preflight.d.ts +3 -16
- package/dist/tools/flow-preflight.js +6 -25
- package/dist/tools/navigation.js +2 -2
- package/dist/tools/one-off.d.ts +15 -2
- package/dist/tools/one-off.js +23 -6
- package/dist/tools/prompts.d.ts +2 -2
- package/dist/tools/prompts.js +2 -2
- package/dist/tools/readiness.d.ts +1 -0
- package/dist/tools/readiness.js +4 -2
- package/dist/tools/sequencer.d.ts +7 -0
- package/dist/tools/sequencer.js +8 -2
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +66 -5
- package/skills/create-campaign-v2/core/flow.v2.json +0 -2
- package/skills/research-sender/SKILL.md +78 -36
|
@@ -4,6 +4,8 @@ description: Create a Sellable campaign through the approval-gated workflow.
|
|
|
4
4
|
visibility: public
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- mcp__sellable__get_auth_status
|
|
7
|
+
- mcp__sellable__start_cli_login
|
|
8
|
+
- mcp__sellable__wait_for_cli_login
|
|
7
9
|
- mcp__sellable__bootstrap_create_campaign
|
|
8
10
|
- mcp__sellable__get_subskill_prompt
|
|
9
11
|
- mcp__sellable__search_subskill_prompts
|
|
@@ -323,14 +325,73 @@ plugin cache paths, MCP namespaces, or runbooks in customer-facing progress
|
|
|
323
325
|
updates.
|
|
324
326
|
|
|
325
327
|
1. Call `mcp__sellable__get_auth_status({})`.
|
|
326
|
-
2. If auth is not OK
|
|
327
|
-
|
|
328
|
-
|
|
328
|
+
2. If auth is not OK with `error.type === "config"` or `error.type === "auth"`,
|
|
329
|
+
the user has not signed in yet. Run the FTUX magic-link handoff:
|
|
330
|
+
|
|
331
|
+
a. Say to the user verbatim:
|
|
332
|
+
|
|
333
|
+
```text
|
|
334
|
+
Welcome to Sellable. What's your email?
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
b. Wait for the user to paste their email in normal chat. Do NOT use
|
|
338
|
+
`AskUserQuestion` / `request_user_input` for this — it's free-text input.
|
|
339
|
+
|
|
340
|
+
c. Call `mcp__sellable__start_cli_login({ email })` with the email the user
|
|
341
|
+
typed.
|
|
342
|
+
|
|
343
|
+
d. If `start_cli_login` returns `ok: false`, surface `error.guidance` to the
|
|
344
|
+
user and stop. Do not retry automatically.
|
|
345
|
+
|
|
346
|
+
e. On `ok: true`, say to the user verbatim (substituting the email exactly
|
|
347
|
+
as the user typed it):
|
|
348
|
+
|
|
349
|
+
```text
|
|
350
|
+
Magic link sent to {email}. Click it from your inbox — I'll wait. (If your team already has a Sellable workspace, ask an admin to invite you instead — that gets you straight into their data.)
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
f. Call `mcp__sellable__wait_for_cli_login({ sessionId })` using the
|
|
354
|
+
`sessionId` returned by `start_cli_login`.
|
|
355
|
+
|
|
356
|
+
- If the result is `error.type === "tool_timeout_guard"`, IMMEDIATELY
|
|
357
|
+
re-call `mcp__sellable__wait_for_cli_login({ sessionId })` with the
|
|
358
|
+
SAME sessionId. Do not narrate anything to the user. Do not call
|
|
359
|
+
`start_cli_login` again — that would send a new magic link and confuse
|
|
360
|
+
them. Loop on `tool_timeout_guard` until you get a different result.
|
|
361
|
+
|
|
362
|
+
- If `error.type === "expired"` or `error.type === "timeout"`, say to the
|
|
363
|
+
user verbatim and stop:
|
|
364
|
+
|
|
365
|
+
```text
|
|
366
|
+
That magic link expired. Run /sellable:create-campaign again to retry.
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
- If `error.type === "already_consumed"` or any other error, surface
|
|
370
|
+
`error.guidance` and stop.
|
|
371
|
+
|
|
372
|
+
- On `ok: true`, the user is signed in and `~/.sellable/config.json` has
|
|
373
|
+
been written. Your IMMEDIATE next visible message MUST be the locked
|
|
374
|
+
Step 3 narration verbatim (no welcome line, no "all set", no "you're
|
|
375
|
+
signed in", no acknowledgement of any kind):
|
|
376
|
+
|
|
377
|
+
```text
|
|
378
|
+
Now — paste the LinkedIn profile URL of the person you want to send from.
|
|
379
|
+
```
|
|
380
|
+
|
|
381
|
+
After the user pastes the URL, proceed with the existing identity-first
|
|
382
|
+
sender flow (Step 3 onwards in the v2 subskill prompt — sender
|
|
383
|
+
enrichment via `fetch_linkedin_profile` / `enrich_sender`).
|
|
384
|
+
|
|
385
|
+
3. If auth is not OK with `error.type === "workspace"` (token valid, no active
|
|
386
|
+
workspace), stop and show the returned guidance — that's not a fresh-user
|
|
387
|
+
scenario; the user needs to run `set_active_workspace`.
|
|
388
|
+
4. Detect optional campaign id in the user request (`cmp_...`).
|
|
389
|
+
5. If no campaign id is provided, stay in fresh-create mode and do not call campaign discovery/resume helpers to find one.
|
|
329
390
|
- Do not call `mcp__sellable__get_campaigns`.
|
|
330
391
|
- Do not call `mcp__sellable__get_campaign` to hunt for IDs.
|
|
331
392
|
- Do not call `mcp__sellable__create_campaign({ campaignId: ... })` unless the user supplied that id.
|
|
332
|
-
|
|
333
|
-
|
|
393
|
+
6. Call `mcp__sellable__bootstrap_create_campaign({ flowVersion: "v2", campaignId? })`.
|
|
394
|
+
7. If `safeToProceed !== true`, stop and show `blockingErrors` + `nextStep`.
|
|
334
395
|
|
|
335
396
|
## Execute Workflow
|
|
336
397
|
|
|
@@ -102,7 +102,6 @@
|
|
|
102
102
|
"allowedTools": [
|
|
103
103
|
"list_senders",
|
|
104
104
|
"get_sender",
|
|
105
|
-
"enrich_sender",
|
|
106
105
|
"fetch_company",
|
|
107
106
|
"fetch_linkedin_profile",
|
|
108
107
|
"WebFetch",
|
|
@@ -174,7 +173,6 @@
|
|
|
174
173
|
"get_active_workspace",
|
|
175
174
|
"list_senders",
|
|
176
175
|
"get_sender",
|
|
177
|
-
"enrich_sender",
|
|
178
176
|
"complete_sender_research",
|
|
179
177
|
"fetch_company",
|
|
180
178
|
"fetch_company_posts",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: research-sender
|
|
3
|
-
description: Parallel-first sender research protocol. One round of batched tool calls, ~
|
|
3
|
+
description: "Parallel-first sender research protocol. One round of five batched tool calls (fetch_linkedin_profile + fetch_company + 3× WebSearch), no enrich_sender required, ~30-40s wall time."
|
|
4
4
|
visibility: internal
|
|
5
|
+
allowed-tools: mcp__sellable__fetch_linkedin_profile mcp__sellable__fetch_company mcp__sellable__complete_sender_research WebSearch ToolSearch
|
|
5
6
|
---
|
|
6
7
|
|
|
7
8
|
# Research Sender (Parallel-First)
|
|
@@ -12,28 +13,36 @@ This protocol is standalone and does not depend on the generic `research` subski
|
|
|
12
13
|
## Core Idea — Don't Wait, Fan Out
|
|
13
14
|
|
|
14
15
|
Old protocol: call `enrich_sender` → wait for partial → maybe spawn 2-3 subagents → wait.
|
|
15
|
-
That's 3-4 sequential rounds and ~
|
|
16
|
+
That's 3-4 sequential rounds and ~88s+ wall time.
|
|
16
17
|
|
|
17
|
-
New protocol: **fire one parallel batch.** `enrich_sender`
|
|
18
|
-
`
|
|
19
|
-
|
|
18
|
+
New protocol: **fire one parallel batch.** No `enrich_sender` precondition.
|
|
19
|
+
`fetch_linkedin_profile` (sender background), `fetch_company` (company description /
|
|
20
|
+
industry / employees), and 3× `WebSearch` (proof, growth, positioning) all run in the
|
|
21
|
+
same window. They finish in ~10-15s if truly parallel, ~30-40s when the model
|
|
22
|
+
serializes (interactive mode is best-effort, headless `claude -p` always serializes).
|
|
20
23
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Don't skip it — just stop waiting for it before doing the rest of the research.
|
|
24
|
+
Then synthesize once into the campaign brief — the brief is what downstream
|
|
25
|
+
message generation reads, not a `clientProspect` row. `clientProspectId` is
|
|
26
|
+
optional in `create_campaign`; pass `senderLinkedinUrl` instead.
|
|
25
27
|
|
|
26
28
|
## Inputs
|
|
27
29
|
|
|
28
30
|
Provide as many as available. Use `"Unknown"` for missing values.
|
|
29
31
|
|
|
30
|
-
- `linkedinUrl` (REQUIRED)
|
|
32
|
+
- `linkedinUrl` (REQUIRED — drives `fetch_linkedin_profile`)
|
|
31
33
|
- `name`
|
|
32
34
|
- `title`
|
|
33
35
|
- `companyName`
|
|
34
|
-
- `companyDomain` (REQUIRED
|
|
36
|
+
- `companyDomain` (REQUIRED — drives `fetch_company` and all three WebSearch queries; derive from email or LinkedIn if absent)
|
|
35
37
|
- `headline` (optional)
|
|
36
38
|
|
|
39
|
+
## Execution Backend Routing
|
|
40
|
+
|
|
41
|
+
This skill runs differently depending on the host:
|
|
42
|
+
|
|
43
|
+
- **Claude Code host:** the parallel batch uses MCP tools directly — `mcp__sellable__fetch_linkedin_profile`, `mcp__sellable__fetch_company`, and `WebSearch`. Issue all five calls as `tool_use` blocks in a single assistant message.
|
|
44
|
+
- **Codex host:** if MCP `fetch_*` tools are not available, fall back to web-fetch equivalents — use the LinkedIn URL with WebFetch for the sender profile, fetch the company's `/about` page for company facts, and use the same three WebSearch queries below for proof / growth / positioning. Synthesis treats both backends identically.
|
|
45
|
+
|
|
37
46
|
## Setup: Pre-Load Deferred Tools (One-Time, Before The Parallel Batch)
|
|
38
47
|
|
|
39
48
|
Some hosts (Claude Code with deferred-tool fetch) require `WebSearch` and
|
|
@@ -41,27 +50,28 @@ some MCP tools to be loaded via `ToolSearch` before they can be invoked. If
|
|
|
41
50
|
they are not already directly callable, issue this once as the first tool
|
|
42
51
|
call, on its own:
|
|
43
52
|
|
|
44
|
-
`ToolSearch({ query: "select:WebSearch,
|
|
53
|
+
`ToolSearch({ query: "select:WebSearch,mcp__sellable__fetch_linkedin_profile,mcp__sellable__fetch_company,mcp__sellable__complete_sender_research", max_results: 5 })`
|
|
45
54
|
|
|
46
55
|
Skip this turn entirely if the tools are already directly callable.
|
|
47
56
|
|
|
48
57
|
## The One Round (Parallel Batch)
|
|
49
58
|
|
|
50
|
-
**HARD RULE:** the
|
|
59
|
+
**HARD RULE:** the five tool calls below MUST be emitted as **five `tool_use`
|
|
51
60
|
content blocks inside a single assistant message** (one turn, parallel
|
|
52
61
|
execution). Do NOT split them across multiple assistant messages.
|
|
53
62
|
|
|
54
|
-
Concretely, your next assistant message must contain exactly these
|
|
63
|
+
Concretely, your next assistant message must contain exactly these five
|
|
55
64
|
`tool_use` blocks, in any order, with no leading or trailing prose:
|
|
56
65
|
|
|
57
|
-
1. `
|
|
58
|
-
2. `
|
|
59
|
-
3. `WebSearch({ query: '
|
|
60
|
-
4. `WebSearch({ query: '"{companyName}"
|
|
66
|
+
1. `fetch_linkedin_profile({ linkedinUrl })` — sender's LinkedIn profile (firstName/lastName, currentCompany, experience, follower count, education) for the brief's "sender background" section.
|
|
67
|
+
2. `fetch_company({ companyDomain })` — sender's company LinkedIn page (description, industry, employee range, recent posts). REQUIRED — replaces what `enrich_sender` used to provide for `companySnapshot`.
|
|
68
|
+
3. `WebSearch({ query: 'site:{companyDomain} ("case study" OR "customer story" OR testimonial OR "success story") "{companyName}"' })` — proof.
|
|
69
|
+
4. `WebSearch({ query: '"{companyName}" {companyDomain} {currentYear} (funding OR raised OR seed OR series OR hiring OR launch OR "press release")' })` — growth/credibility.
|
|
70
|
+
5. `WebSearch({ query: '"{companyName}" about product site:{companyDomain}' })` — positioning.
|
|
61
71
|
|
|
62
|
-
**Self-check before you reply:** if your reply contains fewer than
|
|
72
|
+
**Self-check before you reply:** if your reply contains fewer than five
|
|
63
73
|
`tool_use` blocks (after the optional `ToolSearch` setup turn), you are
|
|
64
|
-
violating the protocol. Stop, rewrite the reply with all
|
|
74
|
+
violating the protocol. Stop, rewrite the reply with all five.
|
|
65
75
|
|
|
66
76
|
**Known limitation (`claude -p` headless mode):** Claude often serializes
|
|
67
77
|
these calls one-per-turn even with explicit instructions. That's fine — the
|
|
@@ -69,35 +79,63 @@ skill still produces the same output, just with sequential tool execution
|
|
|
69
79
|
(~30–40s total wall time vs. ~10–15s if truly parallelized). If you can
|
|
70
80
|
batch, do; if not, proceed sequentially without retrying or re-prompting.
|
|
71
81
|
|
|
72
|
-
No subagent fan-out. No second round of enrichment polling.
|
|
82
|
+
No subagent fan-out. No second round of enrichment polling. **No `enrich_sender` call.**
|
|
73
83
|
|
|
74
84
|
## Synthesis (Single Pass After Batch Returns)
|
|
75
85
|
|
|
76
|
-
Merge the
|
|
77
|
-
for company
|
|
86
|
+
Merge the five results into the campaign brief. Treat `fetch_linkedin_profile` and
|
|
87
|
+
`fetch_company` as authoritative for sender/company facts; treat `WebSearch` as
|
|
88
|
+
proof and signals.
|
|
78
89
|
|
|
79
90
|
```markdown
|
|
80
91
|
## {Company} - Sender Research
|
|
81
92
|
|
|
82
|
-
**
|
|
83
|
-
**
|
|
84
|
-
**
|
|
85
|
-
**
|
|
86
|
-
**
|
|
93
|
+
**Sender Background:** [from fetch_linkedin_profile — name, current title, follower count, key experience]
|
|
94
|
+
**Company Context:** [from fetch_company.description OR positioning WebSearch top result]
|
|
95
|
+
**Industry / Size:** [from fetch_company.industry + employee range]
|
|
96
|
+
**Positioning Notes:** [positioning WebSearch top results + any positioning one-liner from fetch_company]
|
|
97
|
+
**Proof Options:** [case-study WebSearch hits — pick 1-3 with named customers / metrics]
|
|
98
|
+
**Credibility Signals:** [growth WebSearch hits (funding, hiring, press) + any high-signal posts from fetch_company]
|
|
87
99
|
**Gaps:** [what's still missing — usually fine to ship with this]
|
|
88
100
|
```
|
|
89
101
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
`
|
|
93
|
-
|
|
102
|
+
The synthesis result is what feeds the campaign brief, which is what message
|
|
103
|
+
generation reads via `campaignBriefContent` (see `getOfferPositioning()` in
|
|
104
|
+
`prompt.ts:236`). The brief is the load-bearing path now — `clientProspect`
|
|
105
|
+
contributes only fallback context and the sender display name (which is
|
|
106
|
+
resolved from the `Sender` row's profileData when `clientProspect` is null).
|
|
107
|
+
|
|
108
|
+
### Partial Failure Handling
|
|
109
|
+
|
|
110
|
+
The five calls run in parallel, so any one of them can fail independently
|
|
111
|
+
(404, rate-limit, timeout). Synthesis MUST still produce a usable brief.
|
|
112
|
+
Apply these rules:
|
|
113
|
+
|
|
114
|
+
- **If `fetch_company` errors or returns empty:** mark `Company Context` as
|
|
115
|
+
`[unavailable — fetch_company failed]`. Use the positioning WebSearch top
|
|
116
|
+
result as a partial substitute. Set `Industry / Size` to `[unavailable]`
|
|
117
|
+
(do NOT guess). The brief still ships; downstream message generation reads
|
|
118
|
+
the brief verbatim and is null-safe on missing fields.
|
|
119
|
+
- **If `fetch_linkedin_profile` errors:** fall back to WebSearch-derived
|
|
120
|
+
sender background. Set `Sender Background` to a one-liner derived from the
|
|
121
|
+
positioning WebSearch ("found via {companyDomain}/about and other web
|
|
122
|
+
signals — full LinkedIn profile unavailable").
|
|
123
|
+
- **If a WebSearch errors:** mark the corresponding section
|
|
124
|
+
(`Proof Options`, `Credibility Signals`, or `Positioning Notes`) as `[no
|
|
125
|
+
results]` and continue. Do NOT retry — one round only.
|
|
126
|
+
- **NEVER skip `complete_sender_research`** even on partial failure. Pass
|
|
127
|
+
`proofItemsFound: 0`, `caseStudyItemsFound: 0`, `credibilitySignalsFound: 0`
|
|
128
|
+
for the failed sections so the preflight knows the research ran.
|
|
129
|
+
|
|
130
|
+
**The contract:** synthesis always produces SOME brief. `complete_sender_research`
|
|
131
|
+
always fires. The brief ships even with gaps; downstream is null-safe.
|
|
94
132
|
|
|
95
133
|
## Optional Deepen (Only If Synthesis Reveals A Hard Gap)
|
|
96
134
|
|
|
97
135
|
If after synthesis you genuinely have zero proof and zero credibility
|
|
98
136
|
signals, AND the campaign fixture/operator told you proof is required, you
|
|
99
137
|
MAY issue ONE additional WebFetch on the most promising case-study URL from
|
|
100
|
-
|
|
138
|
+
the proof WebSearch. Cap at one WebFetch. No subagents, no second WebSearch round.
|
|
101
139
|
|
|
102
140
|
If that still yields nothing, set `proofItemsFound: 0` in
|
|
103
141
|
`complete_sender_research` and proceed; the brief can ship without proof.
|
|
@@ -122,7 +160,7 @@ If no reliable evidence is found, set counts to 0 and include that in `notes`.
|
|
|
122
160
|
Before issuing the parallel batch:
|
|
123
161
|
|
|
124
162
|
```
|
|
125
|
-
Pulling sender
|
|
163
|
+
Pulling sender profile + company snapshot + case studies + growth signals + positioning in parallel (~10s)...
|
|
126
164
|
```
|
|
127
165
|
|
|
128
166
|
After synthesis:
|
|
@@ -133,7 +171,11 @@ Sender research ready — {proofItemsFound} proof items, {credibilitySignalsFoun
|
|
|
133
171
|
|
|
134
172
|
## What Changed From The Previous Protocol
|
|
135
173
|
|
|
174
|
+
- **Removed `enrich_sender` from the parallel batch entirely.** The MCP `create_campaign` tool now accepts `senderLinkedinUrl` as an alternative to `clientProspectId`. `enrich_sender` itself has been removed from the MCP tool registry — calling it returns "tool not found".
|
|
175
|
+
- Replaced with `fetch_company` (REQUIRED) — provides company description, industry, employee data that `enrich_sender` used to give via `companySnapshot`.
|
|
176
|
+
- Five parallel tool calls instead of four (added `fetch_linkedin_profile` + `fetch_company`, dropped `enrich_sender`).
|
|
177
|
+
- Added explicit Partial Failure Handling — synthesis always produces a brief even when one or two of the five calls error.
|
|
136
178
|
- No "minimal-verification vs deep-proof" branch. Always run the same parallel batch.
|
|
137
|
-
- No
|
|
138
|
-
-
|
|
179
|
+
- No subagent fan-out — WebSearch from the orchestrator turn is faster and cheaper.
|
|
180
|
+
- Synthesis writes to the campaign brief, not to an `EnrichedProspect` row.
|
|
139
181
|
- One round, one synthesis, one `complete_sender_research` call.
|