fix(mcp,core): #3504 silent npx exit + dead URL hints + score contracts - #15
Merged
Conversation
…e contracts Three failure modes a user hits before they get any work done, fixed in one pass. 1. #3504 — `npx -y @leadbay/mcp` silently exits 0 on Node 25. The `isEntrypoint` check compared `process.argv[1]` (the npx shim symlink) to `import.meta.url` (the resolved `dist/bin.js`); they never match, so `main()` never runs and the MCP host sees a dead connection with no diagnostic. Both sides now go through `realpathSync` so the symlink collapses. Reproduce-then-fix verified with a new `test/smoke/npx-entrypoint.test.ts` regression guard. 2. Dead `app.leadbay.ai` URLs in error strings and READMEs replaced with the real recovery commands (`leadbay-mcp install`, `leadbay-mcp login`). Hints now include `--region <us|fr>` because the CLI refuses without it (anti- cross-region credential-leak guard) — earlier draft would have dead-ended the user a second time. 3. `avg_score_0_to_10` on `pull_leads` / `bulk_qualify_leads` was lying: per- question AI agent scores are discrete boosts (-10/0/10/20), not 0-10 averages. Renamed to `avg_qualification_boost` with JSDoc on the underlying `AiAgentResponse.score` interface explaining the real contract. Documented `score` (ICP similarity) vs `ai_agent_lead_score` (deep-AI qualification boost) — they're combined and normalized server-side to 0-100. Live smoke harness extended with composite-level checks + optional thinking- model judge that writes a redacted report to `.context/`. Token-first via `LEADBAY_TEST_TOKEN`; macOS Keychain fallback (refuses plaintext password env var, on purpose). Versions: core 0.2.0→0.2.1 (private), leadclaw + mcp 0.2.1→0.2.2 (openclaw plugin manifest synced). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Both branches independently bumped mcp/leadclaw to 0.2.2. Combined into a single 0.2.2 release: this branch's npx silent-exit fix + dead URL hints + score-contract correction PLUS main's mental-model docs (SERVER_INSTRUCTIONS inbox/two-scoring-layers/daily-rhythm + per-tool description rewrites + the new openclaw.plugin.json description). Single coherent release rather than two near-simultaneous tags. Conflicts resolved: - packages/leadclaw/CHANGELOG.md: merged 0.2.2 entries. - packages/leadclaw/openclaw.plugin.json: kept main's longer description. - packages/mcp/CHANGELOG.md: merged 0.2.2 entries. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
ArtyETH06
added a commit
that referenced
this pull request
Jun 8, 2026
… import pipeline
Live testing through Claude Desktop's actual server binary revealed the
import-pipeline add path (leadbay_import_and_qualify) returns 401 on the
test account — POST /imports is rejected even with a valid token, and the
agent surfaced it as a misleading "LEADBAY_TOKEN is no longer valid" error.
The direct endpoint the web UI uses — POST /leads/{id}/contacts — works with
the SAME token (verified 200, contact created with its id). So:
- New leadbay_add_contact composite write tool wrapping POST
/leads/{id}/contacts. One call, no import/qualify quota, accepts
first/last name + optional job_title/linkedin_page/email/phone. Pairs with
leadbay_remove_contact (POST /contacts/{id}/archive).
- Revert the import_and_qualify routing/recipe changes — the add-contact
intent now routes to leadbay_add_contact instead. (Kept one incidental fix:
a stale leadbay_research_lead -> leadbay_research_lead_by_id reference.)
- research_lead_by_id anti-trigger now points at leadbay_add_contact.
- index / _composite-file-names / TOOLS_WITH_ROUTING registration,
WORKFLOWS row #15 + contract updated, new unit test.
Verified live through Desktop's bin.js + token: add -> contact created (no
401); remove -> archived; company back to 0 contacts. Suite green
(core 324, promptforge 16, mcp 366).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
milstan
pushed a commit
that referenced
this pull request
Jun 10, 2026
…e / remove / pin / unpin) (#93) * feat(contacts): add + remove a contact on a company from the conversation Closes the workflow gap from leadbay/product#3703: reps prospecting through Claude hit companies with no suggested contacts and left for Attio + Full Enrich because there was no way to add a contact in-conversation. Diagnosis: the backend already supported creating a contact (the import pipeline's CONTACT_* fields, incl. CONTACT_LINKEDIN, create an org contact keyed to a parent company). The gap was routing/discoverability, not a missing endpoint. Changes: - Route the add-a-contact intent to leadbay_import_and_qualify: routing frontmatter + a single-contact-add recipe in its description, a reciprocal anti-trigger from research_lead_by_id, a WORKFLOWS row + eval contract. - Fix the recipe: LEAD_NAME is required even when LEADBAY_ID is passed (the import rejects the row otherwise — caught by a live probe). - New leadbay_remove_contact composite write tool — the undo for the add path. Wraps the backend's POST /contacts/{id}/archive (the same call the web UI's contact-delete fires; keyed by the contact's own id, soft-delete). Description + routing, index/_composite-file-names/TOOLS_WITH_ROUTING registration, WORKFLOWS row #16 + contract, unit test. Verified live against a test account: add -> contact appears on the company; remove -> contact gone. Full suite green (core 320, promptforge 16, mcp 366). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(contacts): add via direct POST /leads/{id}/contacts, not the import pipeline Live testing through Claude Desktop's actual server binary revealed the import-pipeline add path (leadbay_import_and_qualify) returns 401 on the test account — POST /imports is rejected even with a valid token, and the agent surfaced it as a misleading "LEADBAY_TOKEN is no longer valid" error. The direct endpoint the web UI uses — POST /leads/{id}/contacts — works with the SAME token (verified 200, contact created with its id). So: - New leadbay_add_contact composite write tool wrapping POST /leads/{id}/contacts. One call, no import/qualify quota, accepts first/last name + optional job_title/linkedin_page/email/phone. Pairs with leadbay_remove_contact (POST /contacts/{id}/archive). - Revert the import_and_qualify routing/recipe changes — the add-contact intent now routes to leadbay_add_contact instead. (Kept one incidental fix: a stale leadbay_research_lead -> leadbay_research_lead_by_id reference.) - research_lead_by_id anti-trigger now points at leadbay_add_contact. - index / _composite-file-names / TOOLS_WITH_ROUTING registration, WORKFLOWS row #15 + contract updated, new unit test. Verified live through Desktop's bin.js + token: add -> contact created (no 401); remove -> archived; company back to 0 contacts. Suite green (core 324, promptforge 16, mcp 366). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(contacts): add pin + unpin contact tools Round out the contact-management surface alongside add/remove. Both endpoints verified live (204) and the web UI uses them. - leadbay_pin_contact → POST /contacts/{id}/pin (mark a person as priority) - leadbay_unpin_contact → POST /contacts/{id}/unpin (clear the pin) One tool per file (pin-contact.ts / unpin-contact.ts) to satisfy the COMPOSITE_FILE_TOOL_NAMES ↔ directory round-trip audit. Descriptions + routing, index / _composite-file-names / TOOLS_WITH_ROUTING registration, WORKFLOWS rows #17/#18 + contracts, unit tests. Verified live through Desktop's bin.js + token: add → pin → unpin → remove all return success. Suite green (core 329, promptforge 16, mcp 366). Note: contact MODIFY has no working backend endpoint (PUT/PATCH /contacts/{id} both 404), so edit-in-place is intentionally NOT included — needs a backend route first. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(contacts): add leadbay_update_contact — edit a contact in place Completes the contact-management surface (create/remove/pin/unpin/update). Backend route cracked from the error messages: POST /contacts/{id}/update with a SNAKE_CASE body that MUST include first_name + last_name (the endpoint validates the full identity and 400s "invalid contact" on a partial body; camelCase 400s "JSON deserialization error"). Edits in place — same id. - leadbay_update_contact composite write tool (contact_id + first/last name required; job_title/linkedin_page/email/phone optional). - Description + routing, index / _composite-file-names / TOOLS_WITH_ROUTING registration, WORKFLOWS row #19 + contract, unit test. Verified live through Desktop's bin.js + token: add (OLD TITLE) → update → title becomes SVP Engineering on the same contact id → remove. Suite green (core 332, promptforge 16, mcp 366). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(contacts): correct remove-contact routing + allow null clears on update (review) Two pre-landing review findings on PR #93: - [P2] remove-contact anti-trigger + prose still pointed "add a contact" at leadbay_import_and_qualify (the old import path that 401s / burns quota). Repointed the anti-trigger to leadbay_add_contact and fixed the stale "undo for import_and_qualify" prose to reference leadbay_add_contact. - [P2] update-contact input schema declared optional fields as type:"string" only, so MCP schema validation would reject a valid "clear this email/title/phone/linkedin" (null) update before execute ran — even though the params type + execute already forward null. Changed those four fields to type:["string","null"]. Verified live: POST /contacts/{id}/update with email:null / job_title:null returns 200 and clears the fields. New test (update-contact-null-clear.test.ts): asserts null is sent on the wire for a clear, untouched fields are omitted, and the schema declares the nullable types. Suite green (core 334, promptforge 16, mcp 366). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(contacts): fix stale remove-contact docstring → leadbay_add_contact (review) The remove-contact docstring still called the add path leadbay_import_and_qualify; the add path was refactored to the direct leadbay_add_contact endpoint. User-facing description was already fixed in review; this code comment was the last stale reference. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(contacts): move single-call contact tools composite/ → tools/ (review) Milan: the 5 contact tools are each a single API call, so by the repo convention (CLAUDE.md: "single relay call → granular") they're granular, not composite. Moved add/update/remove/pin/unpin-contact from composite/ to tools/ (source, tests, and promptforge templates). They stay registered in compositeWriteTools — same pattern as likeLead/dislikeLead/addNote: granular-shaped tools kept on the DEFAULT write surface so reps manage contacts in-conversation without LEADBAY_MCP_ADVANCED. Dropped from COMPOSITE_FILE_TOOL_NAMES (no longer in composite/), so they also shed the _triggered_by mandate — consistent with the other granular writes. No behavior change: generated tool descriptions are byte-identical (promptforge keys on `name:`, not directory). Suite green (core 353, promptforge 16, mcp 370; composite-file-names round-trip audit passes). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Three failure modes a user hits before getting any work done, fixed in one pass:
npx -y @leadbay/mcpexited 0 silently on Node 25 becauseisEntrypointcompared the npx shim symlink to the resolveddist/bin.js. Both sides now go throughrealpathSync. Newtest/smoke/npx-entrypoint.test.tsis the regression guard (reproduce-then-fix verified).app.leadbay.aiURLs in error strings + READMEs with the real recovery commands. Hints now include--region <us\|fr>because the CLI refuses without it (anti-cross-region credential-leak guard).avg_score_0_to_10was lying — per-question AI agent scores are discrete boosts (-10/0/10/20). Renamed toavg_qualification_boost. JSDoc onAiAgentResponse.score,LeadPayload.score, andLeadPayload.ai_agent_lead_scorenow documents the real contract (similarity + qualification boost, combined and normalized server-side to 0-100).Live smoke harness extended with composite-level checks + optional thinking-model judge writing a redacted report to
.context/. Token-first viaLEADBAY_TEST_TOKEN; macOS Keychain fallback (refuses plaintext password env var). Versions: core 0.2.0→0.2.1 (private), leadclaw + mcp 0.2.1→0.2.2 (openclaw plugin manifest synced).Test plan
pnpm -r typecheckgreen across core / leadclaw / mcppnpm test— unit suite passespnpm --filter @leadbay/mcp test:smoke—npx-entrypoint.test.tspasses;live.test.tspasses against real account (LEADBAY_TEST_TOKEN + ANTHROPIC_API_KEY)npx --helptranscript🤖 Generated with Claude Code