@leadbay/mcp 0.32.4 → 0.32.5

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 CHANGED
@@ -1,5 +1,34 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.32.5 — 2026-09-01
4
+
5
+ `leadbay_update_contact` returned `NOT_FOUND` / 404 on 100% of the calls ever
6
+ made to it (product#3997). Not a missing route and not a backend bug.
7
+
8
+ Leadbay holds contacts in two id namespaces — `org_contacts` (the org's own
9
+ directory) and `paid_contacts` (enrichment results) — with separate models,
10
+ separate DAOs and separate ids. `POST /contacts/{id}/update` resolves
11
+ `orgContacts.findById` only, so a paid id can only ever 404.
12
+
13
+ `research_lead_by_id` merges `/leads/{id}/enrich/contacts` and
14
+ `/leads/{id}/contacts` into `contacts.reachable` / `contacts.candidates`, split
15
+ by **whether the person is messagable right now, not by which endpoint they came
16
+ from**. That split is right for outreach and wrong for identity: an enriched
17
+ paid contact sits in the same list as an org contact, and the agent had nothing
18
+ to distinguish them by. Roughly half the ids we handed out were unusable by a
19
+ tool the server instructions tell the agent to call after every outreach.
20
+
21
+ - The `source` field (`"org"` / `"paid"`) was already on the wire but undeclared
22
+ and unexplained. It is now documented in `research_lead_by_id`'s output schema
23
+ as the thing that decides whether an id can be passed to
24
+ `leadbay_update_contact`.
25
+ - `leadbay_update_contact`'s description states which namespace it accepts,
26
+ what a 404 means, and routes a correction to a paid contact through
27
+ `leadbay_add_contact` instead — the enrichment row is a provider's answer and
28
+ is not ours to edit.
29
+ - New: `research-contact-source-provenance.test.ts`, covering the case the
30
+ reachability split hides — an enriched paid contact and an org contact in the
31
+ SAME `reachable` list, distinguished only by `source`.
3
32
  ## 0.32.4 — 2026-09-01
4
33
 
5
34
  `leadbay_account_status.notifications` was permanently `[]` on the hosted server
@@ -27,7 +56,6 @@ answer, and the ledger is not called.
27
56
  responses.** Reviving it would mean a `GET /notifications` per tool call to
28
57
  decorate every response. The cost lands on the check-in entry point only,
29
58
  which is where the daily-rhythm channel is actually read.
30
-
31
59
  ## 0.32.0 — 2026-09-01
32
60
 
33
61
  A poll-budget timeout stops being an error (product#4007). The import wizard's
package/dist/bin.js CHANGED
@@ -10289,9 +10289,9 @@ Trigger phrases: "update this contact", "fix this contact's title", "change thei
10289
10289
 
10290
10290
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
10291
10291
 
10292
- Do NOT use for: "add a new contact to this company" \u2192 \`leadbay_add_contact\`; "remove / delete this contact" \u2192 \`leadbay_remove_contact\`; "get email/phone for a contact (enrichment)" \u2192 \`leadbay_enrich_titles\`.
10292
+ Do NOT use for: "add a new contact to this company" \u2192 \`leadbay_add_contact\`; "remove / delete this contact" \u2192 \`leadbay_remove_contact\`; "get email/phone for a contact (enrichment)" \u2192 \`leadbay_enrich_titles\`; "fix an enriched contact's details" \u2192 \`leadbay_add_contact\`.
10293
10293
 
10294
- Prefer when: user wants to change details on an EXISTING contact \u2014 pass that contact's own \`contact_id\` plus first_name + last_name (required) and the fields to change
10294
+ Prefer when: user wants to change details on a contact that is in their own directory (\`source: "org"\`) \u2014 pass that contact's own \`contact_id\` plus first_name + last_name (required) and the fields to change
10295
10295
 
10296
10296
  Examples that SHOULD invoke this tool:
10297
10297
  - "Update Jane's title to SVP Engineering."
@@ -10311,7 +10311,18 @@ One-line confirmation naming the contact and what changed. No table.
10311
10311
 
10312
10312
  Edit an existing contact in place \u2014 change their \`job_title\`, \`linkedin_page\`, \`email\`, \`phone_number\`, or name.
10313
10313
 
10314
- Pass the contact's **own** \`contact_id\` (the \`id\` field from \`leadbay_research_lead_by_id\` or a contacts list) \u2014 **not** the parent lead id.
10314
+ Pass the contact's **own** \`contact_id\` \u2014 **not** the parent lead id.
10315
+
10316
+ **Only your organization's own directory contacts can be edited.** Leadbay holds contacts in two separate id namespaces and this endpoint resolves one of them:
10317
+
10318
+ | \`source\` on the contact | What it is | Editable here |
10319
+ |---|---|---|
10320
+ | \`"org"\` | A row in your organization's contact directory \u2014 added by you or your team, or promoted from an import | **yes** |
10321
+ | \`"paid"\` | An enrichment result bought from a data provider | **no** \u2014 returns \`NOT_FOUND\` / 404 |
10322
+
10323
+ \`leadbay_research_lead_by_id\` returns both, merged into \`contacts.reachable\` / \`contacts.candidates\` and split by whether the person is messagable right now \u2014 **not** by which namespace they came from. So read \`source\` on the contact before calling this. If it is \`"paid"\` and the user wants different details on record, add the corrected person with \`leadbay_add_contact\` instead; the enrichment row is a provider's answer and is not ours to edit.
10324
+
10325
+ A 404 from this tool almost always means a \`"paid"\` id was passed. Re-read the contact, check \`source\`, and do not retry the same id.
10315
10326
 
10316
10327
  **\`first_name\` + \`last_name\` are required even on an edit.** The backend validates the full contact identity and rejects a partial body (\`invalid contact\`). So pass the contact's *current* first/last name even when you're only changing the title \u2014 read the current values via \`leadbay_research_lead_by_id\` first if you don't have them.
10317
10328
 
@@ -19141,7 +19152,7 @@ var init_research_lead_by_id = __esm({
19141
19152
  },
19142
19153
  contacts: {
19143
19154
  type: "object",
19144
- description: "Two-tier contact set, partitioned by reachability \u2014 agent-friendly framing of the backend's paid-vs-org split. `reachable`: contacts with an email or phone right now (org-directory entries that ship with channels, PLUS paid contacts whose enrichment has completed). The agent can message these without buying enrichment. `candidates`: paid-contact entries WITHOUT resolved channels yet \u2014 typically LinkedIn URL only, `enrichment_done: false`. The agent must call leadbay_enrich_titles (or leadbay_prepare_outreach with enrich:true) before these become messagable.",
19155
+ description: 'Two-tier contact set, partitioned by reachability \u2014 agent-friendly framing of the backend\'s paid-vs-org split. `reachable`: contacts with an email or phone right now (org-directory entries that ship with channels, PLUS paid contacts whose enrichment has completed). The agent can message these without buying enrichment. `candidates`: paid-contact entries WITHOUT resolved channels yet \u2014 typically LinkedIn URL only, `enrichment_done: false`. The agent must call leadbay_enrich_titles (or leadbay_prepare_outreach with enrich:true) before these become messagable. Every contact in both lists carries `source`: `"org"` means it is a row in your organization\'s own contact directory, `"paid"` means it came from enrichment. The two are separate id namespaces on the backend, so only a `source:"org"` id can be passed to leadbay_update_contact / leadbay_remove_contact \u2014 a `"paid"` id returns NOT_FOUND there.',
19145
19156
  properties: {
19146
19157
  reachable: { type: "array", items: { type: "object" } },
19147
19158
  candidates: { type: "array", items: { type: "object" } }
@@ -32961,7 +32972,7 @@ var OAUTH_BASE_URLS = {
32961
32972
  fr: "https://staging.api.leadbay.app"
32962
32973
  }
32963
32974
  };
32964
- var VERSION = "0.32.4";
32975
+ var VERSION = "0.32.5";
32965
32976
  var HELP = `
32966
32977
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
32967
32978
 
@@ -12682,9 +12682,9 @@ Trigger phrases: "update this contact", "fix this contact's title", "change thei
12682
12682
 
12683
12683
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
12684
12684
 
12685
- Do NOT use for: "add a new contact to this company" \u2192 \`leadbay_add_contact\`; "remove / delete this contact" \u2192 \`leadbay_remove_contact\`; "get email/phone for a contact (enrichment)" \u2192 \`leadbay_enrich_titles\`.
12685
+ Do NOT use for: "add a new contact to this company" \u2192 \`leadbay_add_contact\`; "remove / delete this contact" \u2192 \`leadbay_remove_contact\`; "get email/phone for a contact (enrichment)" \u2192 \`leadbay_enrich_titles\`; "fix an enriched contact's details" \u2192 \`leadbay_add_contact\`.
12686
12686
 
12687
- Prefer when: user wants to change details on an EXISTING contact \u2014 pass that contact's own \`contact_id\` plus first_name + last_name (required) and the fields to change
12687
+ Prefer when: user wants to change details on a contact that is in their own directory (\`source: "org"\`) \u2014 pass that contact's own \`contact_id\` plus first_name + last_name (required) and the fields to change
12688
12688
 
12689
12689
  Examples that SHOULD invoke this tool:
12690
12690
  - "Update Jane's title to SVP Engineering."
@@ -12704,7 +12704,18 @@ One-line confirmation naming the contact and what changed. No table.
12704
12704
 
12705
12705
  Edit an existing contact in place \u2014 change their \`job_title\`, \`linkedin_page\`, \`email\`, \`phone_number\`, or name.
12706
12706
 
12707
- Pass the contact's **own** \`contact_id\` (the \`id\` field from \`leadbay_research_lead_by_id\` or a contacts list) \u2014 **not** the parent lead id.
12707
+ Pass the contact's **own** \`contact_id\` \u2014 **not** the parent lead id.
12708
+
12709
+ **Only your organization's own directory contacts can be edited.** Leadbay holds contacts in two separate id namespaces and this endpoint resolves one of them:
12710
+
12711
+ | \`source\` on the contact | What it is | Editable here |
12712
+ |---|---|---|
12713
+ | \`"org"\` | A row in your organization's contact directory \u2014 added by you or your team, or promoted from an import | **yes** |
12714
+ | \`"paid"\` | An enrichment result bought from a data provider | **no** \u2014 returns \`NOT_FOUND\` / 404 |
12715
+
12716
+ \`leadbay_research_lead_by_id\` returns both, merged into \`contacts.reachable\` / \`contacts.candidates\` and split by whether the person is messagable right now \u2014 **not** by which namespace they came from. So read \`source\` on the contact before calling this. If it is \`"paid"\` and the user wants different details on record, add the corrected person with \`leadbay_add_contact\` instead; the enrichment row is a provider's answer and is not ours to edit.
12717
+
12718
+ A 404 from this tool almost always means a \`"paid"\` id was passed. Re-read the contact, check \`source\`, and do not retry the same id.
12708
12719
 
12709
12720
  **\`first_name\` + \`last_name\` are required even on an edit.** The backend validates the full contact identity and rejects a partial body (\`invalid contact\`). So pass the contact's *current* first/last name even when you're only changing the title \u2014 read the current values via \`leadbay_research_lead_by_id\` first if you don't have them.
12710
12721
 
@@ -20921,7 +20932,7 @@ var researchLeadById = {
20921
20932
  },
20922
20933
  contacts: {
20923
20934
  type: "object",
20924
- description: "Two-tier contact set, partitioned by reachability \u2014 agent-friendly framing of the backend's paid-vs-org split. `reachable`: contacts with an email or phone right now (org-directory entries that ship with channels, PLUS paid contacts whose enrichment has completed). The agent can message these without buying enrichment. `candidates`: paid-contact entries WITHOUT resolved channels yet \u2014 typically LinkedIn URL only, `enrichment_done: false`. The agent must call leadbay_enrich_titles (or leadbay_prepare_outreach with enrich:true) before these become messagable.",
20935
+ description: 'Two-tier contact set, partitioned by reachability \u2014 agent-friendly framing of the backend\'s paid-vs-org split. `reachable`: contacts with an email or phone right now (org-directory entries that ship with channels, PLUS paid contacts whose enrichment has completed). The agent can message these without buying enrichment. `candidates`: paid-contact entries WITHOUT resolved channels yet \u2014 typically LinkedIn URL only, `enrichment_done: false`. The agent must call leadbay_enrich_titles (or leadbay_prepare_outreach with enrich:true) before these become messagable. Every contact in both lists carries `source`: `"org"` means it is a row in your organization\'s own contact directory, `"paid"` means it came from enrichment. The two are separate id namespaces on the backend, so only a `source:"org"` id can be passed to leadbay_update_contact / leadbay_remove_contact \u2014 a `"paid"` id returns NOT_FOUND there.',
20925
20936
  properties: {
20926
20937
  reachable: { type: "array", items: { type: "object" } },
20927
20938
  candidates: { type: "array", items: { type: "object" } }
@@ -29547,7 +29558,7 @@ function parseWriteEnv(env = process.env) {
29547
29558
  }
29548
29559
 
29549
29560
  // src/http-server.ts
29550
- var VERSION = true ? "0.32.4" : "0.0.0-dev";
29561
+ var VERSION = true ? "0.32.5" : "0.0.0-dev";
29551
29562
  var PORT = Number(process.env.PORT ?? 8080);
29552
29563
  var HOST = process.env.HOST ?? "0.0.0.0";
29553
29564
  var logger = {
@@ -1804,7 +1804,7 @@ var init_installer_gui = __esm({
1804
1804
  init_install_dxt();
1805
1805
  init_install_shared();
1806
1806
  init_oauth();
1807
- VERSION = true ? "0.32.4" : "0.0.0-dev";
1807
+ VERSION = true ? "0.32.5" : "0.0.0-dev";
1808
1808
  MESSAGES = {
1809
1809
  en: {
1810
1810
  installer: {
@@ -1067,7 +1067,7 @@ async function oauthLogin(opts) {
1067
1067
  }
1068
1068
 
1069
1069
  // installer/installer-gui.ts
1070
- var VERSION = true ? "0.32.4" : "0.0.0-dev";
1070
+ var VERSION = true ? "0.32.5" : "0.0.0-dev";
1071
1071
  var MESSAGES = {
1072
1072
  en: {
1073
1073
  installer: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.32.4",
3
+ "version": "0.32.5",
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",