@leadbay/mcp 0.21.0 → 0.21.1

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,9 @@
1
1
  # Changelog — @leadbay/mcp
2
2
 
3
+ ## 0.21.1 — 2026-06-16
4
+
5
+ - **CSV import no longer 400s on a lead status the agent didn't uppercase** (product#3745): `leadbay_import_leads` / `leadbay_import_and_qualify` forwarded `default_status` / `statuses` values verbatim to `POST /imports/{id}/update_mappings`, whose backend `MappingsPayload` decodes them as the strict, case-sensitive `LeadStatus` enum (`DEFAULT, INBOUND, UNWANTED, WANTED, LOST, WON`). A value like "Won" failed deserialization and the whole call 400'd with an opaque "JSON deserialization error" before any record committed — JM hit this trying to tag 179 companies as Won. The MCP now owns the canonical set and enforces it before sending: status values are matched case-insensitively to their enum member ("Won" → "WON"), an empty default means no default, and a genuinely unknown status returns a clear `IMPORT_INVALID_STATUS` error naming the valid values instead of an opaque backend 400. The two tools' input schemas now declare the enum.
6
+
3
7
  ## 0.21.0 — 2026-06-16
4
8
 
5
9
  - **Hosted MCP now triggers OAuth sign-in in Claude Desktop / ChatGPT** (remote custom connectors): the Fly endpoint was not an OAuth-compliant resource server, so a remote client had nothing to discover, never prompted the user to sign in, and then surfaced a host-side "needs auth / token expired" state even though the user never had a token. The server now implements the MCP authorization spec (RFC 9728): it serves OAuth 2.0 Protected Resource Metadata at `/.well-known/oauth-protected-resource[/<resource>]` and answers an unauthenticated (or invalid/expired) `POST /mcp` with `401` + `WWW-Authenticate: Bearer ... resource_metadata="…"`. The client discovers the Leadbay authorization server (the existing regional backend used by `login --oauth`) and runs the browser sign-in. Tool requests auto-probe both regions, so a valid token routes correctly and a stale one re-prompts instead of erroring.
package/dist/bin.js CHANGED
@@ -10828,6 +10828,13 @@ function coerceCell(client, v, path) {
10828
10828
  return String(v);
10829
10829
  throw client.makeError("IMPORT_INVALID_CELL_TYPE", `Cell at ${path} is ${Array.isArray(v) ? "an array" : typeof v}, expected string|number|boolean|null`, `Convert the value to a string before passing.`, "POST /imports");
10830
10830
  }
10831
+ function enforceLeadStatus(client, raw, path) {
10832
+ const canonical = String(raw).trim().toUpperCase();
10833
+ if (!LEAD_STATUS_SET.has(canonical)) {
10834
+ throw client.makeError("IMPORT_INVALID_STATUS", `${path} ${JSON.stringify(raw)} is not a valid lead status`, `Use one of ${LEAD_STATUSES.join(", ")} (case-insensitive), or omit it.`, "POST /imports");
10835
+ }
10836
+ return canonical;
10837
+ }
10831
10838
  function prepareDomainsMode(client, inputs) {
10832
10839
  const validInputs = [];
10833
10840
  const malformedDomains = [];
@@ -10949,6 +10956,12 @@ function prepareRecordsMode(client, records, mappings, customFieldCatalog) {
10949
10956
  if (normDomain && !byDomain.has(normDomain))
10950
10957
  byDomain.set(normDomain, idx);
10951
10958
  });
10959
+ const statuses = {};
10960
+ for (const [cell, status] of Object.entries(mappings.statuses ?? {})) {
10961
+ statuses[cell] = enforceLeadStatus(client, status, `mappings.statuses[${JSON.stringify(cell)}]`);
10962
+ }
10963
+ const rawDefault = mappings.default_status;
10964
+ const default_status = rawDefault == null || String(rawDefault).trim() === "" ? null : enforceLeadStatus(client, rawDefault, "mappings.default_status");
10952
10965
  return {
10953
10966
  mode: "records",
10954
10967
  validInputs,
@@ -10958,8 +10971,8 @@ function prepareRecordsMode(client, records, mappings, customFieldCatalog) {
10958
10971
  header,
10959
10972
  mappings: {
10960
10973
  fields: { ...normalizedFields },
10961
- statuses: mappings.statuses ?? {},
10962
- default_status: mappings.default_status ?? null
10974
+ statuses,
10975
+ default_status
10963
10976
  }
10964
10977
  };
10965
10978
  }
@@ -11334,7 +11347,7 @@ async function runImportInBackground(client, prep, uploadedChunks, opts, ctx, ha
11334
11347
  })();
11335
11348
  }, 0);
11336
11349
  }
11337
- var CHUNK_SIZE, POLL_INTERVAL_MS2, DEFAULT_PER_PHASE_BUDGET_MS, DEFAULT_TOTAL_BUDGET_MS, STABILIZATION_POLLS, MAX_COLUMN_NAME_LEN, RESERVED_COLUMN_RE, CUSTOM_FIELD_RE, IMPORT_RESOLVER_FIELDS, PUBLIC_MAILBOX_DOMAINS, importLeads;
11350
+ var CHUNK_SIZE, POLL_INTERVAL_MS2, DEFAULT_PER_PHASE_BUDGET_MS, DEFAULT_TOTAL_BUDGET_MS, STABILIZATION_POLLS, MAX_COLUMN_NAME_LEN, RESERVED_COLUMN_RE, CUSTOM_FIELD_RE, IMPORT_RESOLVER_FIELDS, PUBLIC_MAILBOX_DOMAINS, LEAD_STATUSES, LEAD_STATUS_SET, importLeads;
11338
11351
  var init_import_leads = __esm({
11339
11352
  "../core/dist/composite/import-leads.js"() {
11340
11353
  "use strict";
@@ -11379,6 +11392,15 @@ var init_import_leads = __esm({
11379
11392
  "163.com",
11380
11393
  "126.com"
11381
11394
  ]);
11395
+ LEAD_STATUSES = [
11396
+ "DEFAULT",
11397
+ "INBOUND",
11398
+ "UNWANTED",
11399
+ "WANTED",
11400
+ "LOST",
11401
+ "WON"
11402
+ ];
11403
+ LEAD_STATUS_SET = new Set(LEAD_STATUSES);
11382
11404
  importLeads = {
11383
11405
  name: "leadbay_import_leads",
11384
11406
  annotations: {
@@ -11437,11 +11459,12 @@ var init_import_leads = __esm({
11437
11459
  },
11438
11460
  statuses: {
11439
11461
  type: "object",
11440
- description: "Optional status string mapping (rarely needed). Defaults to {}."
11462
+ description: `Optional map of raw CSV status-cell text \u2192 lead status (rarely needed). Keys are the verbatim cell strings; values must be one of ${LEAD_STATUSES.join(", ")} (case-insensitive). Defaults to {}.`
11441
11463
  },
11442
11464
  default_status: {
11443
11465
  type: ["string", "null"],
11444
- description: "Optional default status. Defaults to null."
11466
+ enum: [...LEAD_STATUSES, null],
11467
+ description: `Optional default lead status applied to rows without an explicit status. One of ${LEAD_STATUSES.join(", ")} (case-insensitive). Defaults to null.`
11445
11468
  }
11446
11469
  },
11447
11470
  required: ["fields"]
@@ -17499,8 +17522,15 @@ var init_import_and_qualify = __esm({
17499
17522
  type: "object",
17500
17523
  description: "Ergonomic shorthand: `{CsvColumn: <number-id>}` or `{CsvColumn: '<field-name>'}` for custom-field mappings. Resolved against /crm/custom_fields catalog."
17501
17524
  },
17502
- statuses: { type: "object", description: "Optional status string mapping." },
17503
- default_status: { type: ["string", "null"], description: "Optional default status." }
17525
+ statuses: {
17526
+ type: "object",
17527
+ description: `Optional map of raw CSV status-cell text \u2192 lead status. Values must be one of ${LEAD_STATUSES.join(", ")} (case-insensitive); keys are the verbatim cell strings.`
17528
+ },
17529
+ default_status: {
17530
+ type: ["string", "null"],
17531
+ enum: [...LEAD_STATUSES, null],
17532
+ description: `Optional default lead status. One of ${LEAD_STATUSES.join(", ")} (case-insensitive).`
17533
+ }
17504
17534
  },
17505
17535
  // mappings has a closed shape (fields/custom_fields/statuses/default_status).
17506
17536
  // Inner objects (fields, custom_fields, statuses) keep open shapes
@@ -25928,7 +25958,7 @@ var OAUTH_BASE_URLS = {
25928
25958
  fr: "https://staging.api.leadbay.app"
25929
25959
  }
25930
25960
  };
25931
- var VERSION = "0.21.0";
25961
+ var VERSION = "0.21.1";
25932
25962
  var HELP = `
25933
25963
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
25934
25964
 
@@ -11524,6 +11524,22 @@ function coerceCell(client, v, path) {
11524
11524
  return String(v);
11525
11525
  throw client.makeError("IMPORT_INVALID_CELL_TYPE", `Cell at ${path} is ${Array.isArray(v) ? "an array" : typeof v}, expected string|number|boolean|null`, `Convert the value to a string before passing.`, "POST /imports");
11526
11526
  }
11527
+ var LEAD_STATUSES = [
11528
+ "DEFAULT",
11529
+ "INBOUND",
11530
+ "UNWANTED",
11531
+ "WANTED",
11532
+ "LOST",
11533
+ "WON"
11534
+ ];
11535
+ var LEAD_STATUS_SET = new Set(LEAD_STATUSES);
11536
+ function enforceLeadStatus(client, raw, path) {
11537
+ const canonical = String(raw).trim().toUpperCase();
11538
+ if (!LEAD_STATUS_SET.has(canonical)) {
11539
+ throw client.makeError("IMPORT_INVALID_STATUS", `${path} ${JSON.stringify(raw)} is not a valid lead status`, `Use one of ${LEAD_STATUSES.join(", ")} (case-insensitive), or omit it.`, "POST /imports");
11540
+ }
11541
+ return canonical;
11542
+ }
11527
11543
  function prepareDomainsMode(client, inputs) {
11528
11544
  const validInputs = [];
11529
11545
  const malformedDomains = [];
@@ -11645,6 +11661,12 @@ function prepareRecordsMode(client, records, mappings, customFieldCatalog) {
11645
11661
  if (normDomain && !byDomain.has(normDomain))
11646
11662
  byDomain.set(normDomain, idx);
11647
11663
  });
11664
+ const statuses = {};
11665
+ for (const [cell, status] of Object.entries(mappings.statuses ?? {})) {
11666
+ statuses[cell] = enforceLeadStatus(client, status, `mappings.statuses[${JSON.stringify(cell)}]`);
11667
+ }
11668
+ const rawDefault = mappings.default_status;
11669
+ const default_status = rawDefault == null || String(rawDefault).trim() === "" ? null : enforceLeadStatus(client, rawDefault, "mappings.default_status");
11648
11670
  return {
11649
11671
  mode: "records",
11650
11672
  validInputs,
@@ -11654,8 +11676,8 @@ function prepareRecordsMode(client, records, mappings, customFieldCatalog) {
11654
11676
  header,
11655
11677
  mappings: {
11656
11678
  fields: { ...normalizedFields },
11657
- statuses: mappings.statuses ?? {},
11658
- default_status: mappings.default_status ?? null
11679
+ statuses,
11680
+ default_status
11659
11681
  }
11660
11682
  };
11661
11683
  }
@@ -12048,11 +12070,12 @@ var importLeads = {
12048
12070
  },
12049
12071
  statuses: {
12050
12072
  type: "object",
12051
- description: "Optional status string mapping (rarely needed). Defaults to {}."
12073
+ description: `Optional map of raw CSV status-cell text \u2192 lead status (rarely needed). Keys are the verbatim cell strings; values must be one of ${LEAD_STATUSES.join(", ")} (case-insensitive). Defaults to {}.`
12052
12074
  },
12053
12075
  default_status: {
12054
12076
  type: ["string", "null"],
12055
- description: "Optional default status. Defaults to null."
12077
+ enum: [...LEAD_STATUSES, null],
12078
+ description: `Optional default lead status applied to rows without an explicit status. One of ${LEAD_STATUSES.join(", ")} (case-insensitive). Defaults to null.`
12056
12079
  }
12057
12080
  },
12058
12081
  required: ["fields"]
@@ -17627,8 +17650,15 @@ var importAndQualify = {
17627
17650
  type: "object",
17628
17651
  description: "Ergonomic shorthand: `{CsvColumn: <number-id>}` or `{CsvColumn: '<field-name>'}` for custom-field mappings. Resolved against /crm/custom_fields catalog."
17629
17652
  },
17630
- statuses: { type: "object", description: "Optional status string mapping." },
17631
- default_status: { type: ["string", "null"], description: "Optional default status." }
17653
+ statuses: {
17654
+ type: "object",
17655
+ description: `Optional map of raw CSV status-cell text \u2192 lead status. Values must be one of ${LEAD_STATUSES.join(", ")} (case-insensitive); keys are the verbatim cell strings.`
17656
+ },
17657
+ default_status: {
17658
+ type: ["string", "null"],
17659
+ enum: [...LEAD_STATUSES, null],
17660
+ description: `Optional default lead status. One of ${LEAD_STATUSES.join(", ")} (case-insensitive).`
17661
+ }
17632
17662
  },
17633
17663
  // mappings has a closed shape (fields/custom_fields/statuses/default_status).
17634
17664
  // Inner objects (fields, custom_fields, statuses) keep open shapes
@@ -22461,7 +22491,7 @@ function parseWriteEnv(env = process.env) {
22461
22491
  }
22462
22492
 
22463
22493
  // src/http-server.ts
22464
- var VERSION = true ? "0.21.0" : "0.0.0-dev";
22494
+ var VERSION = true ? "0.21.1" : "0.0.0-dev";
22465
22495
  var PORT = Number(process.env.PORT ?? 8080);
22466
22496
  var HOST = process.env.HOST ?? "0.0.0.0";
22467
22497
  var sseSessions = /* @__PURE__ */ new Map();
@@ -1466,7 +1466,7 @@ var init_installer_gui = __esm({
1466
1466
  init_install_dxt();
1467
1467
  init_install_shared();
1468
1468
  init_oauth();
1469
- VERSION = "0.21.0";
1469
+ VERSION = "0.21.1";
1470
1470
  PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
1471
1471
  sessions = /* @__PURE__ */ new Map();
1472
1472
  OAUTH_BASE_URLS = {
@@ -873,7 +873,7 @@ async function oauthLogin(opts) {
873
873
  }
874
874
 
875
875
  // installer/installer-gui.ts
876
- var VERSION = "0.21.0";
876
+ var VERSION = "0.21.1";
877
877
  var PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
878
878
  var sessions = /* @__PURE__ */ new Map();
879
879
  var OAUTH_BASE_URLS = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leadbay/mcp",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
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",