@leadbay/mcp 0.23.9 → 0.23.10
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/CHANGELOG.md +8 -0
- package/README.md +5 -5
- package/dist/bin.js +73 -27
- package/dist/http-server.js +96 -26
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.23.10 — 2026-07-01
|
|
4
|
+
|
|
5
|
+
A freshly-created lens no longer reads as "empty" (product#3833).
|
|
6
|
+
|
|
7
|
+
- **`leadbay_new_lens`** — creating a lens with criteria queues an asynchronous backend wishlist rebuild, so an immediate `leadbay_pull_leads` can read empty for a few seconds while it computes. The `created` result now carries `computing_wishlist: true` and a "leads stream in — pull in ~30s" message so the agent waits instead of reporting an empty lens. A criteria-less clone inherits the base lens's leads immediately (`computing_wishlist: false`).
|
|
8
|
+
- **`leadbay_pull_leads`** — on an EMPTY page where the backend reports the lens is still computing (`computing_wishlist`/`computing_scores`), `next_steps` now carries a single "Re-pull in ~30s" option (`kind: repull_computing`) instead of `null`, so the agent renders a wait-and-retry widget rather than "no leads." An empty page with nothing computing still returns `null` (a genuinely empty / over-narrow lens — no fabricated leads).
|
|
9
|
+
- Confirmed against `leadbay/backend` (`POST /lenses/:id/filter` → `queueRefreshJob` → `RefreshLens`) and a live probe on `api-us`: a bare clone shows leads instantly; a filtered clone shows `computing_wishlist: true` then settles within ~1–30s.
|
|
10
|
+
|
|
3
11
|
## 0.23.9 — 2026-06-27
|
|
4
12
|
|
|
5
13
|
MCP data plane now targets backend **/1.6** (was /1.5).
|
package/README.md
CHANGED
|
@@ -143,11 +143,11 @@ To rotate the local MCP credential, re-run `npx -y @leadbay/mcp install --oauth`
|
|
|
143
143
|
### Install via the Claude Code plugin marketplace
|
|
144
144
|
|
|
145
145
|
```text
|
|
146
|
-
/plugin marketplace add leadbay/
|
|
146
|
+
/plugin marketplace add leadbay/mcp
|
|
147
147
|
```
|
|
148
148
|
|
|
149
149
|
```text
|
|
150
|
-
/plugin install leadbay@leadbay-
|
|
150
|
+
/plugin install leadbay@leadbay-mcp
|
|
151
151
|
```
|
|
152
152
|
|
|
153
153
|
Claude Code prompts for Leadbay auth/config through the plugin's `userConfig`. This is equivalent to the npm/CLI install above.
|
|
@@ -157,13 +157,13 @@ The plugin install gives you **two surfaces in one shot**:
|
|
|
157
157
|
1. **The MCP server** — registered via the plugin's `mcpServers.leadbay` block (boots `@leadbay/mcp` over stdio). This exposes the `leadbay_*` tools to the agent.
|
|
158
158
|
2. **Six auto-discovered Claude Code skills** under `skills/` — `leadbay_daily_check_in`, `leadbay_research_a_domain`, `leadbay_import_file`, `leadbay_log_outreach`, `leadbay_qualify_top_n`, `leadbay_refine_audience`. These auto-trigger on natural-language matches against each skill's description ("get me leadbay leads today", "research acme.com", etc.) and dispatch to the MCP tools above by name. Each `SKILL.md` is generated by `@leadbay/promptforge` from the same `.md.tmpl` source as the MCP prompt, so the two surfaces never drift.
|
|
159
159
|
|
|
160
|
-
You can verify the skills installed by running `/skill list` after install. To uninstall everything, `/plugin uninstall leadbay@leadbay-
|
|
160
|
+
You can verify the skills installed by running `/skill list` after install. To uninstall everything, `/plugin uninstall leadbay@leadbay-mcp` removes the MCP server registration **and** the skills together.
|
|
161
161
|
|
|
162
162
|
### Claude Desktop
|
|
163
163
|
|
|
164
164
|
The guided installer writes the local `mcpServers.leadbay` entry in `claude_desktop_config.json` with `npx -y @leadbay/mcp@latest`, like the Linux flow. Restart Claude Desktop after install.
|
|
165
165
|
|
|
166
|
-
The `.dxt` / `.mcpb` bundle from [Releases](https://github.com/leadbay/
|
|
166
|
+
The `.dxt` / `.mcpb` bundle from [Releases](https://github.com/leadbay/mcp/releases/latest) remains available as an alternative install path.
|
|
167
167
|
|
|
168
168
|
### `npm install -g` says "EACCES" / "permission denied"
|
|
169
169
|
|
|
@@ -551,7 +551,7 @@ The user's literal text replaces `verification.ref` in the outreach record, and
|
|
|
551
551
|
|
|
552
552
|
## 7. Upgrade & rotation
|
|
553
553
|
|
|
554
|
-
**Upgrade**: the guided installer pins `@leadbay/mcp@latest`, so each MCP session already picks up the newest published version — just restart the client. (If you hand-pinned a specific version in your config, change it to `@latest` or the version you want, then restart.) **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/
|
|
554
|
+
**Upgrade**: the guided installer pins `@leadbay/mcp@latest`, so each MCP session already picks up the newest published version — just restart the client. (If you hand-pinned a specific version in your config, change it to `@latest` or the version you want, then restart.) **0.3.0 enables composite write tools by default** — see [MIGRATION.md](./MIGRATION.md). See also the [changelog](https://github.com/leadbay/mcp/releases).
|
|
555
555
|
|
|
556
556
|
**Rotate local credential**: re-run `npx -y @leadbay/mcp@latest install --oauth` (or `login --oauth`) — the new credential replaces the old one in your MCP client config.
|
|
557
557
|
|
package/dist/bin.js
CHANGED
|
@@ -7411,8 +7411,9 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
|
|
|
7411
7411
|
created (name + resolved sectors/sizes as chips) and ASK the user to confirm
|
|
7412
7412
|
via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
|
|
7413
7413
|
"yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
|
|
7414
|
-
|
|
7415
|
-
|
|
7414
|
+
(if \`computing_wishlist:true\`, add that leads stream in \u2014 pull in ~30s, don't
|
|
7415
|
+
report "empty"). On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the
|
|
7416
|
+
candidates to pick from.
|
|
7416
7417
|
|
|
7417
7418
|
---
|
|
7418
7419
|
|
|
@@ -7426,6 +7427,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
|
|
|
7426
7427
|
|
|
7427
7428
|
**Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
|
|
7428
7429
|
|
|
7430
|
+
**Leads compute asynchronously.** When the lens has criteria, applying the filter kicks off a backend wishlist rebuild \u2014 the \`created\` result carries \`computing_wishlist:true\`. An immediate \`leadbay_pull_leads\` may read empty for a few seconds while it computes; that lens is *warming up*, not empty. Wait ~30s before the first pull (or offer the user a re-pull). A criteria-less clone inherits the base lens's leads right away (\`computing_wishlist:false\`).
|
|
7431
|
+
|
|
7429
7432
|
WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
|
|
7430
7433
|
|
|
7431
7434
|
WHEN NOT TO USE: to EDIT an existing lens \u2014 use \`leadbay_adjust_audience\` (pass \`lensName\` to target one by name). Not for listing/switching (\`leadbay_my_lenses\`) or topping up (\`leadbay_extend_lens\`).
|
|
@@ -7479,17 +7482,23 @@ User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutual
|
|
|
7479
7482
|
Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
|
|
7480
7483
|
follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
|
|
7481
7484
|
|
|
7482
|
-
|
|
7483
|
-
|
|
7484
|
-
|
|
7485
|
-
|
|
7486
|
-
|
|
7487
|
-
|
|
|
7488
|
-
|
|
7489
|
-
| \`
|
|
7490
|
-
|
|
7491
|
-
|
|
7492
|
-
|
|
7485
|
+
**\`created\` with \`computing_wishlist: true\`** \u2014 the new lens's leads are being
|
|
7486
|
+
(re)computed asynchronously. Do NOT fire an immediate \`leadbay_pull_leads\` and
|
|
7487
|
+
report "empty" \u2014 the lens is warming up, not empty. Tell the user the lens was
|
|
7488
|
+
created and its leads are streaming in, and offer to pull in ~30s.
|
|
7489
|
+
|
|
7490
|
+
| Observation | Suggest | Calls |
|
|
7491
|
+
|-----------------------------------------|------------------------------------------|--------------------------------------------------------|
|
|
7492
|
+
| \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
|
|
7493
|
+
| \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
|
|
7494
|
+
| Lens created, \`computing_wishlist=true\` | "Give it ~30s, then pull leads (the wishlist is still computing)" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` after ~30s |
|
|
7495
|
+
| Lens created (no criteria) | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
|
|
7496
|
+
| Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
|
|
7497
|
+
| Lens created | "Leave it; keep my current lens active" | (no call) |
|
|
7498
|
+
| \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
|
|
7499
|
+
|
|
7500
|
+
If nothing fits, default to "switch to the new lens and pull leads in ~30s
|
|
7501
|
+
(the wishlist may still be computing)" \u2014 never invent a tool that doesn't exist.
|
|
7493
7502
|
`;
|
|
7494
7503
|
leadbay_open_billing_portal = `Generate a one-shot Stripe customer-portal URL. Wraps \`GET /1.6/stripe/portal\` \u2192 \`{url}\`. Returns a fresh Stripe-hosted URL the user can open to manage their existing Leadbay subscription: change plan tier, swap payment method, view invoices. The agent does NOT make subscription changes itself \u2014 it surfaces the URL and lets the user act.
|
|
7495
7504
|
|
|
@@ -7951,7 +7960,8 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
7951
7960
|
|
|
7952
7961
|
## RENDER (quick)
|
|
7953
7962
|
|
|
7954
|
-
3-col markdown table
|
|
7963
|
+
3-col markdown table in the order the tool returns them (the Discover-tab
|
|
7964
|
+
order \u2014 do NOT re-sort by score) \u2014 DO NOT print the numeric
|
|
7955
7965
|
score. Col 1 = inline-code 10-segment bar (\`\u25B0\` firmographic, \`\u2756\` AI
|
|
7956
7966
|
booster cap at the right end of the filled run, \`\u25B1\` empty;
|
|
7957
7967
|
filled=round(score/10), ai=round(avg_boost/3.3)) + \`<br>\` + linked
|
|
@@ -7978,7 +7988,7 @@ The active lens can change between calls (5-min cache + backend \`last_requested
|
|
|
7978
7988
|
|
|
7979
7989
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
7980
7990
|
|
|
7981
|
-
Present the response as a markdown table
|
|
7991
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
7982
7992
|
|
|
7983
7993
|
## Score-bar (10-segment, inline-code wrapped)
|
|
7984
7994
|
|
|
@@ -8084,6 +8094,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
|
|
|
8084
8094
|
| Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
|
|
8085
8095
|
| Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
|
|
8086
8096
|
| \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
|
|
8097
|
+
| Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
|
|
8087
8098
|
| User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
|
|
8088
8099
|
| Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
|
|
8089
8100
|
If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
|
|
@@ -11413,7 +11424,7 @@ async function pollRecordsToTerminal(client, importId, budgetMs, expectedRowCoun
|
|
|
11413
11424
|
}
|
|
11414
11425
|
}
|
|
11415
11426
|
if (!exhaustedPagination) {
|
|
11416
|
-
throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/
|
|
11427
|
+
throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/mcp/issues.`, `GET /imports/${importId}/records`);
|
|
11417
11428
|
}
|
|
11418
11429
|
const snapshot = { total, transient };
|
|
11419
11430
|
const settled = transient === 0;
|
|
@@ -14474,9 +14485,27 @@ function summarise(responses) {
|
|
|
14474
14485
|
return { answered, total, avg_qualification_boost: avg, best_response_excerpt: excerpt };
|
|
14475
14486
|
}
|
|
14476
14487
|
function buildPullLeadsNextSteps(args) {
|
|
14477
|
-
const { leadCount, hasMore, nextPage } = args;
|
|
14478
|
-
if (leadCount <= 0)
|
|
14488
|
+
const { leadCount, hasMore, nextPage, computingWishlist, computingScores } = args;
|
|
14489
|
+
if (leadCount <= 0) {
|
|
14490
|
+
if (computingWishlist || computingScores) {
|
|
14491
|
+
return {
|
|
14492
|
+
question: "This lens is still warming up. What next?",
|
|
14493
|
+
options: [
|
|
14494
|
+
{
|
|
14495
|
+
label: "Re-pull in ~30s",
|
|
14496
|
+
description: "Leads are still being computed for this lens \u2014 wait ~30s, then pull again to see them.",
|
|
14497
|
+
kind: "repull_computing"
|
|
14498
|
+
},
|
|
14499
|
+
{
|
|
14500
|
+
label: "Refine audience",
|
|
14501
|
+
description: "Adjust the lens audience / filters (sector, size, prompt) \u2014 useful if it's coming back sparse.",
|
|
14502
|
+
kind: "refine_audience"
|
|
14503
|
+
}
|
|
14504
|
+
]
|
|
14505
|
+
};
|
|
14506
|
+
}
|
|
14479
14507
|
return null;
|
|
14508
|
+
}
|
|
14480
14509
|
const options = [];
|
|
14481
14510
|
options.push({
|
|
14482
14511
|
label: "Triage board",
|
|
@@ -14574,7 +14603,7 @@ var init_pull_leads = __esm({
|
|
|
14574
14603
|
},
|
|
14575
14604
|
next_steps: {
|
|
14576
14605
|
type: ["object", "null"],
|
|
14577
|
-
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. null only when the batch is empty.",
|
|
14606
|
+
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
14578
14607
|
properties: {
|
|
14579
14608
|
question: { type: "string" },
|
|
14580
14609
|
options: {
|
|
@@ -14665,7 +14694,13 @@ var init_pull_leads = __esm({
|
|
|
14665
14694
|
const hasMore = currentPage < totalPages - 1;
|
|
14666
14695
|
const nextPage = hasMore ? currentPage + 1 : null;
|
|
14667
14696
|
const leadCount = res.items.length;
|
|
14668
|
-
const nextSteps = buildPullLeadsNextSteps({
|
|
14697
|
+
const nextSteps = buildPullLeadsNextSteps({
|
|
14698
|
+
leadCount,
|
|
14699
|
+
hasMore,
|
|
14700
|
+
nextPage,
|
|
14701
|
+
computingWishlist: res.computing_wishlist,
|
|
14702
|
+
computingScores: res.computing_scores
|
|
14703
|
+
});
|
|
14669
14704
|
return withAgentMemoryMeta(client, {
|
|
14670
14705
|
lens: { id: lensId },
|
|
14671
14706
|
leads: res.items.map((lead) => ({
|
|
@@ -21818,6 +21853,10 @@ var init_new_lens = __esm({
|
|
|
21818
21853
|
items: { type: "object" }
|
|
21819
21854
|
},
|
|
21820
21855
|
filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
|
|
21856
|
+
computing_wishlist: {
|
|
21857
|
+
type: "boolean",
|
|
21858
|
+
description: "On 'created': true when the lens has criteria and its wishlist is (re)computing asynchronously \u2014 an immediate leadbay_pull_leads may read empty; wait ~30s. False for a criteria-less clone (it inherits the base lens's leads immediately)."
|
|
21859
|
+
},
|
|
21821
21860
|
message: { type: "string" },
|
|
21822
21861
|
_meta: { type: "object" }
|
|
21823
21862
|
},
|
|
@@ -21924,7 +21963,13 @@ var init_new_lens = __esm({
|
|
|
21924
21963
|
status: "created",
|
|
21925
21964
|
lens: { id: created.id, name: created.name },
|
|
21926
21965
|
filter_applied: merged,
|
|
21927
|
-
|
|
21966
|
+
// Applying the filter queued a backend wishlist refresh (#3833). The lens
|
|
21967
|
+
// leads are (re)computed asynchronously, so an immediate pull_leads can
|
|
21968
|
+
// read empty for a few seconds. Signal that here (flag-independent — the
|
|
21969
|
+
// backend's own computing_wishlist can lag right after create) so the
|
|
21970
|
+
// agent waits ~30s instead of reporting an empty lens.
|
|
21971
|
+
computing_wishlist: hasCriteria,
|
|
21972
|
+
message: hasCriteria ? `Created "${created.name}". Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them (an immediate pull may show an empty lens while the wishlist computes).` : `Created "${created.name}".`,
|
|
21928
21973
|
_meta: { region: client.region }
|
|
21929
21974
|
};
|
|
21930
21975
|
}
|
|
@@ -23223,7 +23268,7 @@ Call \`leadbay_pull_leads\` on the resolved lens. **Capture \`response.lens.id\`
|
|
|
23223
23268
|
|
|
23224
23269
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
23225
23270
|
|
|
23226
|
-
Present the response as a markdown table
|
|
23271
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
23227
23272
|
|
|
23228
23273
|
## Score-bar (10-segment, inline-code wrapped)
|
|
23229
23274
|
|
|
@@ -23422,7 +23467,7 @@ If the prompt's body and the tool's RENDERING appear to conflict, the tool's REN
|
|
|
23422
23467
|
|
|
23423
23468
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
23424
23469
|
|
|
23425
|
-
Present the response as a markdown table
|
|
23470
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
23426
23471
|
|
|
23427
23472
|
## Score-bar (10-segment, inline-code wrapped)
|
|
23428
23473
|
|
|
@@ -23552,6 +23597,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
|
|
|
23552
23597
|
| Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
|
|
23553
23598
|
| Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
|
|
23554
23599
|
| \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
|
|
23600
|
+
| Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
|
|
23555
23601
|
| User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
|
|
23556
23602
|
| Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
|
|
23557
23603
|
If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
|
|
@@ -24066,7 +24112,7 @@ When \`bulk_qualify_leads\` returns, surface results in two parts.
|
|
|
24066
24112
|
|
|
24067
24113
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
24068
24114
|
|
|
24069
|
-
Present the response as a markdown table
|
|
24115
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
24070
24116
|
|
|
24071
24117
|
## Score-bar (10-segment, inline-code wrapped)
|
|
24072
24118
|
|
|
@@ -25174,7 +25220,7 @@ function getCachedUpdateInfo() {
|
|
|
25174
25220
|
function getInFlightCheck() {
|
|
25175
25221
|
return inFlightCheck;
|
|
25176
25222
|
}
|
|
25177
|
-
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/
|
|
25223
|
+
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/mcp/releases/latest";
|
|
25178
25224
|
var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
25179
25225
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
25180
25226
|
var USER_AGENT = "leadbay-mcp-update-check";
|
|
@@ -27721,7 +27767,7 @@ var OAUTH_BASE_URLS = {
|
|
|
27721
27767
|
fr: "https://staging.api.leadbay.app"
|
|
27722
27768
|
}
|
|
27723
27769
|
};
|
|
27724
|
-
var VERSION = "0.23.
|
|
27770
|
+
var VERSION = "0.23.10";
|
|
27725
27771
|
var HELP = `
|
|
27726
27772
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
27727
27773
|
|
|
@@ -27788,7 +27834,7 @@ EXAMPLE Claude Desktop config (~/Library/Application Support/Claude/claude_deskt
|
|
|
27788
27834
|
}
|
|
27789
27835
|
|
|
27790
27836
|
DOCS
|
|
27791
|
-
https://github.com/leadbay/
|
|
27837
|
+
https://github.com/leadbay/mcp#readme
|
|
27792
27838
|
`.trim();
|
|
27793
27839
|
function makeStderrLogger(level) {
|
|
27794
27840
|
const rank = { debug: 0, info: 1, error: 2 };
|
package/dist/http-server.js
CHANGED
|
@@ -88,7 +88,7 @@ Call \`leadbay_pull_leads\` on the resolved lens. **Capture \`response.lens.id\`
|
|
|
88
88
|
|
|
89
89
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
90
90
|
|
|
91
|
-
Present the response as a markdown table
|
|
91
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
92
92
|
|
|
93
93
|
## Score-bar (10-segment, inline-code wrapped)
|
|
94
94
|
|
|
@@ -287,7 +287,7 @@ If the prompt's body and the tool's RENDERING appear to conflict, the tool's REN
|
|
|
287
287
|
|
|
288
288
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
289
289
|
|
|
290
|
-
Present the response as a markdown table
|
|
290
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
291
291
|
|
|
292
292
|
## Score-bar (10-segment, inline-code wrapped)
|
|
293
293
|
|
|
@@ -417,6 +417,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
|
|
|
417
417
|
| Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
|
|
418
418
|
| Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
|
|
419
419
|
| \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
|
|
420
|
+
| Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
|
|
420
421
|
| User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
|
|
421
422
|
| Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
|
|
422
423
|
If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
|
|
@@ -931,7 +932,7 @@ When \`bulk_qualify_leads\` returns, surface results in two parts.
|
|
|
931
932
|
|
|
932
933
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
933
934
|
|
|
934
|
-
Present the response as a markdown table
|
|
935
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
935
936
|
|
|
936
937
|
## Score-bar (10-segment, inline-code wrapped)
|
|
937
938
|
|
|
@@ -8540,8 +8541,9 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
|
|
|
8540
8541
|
created (name + resolved sectors/sizes as chips) and ASK the user to confirm
|
|
8541
8542
|
via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
|
|
8542
8543
|
"yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
|
|
8543
|
-
|
|
8544
|
-
|
|
8544
|
+
(if \`computing_wishlist:true\`, add that leads stream in \u2014 pull in ~30s, don't
|
|
8545
|
+
report "empty"). On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the
|
|
8546
|
+
candidates to pick from.
|
|
8545
8547
|
|
|
8546
8548
|
---
|
|
8547
8549
|
|
|
@@ -8555,6 +8557,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
|
|
|
8555
8557
|
|
|
8556
8558
|
**Does not switch the active lens.** The new lens is created but the user stays on their current one. Offer \`leadbay_my_lenses(switchToLensId=<new id>)\` as a next step if they want to start pulling from it.
|
|
8557
8559
|
|
|
8560
|
+
**Leads compute asynchronously.** When the lens has criteria, applying the filter kicks off a backend wishlist rebuild \u2014 the \`created\` result carries \`computing_wishlist:true\`. An immediate \`leadbay_pull_leads\` may read empty for a few seconds while it computes; that lens is *warming up*, not empty. Wait ~30s before the first pull (or offer the user a re-pull). A criteria-less clone inherits the base lens's leads right away (\`computing_wishlist:false\`).
|
|
8561
|
+
|
|
8558
8562
|
WHEN TO USE: when the user wants a NEW lens. Canonical phrasings: "create a lens called X", "make a new audience for Y", "set up a lens for <sector>".
|
|
8559
8563
|
|
|
8560
8564
|
WHEN NOT TO USE: to EDIT an existing lens \u2014 use \`leadbay_adjust_audience\` (pass \`lensName\` to target one by name). Not for listing/switching (\`leadbay_my_lenses\`) or topping up (\`leadbay_extend_lens\`).
|
|
@@ -8608,17 +8612,23 @@ User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutual
|
|
|
8608
8612
|
Pick the rows that fit. On \`created\`, the switch + pull rows are the natural
|
|
8609
8613
|
follow-ups. On \`ambiguous_sectors\`, the only move is to pick a sector and re-call.
|
|
8610
8614
|
|
|
8611
|
-
|
|
8612
|
-
|
|
8613
|
-
|
|
8614
|
-
|
|
8615
|
-
|
|
8616
|
-
|
|
|
8617
|
-
|
|
8618
|
-
| \`
|
|
8619
|
-
|
|
8620
|
-
|
|
8621
|
-
|
|
8615
|
+
**\`created\` with \`computing_wishlist: true\`** \u2014 the new lens's leads are being
|
|
8616
|
+
(re)computed asynchronously. Do NOT fire an immediate \`leadbay_pull_leads\` and
|
|
8617
|
+
report "empty" \u2014 the lens is warming up, not empty. Tell the user the lens was
|
|
8618
|
+
created and its leads are streaming in, and offer to pull in ~30s.
|
|
8619
|
+
|
|
8620
|
+
| Observation | Suggest | Calls |
|
|
8621
|
+
|-----------------------------------------|------------------------------------------|--------------------------------------------------------|
|
|
8622
|
+
| \`preview\` (not yet created) | "Yes, create this lens" | \`leadbay_new_lens(...same args..., confirm=true)\` |
|
|
8623
|
+
| \`preview\` (not yet created) | "Change the sectors/size first" | (re-ask the user, then \`leadbay_new_lens\` with new args) |
|
|
8624
|
+
| Lens created, \`computing_wishlist=true\` | "Give it ~30s, then pull leads (the wishlist is still computing)" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` after ~30s |
|
|
8625
|
+
| Lens created (no criteria) | "Switch to it and pull leads" | \`leadbay_my_lenses(switchToLensId=<new id>)\` then \`leadbay_pull_leads()\` |
|
|
8626
|
+
| Lens created | "Refine the audience further" | \`leadbay_adjust_audience(lensName=<new name>, ...)\` |
|
|
8627
|
+
| Lens created | "Leave it; keep my current lens active" | (no call) |
|
|
8628
|
+
| \`ambiguous_sectors\` | "Pick the right sector and create" | \`leadbay_new_lens(name=..., sectors=[<chosen id>])\` |
|
|
8629
|
+
|
|
8630
|
+
If nothing fits, default to "switch to the new lens and pull leads in ~30s
|
|
8631
|
+
(the wishlist may still be computing)" \u2014 never invent a tool that doesn't exist.
|
|
8622
8632
|
`;
|
|
8623
8633
|
var leadbay_open_billing_portal = `Generate a one-shot Stripe customer-portal URL. Wraps \`GET /1.6/stripe/portal\` \u2192 \`{url}\`. Returns a fresh Stripe-hosted URL the user can open to manage their existing Leadbay subscription: change plan tier, swap payment method, view invoices. The agent does NOT make subscription changes itself \u2014 it surfaces the URL and lets the user act.
|
|
8624
8634
|
|
|
@@ -9080,7 +9090,8 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
|
9080
9090
|
|
|
9081
9091
|
## RENDER (quick)
|
|
9082
9092
|
|
|
9083
|
-
3-col markdown table
|
|
9093
|
+
3-col markdown table in the order the tool returns them (the Discover-tab
|
|
9094
|
+
order \u2014 do NOT re-sort by score) \u2014 DO NOT print the numeric
|
|
9084
9095
|
score. Col 1 = inline-code 10-segment bar (\`\u25B0\` firmographic, \`\u2756\` AI
|
|
9085
9096
|
booster cap at the right end of the filled run, \`\u25B1\` empty;
|
|
9086
9097
|
filled=round(score/10), ai=round(avg_boost/3.3)) + \`<br>\` + linked
|
|
@@ -9107,7 +9118,7 @@ The active lens can change between calls (5-min cache + backend \`last_requested
|
|
|
9107
9118
|
|
|
9108
9119
|
## RENDERING \u2014 markdown table, three columns, score-bar driven
|
|
9109
9120
|
|
|
9110
|
-
Present the response as a markdown table
|
|
9121
|
+
Present the response as a markdown table **in the exact order the tool returned the leads** \u2014 this is the Discover-tab order (the backend orders by new-today first, then status, then score). Do **not** re-sort the rows (in particular, do NOT re-order by \`score\`); render them top-to-bottom as received so the list matches what the user sees in the Leadbay UI. Exactly three columns. Do not summarize in prose. Do not show the numeric score anywhere.
|
|
9111
9122
|
|
|
9112
9123
|
## Score-bar (10-segment, inline-code wrapped)
|
|
9113
9124
|
|
|
@@ -9213,6 +9224,7 @@ Pick 2\u20133 items below based on what was actually observed in the response. T
|
|
|
9213
9224
|
| Top row has phone but no email | "Show [Contact]'s call details + a 60-second opener" | leadbay_prepare_outreach(leadId) |
|
|
9214
9225
|
| Top row has contacts but no phone/email | "Order contact enrichment to surface email/phone first" | leadbay_enrich_titles(...) or leadbay_prepare_outreach(leadId, enrich:true) |
|
|
9215
9226
|
| \`computing_scores == true\` or \`computing_wishlist == true\` | "Scores are still being computed \u2014 re-pull in ~30s" | leadbay_pull_leads (retry with same lensId) |
|
|
9227
|
+
| Batch is EMPTY and \`computing_wishlist\`/\`computing_scores == true\` (e.g. a just-created lens) | render the \`next_steps\` widget \u2014 it carries "Re-pull in ~30s" (first) + "Refine audience". Do NOT report "no leads": the lens is warming up, not empty | leadbay_pull_leads (retry with same lensId after ~30s) |
|
|
9216
9228
|
| User wants a narrower / wider audience | "Adjust the lens filters (sector / size)" | leadbay_adjust_audience(...) |
|
|
9217
9229
|
| Phase 4 research was run (\`research_lead_by_id\` called) AND top contacts lack direct email/phone | "Enrich contacts on [Lead1], [Lead2] to get direct emails and phone numbers" | leadbay_enrich_contacts(leadId, contactId) \u2014 ONE call per contact (the tool takes a single leadId + contactId, never a list) |
|
|
9218
9230
|
If nothing in the menu applies cleanly, suggest only "pull next page" and "research a specific lead in depth" \u2014 never invent a tool that doesn't exist.
|
|
@@ -12421,7 +12433,7 @@ async function pollRecordsToTerminal(client, importId, budgetMs, expectedRowCoun
|
|
|
12421
12433
|
}
|
|
12422
12434
|
}
|
|
12423
12435
|
if (!exhaustedPagination) {
|
|
12424
|
-
throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/
|
|
12436
|
+
throw client.makeError("IMPORT_PAGINATION_RUNAWAY", `Records pagination exceeded ${maxPagesPerPoll} pages`, `importId=${importId}. Please file a bug at https://github.com/leadbay/mcp/issues.`, `GET /imports/${importId}/records`);
|
|
12425
12437
|
}
|
|
12426
12438
|
const snapshot = { total, transient };
|
|
12427
12439
|
const settled = transient === 0;
|
|
@@ -15151,9 +15163,27 @@ function summarise(responses) {
|
|
|
15151
15163
|
return { answered, total, avg_qualification_boost: avg, best_response_excerpt: excerpt };
|
|
15152
15164
|
}
|
|
15153
15165
|
function buildPullLeadsNextSteps(args) {
|
|
15154
|
-
const { leadCount, hasMore, nextPage } = args;
|
|
15155
|
-
if (leadCount <= 0)
|
|
15166
|
+
const { leadCount, hasMore, nextPage, computingWishlist, computingScores } = args;
|
|
15167
|
+
if (leadCount <= 0) {
|
|
15168
|
+
if (computingWishlist || computingScores) {
|
|
15169
|
+
return {
|
|
15170
|
+
question: "This lens is still warming up. What next?",
|
|
15171
|
+
options: [
|
|
15172
|
+
{
|
|
15173
|
+
label: "Re-pull in ~30s",
|
|
15174
|
+
description: "Leads are still being computed for this lens \u2014 wait ~30s, then pull again to see them.",
|
|
15175
|
+
kind: "repull_computing"
|
|
15176
|
+
},
|
|
15177
|
+
{
|
|
15178
|
+
label: "Refine audience",
|
|
15179
|
+
description: "Adjust the lens audience / filters (sector, size, prompt) \u2014 useful if it's coming back sparse.",
|
|
15180
|
+
kind: "refine_audience"
|
|
15181
|
+
}
|
|
15182
|
+
]
|
|
15183
|
+
};
|
|
15184
|
+
}
|
|
15156
15185
|
return null;
|
|
15186
|
+
}
|
|
15157
15187
|
const options = [];
|
|
15158
15188
|
options.push({
|
|
15159
15189
|
label: "Triage board",
|
|
@@ -15245,7 +15275,7 @@ var pullLeads = {
|
|
|
15245
15275
|
},
|
|
15246
15276
|
next_steps: {
|
|
15247
15277
|
type: ["object", "null"],
|
|
15248
|
-
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. null only when the batch is empty.",
|
|
15278
|
+
description: "Ready-made NEXT STEPS for the host's choice widget. Each option has a SHORT `label` (\u22645 words, fits AskUserQuestion's label cap on Claude cowork/Claude Code) and a full `description`. For AskUserQuestion (cowork/Claude Code) pass each option as {label, description}. For ask_user_input_v0 (Claude chat/ChatGPT, string-only options) use the `description` as the option string. Use these VERBATIM, in order \u2014 do NOT re-derive, reword, or render as prose when a widget tool exists. options[0] is the artifact offer (build the lead triage board) whenever the batch is non-empty. When the batch is empty but the lens is still computing (computing_wishlist/computing_scores true), this carries a 'Re-pull in ~30s' option (kind:repull_computing) plus 'Refine audience' \u2014 render the widget so the user waits rather than seeing 'no leads.' null only when the batch is empty AND nothing is computing (a genuinely empty / over-narrow lens).",
|
|
15249
15279
|
properties: {
|
|
15250
15280
|
question: { type: "string" },
|
|
15251
15281
|
options: {
|
|
@@ -15336,7 +15366,13 @@ var pullLeads = {
|
|
|
15336
15366
|
const hasMore = currentPage < totalPages - 1;
|
|
15337
15367
|
const nextPage = hasMore ? currentPage + 1 : null;
|
|
15338
15368
|
const leadCount = res.items.length;
|
|
15339
|
-
const nextSteps = buildPullLeadsNextSteps({
|
|
15369
|
+
const nextSteps = buildPullLeadsNextSteps({
|
|
15370
|
+
leadCount,
|
|
15371
|
+
hasMore,
|
|
15372
|
+
nextPage,
|
|
15373
|
+
computingWishlist: res.computing_wishlist,
|
|
15374
|
+
computingScores: res.computing_scores
|
|
15375
|
+
});
|
|
15340
15376
|
return withAgentMemoryMeta(client, {
|
|
15341
15377
|
lens: { id: lensId },
|
|
15342
15378
|
leads: res.items.map((lead) => ({
|
|
@@ -21610,6 +21646,10 @@ var newLens = {
|
|
|
21610
21646
|
items: { type: "object" }
|
|
21611
21647
|
},
|
|
21612
21648
|
filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
|
|
21649
|
+
computing_wishlist: {
|
|
21650
|
+
type: "boolean",
|
|
21651
|
+
description: "On 'created': true when the lens has criteria and its wishlist is (re)computing asynchronously \u2014 an immediate leadbay_pull_leads may read empty; wait ~30s. False for a criteria-less clone (it inherits the base lens's leads immediately)."
|
|
21652
|
+
},
|
|
21613
21653
|
message: { type: "string" },
|
|
21614
21654
|
_meta: { type: "object" }
|
|
21615
21655
|
},
|
|
@@ -21716,7 +21756,13 @@ var newLens = {
|
|
|
21716
21756
|
status: "created",
|
|
21717
21757
|
lens: { id: created.id, name: created.name },
|
|
21718
21758
|
filter_applied: merged,
|
|
21719
|
-
|
|
21759
|
+
// Applying the filter queued a backend wishlist refresh (#3833). The lens
|
|
21760
|
+
// leads are (re)computed asynchronously, so an immediate pull_leads can
|
|
21761
|
+
// read empty for a few seconds. Signal that here (flag-independent — the
|
|
21762
|
+
// backend's own computing_wishlist can lag right after create) so the
|
|
21763
|
+
// agent waits ~30s instead of reporting an empty lens.
|
|
21764
|
+
computing_wishlist: hasCriteria,
|
|
21765
|
+
message: hasCriteria ? `Created "${created.name}". Leads stream in asynchronously \u2014 call leadbay_pull_leads in ~30s to see them (an immediate pull may show an empty lens while the wishlist computes).` : `Created "${created.name}".`,
|
|
21720
21766
|
_meta: { region: client.region }
|
|
21721
21767
|
};
|
|
21722
21768
|
}
|
|
@@ -22771,7 +22817,7 @@ function getCachedUpdateInfo() {
|
|
|
22771
22817
|
function getInFlightCheck() {
|
|
22772
22818
|
return inFlightCheck;
|
|
22773
22819
|
}
|
|
22774
|
-
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/
|
|
22820
|
+
var RELEASES_LATEST_URL = "https://api.github.com/repos/leadbay/mcp/releases/latest";
|
|
22775
22821
|
var CHECK_THROTTLE_MS = 24 * 60 * 60 * 1e3;
|
|
22776
22822
|
var FETCH_TIMEOUT_MS = 5e3;
|
|
22777
22823
|
var USER_AGENT = "leadbay-mcp-update-check";
|
|
@@ -24016,7 +24062,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
24016
24062
|
}
|
|
24017
24063
|
|
|
24018
24064
|
// src/http-server.ts
|
|
24019
|
-
var VERSION = true ? "0.23.
|
|
24065
|
+
var VERSION = true ? "0.23.10" : "0.0.0-dev";
|
|
24020
24066
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
24021
24067
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
24022
24068
|
var sseSessions = /* @__PURE__ */ new Map();
|
|
@@ -24058,6 +24104,24 @@ function applyCors(c) {
|
|
|
24058
24104
|
c.header("Access-Control-Allow-Origin", "*");
|
|
24059
24105
|
c.header("Access-Control-Expose-Headers", "WWW-Authenticate");
|
|
24060
24106
|
}
|
|
24107
|
+
var DEFAULT_ALLOWED_ORIGINS = [
|
|
24108
|
+
"https://claude.ai",
|
|
24109
|
+
"https://claude.com",
|
|
24110
|
+
"https://chatgpt.com"
|
|
24111
|
+
];
|
|
24112
|
+
function allowedOrigins() {
|
|
24113
|
+
const extra = (process.env.LEADBAY_MCP_ALLOWED_ORIGINS ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
24114
|
+
return /* @__PURE__ */ new Set([...DEFAULT_ALLOWED_ORIGINS, ...extra]);
|
|
24115
|
+
}
|
|
24116
|
+
function rejectForeignOrigin(c) {
|
|
24117
|
+
const origin = c.req.header("origin");
|
|
24118
|
+
if (!origin) return null;
|
|
24119
|
+
const allowed = allowedOrigins();
|
|
24120
|
+
allowed.add(requestOrigin(c));
|
|
24121
|
+
if (allowed.has(origin)) return null;
|
|
24122
|
+
applyCors(c);
|
|
24123
|
+
return c.json({ error: "forbidden origin" }, 403);
|
|
24124
|
+
}
|
|
24061
24125
|
function servePrm(c, resourcePath) {
|
|
24062
24126
|
applyCors(c);
|
|
24063
24127
|
c.header("Cache-Control", "public, max-age=3600");
|
|
@@ -24096,6 +24160,8 @@ app.use("/mcp", MCP_BODY_LIMIT);
|
|
|
24096
24160
|
app.use("/fr/mcp", MCP_BODY_LIMIT);
|
|
24097
24161
|
app.use("/messages", MCP_BODY_LIMIT);
|
|
24098
24162
|
async function handleStreamable(c, resourcePath) {
|
|
24163
|
+
const foreign = rejectForeignOrigin(c);
|
|
24164
|
+
if (foreign) return foreign;
|
|
24099
24165
|
const token = extractBearer(c.req.header("authorization"));
|
|
24100
24166
|
const resolved = await resolveClientFromToken(token);
|
|
24101
24167
|
if (resolved.authState === "missing" || resolved.authState === "expired") {
|
|
@@ -24141,6 +24207,8 @@ async function handleStreamable(c, resourcePath) {
|
|
|
24141
24207
|
app.all("/mcp", (c) => handleStreamable(c, "/mcp"));
|
|
24142
24208
|
app.all("/fr/mcp", (c) => handleStreamable(c, "/fr/mcp"));
|
|
24143
24209
|
async function handleSse(c, resourcePath) {
|
|
24210
|
+
const foreign = rejectForeignOrigin(c);
|
|
24211
|
+
if (foreign) return foreign;
|
|
24144
24212
|
const token = extractBearer(c.req.header("authorization"));
|
|
24145
24213
|
const resolved = await resolveClientFromToken(token);
|
|
24146
24214
|
if (resolved.authState === "missing" || resolved.authState === "expired") {
|
|
@@ -24162,6 +24230,8 @@ async function handleSse(c, resourcePath) {
|
|
|
24162
24230
|
app.get("/sse", (c) => handleSse(c, "/sse"));
|
|
24163
24231
|
app.get("/fr/sse", (c) => handleSse(c, "/fr/sse"));
|
|
24164
24232
|
app.post("/messages", async (c) => {
|
|
24233
|
+
const foreign = rejectForeignOrigin(c);
|
|
24234
|
+
if (foreign) return foreign;
|
|
24165
24235
|
const sessionId = c.req.query("sessionId");
|
|
24166
24236
|
if (!sessionId) {
|
|
24167
24237
|
return c.json({ error: "missing sessionId" }, 400);
|
|
@@ -1764,7 +1764,7 @@ var init_installer_gui = __esm({
|
|
|
1764
1764
|
init_install_dxt();
|
|
1765
1765
|
init_install_shared();
|
|
1766
1766
|
init_oauth();
|
|
1767
|
-
VERSION = true ? "0.23.
|
|
1767
|
+
VERSION = true ? "0.23.10" : "0.0.0-dev";
|
|
1768
1768
|
MESSAGES = {
|
|
1769
1769
|
en: {
|
|
1770
1770
|
installer: {
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.23.
|
|
3
|
+
"version": "0.23.10",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|
|
@@ -61,13 +61,13 @@
|
|
|
61
61
|
],
|
|
62
62
|
"repository": {
|
|
63
63
|
"type": "git",
|
|
64
|
-
"url": "git+https://github.com/leadbay/
|
|
64
|
+
"url": "git+https://github.com/leadbay/mcp.git",
|
|
65
65
|
"directory": "packages/mcp"
|
|
66
66
|
},
|
|
67
67
|
"bugs": {
|
|
68
|
-
"url": "https://github.com/leadbay/
|
|
68
|
+
"url": "https://github.com/leadbay/mcp/issues"
|
|
69
69
|
},
|
|
70
|
-
"homepage": "https://github.com/leadbay/
|
|
70
|
+
"homepage": "https://github.com/leadbay/mcp/tree/main/packages/mcp#readme",
|
|
71
71
|
"author": "Leadbay",
|
|
72
72
|
"license": "MIT"
|
|
73
73
|
}
|