@leadbay/mcp 0.23.4 → 0.23.6

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.
@@ -6897,7 +6897,7 @@ This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible
6897
6897
  `;
6898
6898
  var leadbay_adjust_audience = `## WHEN TO USE
6899
6899
 
6900
- Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>".
6900
+ Trigger phrases: "narrow the audience to <sector>", "add <sector> to my <name> lens", "remove <sector> from this lens", "only show me companies of <size>", "stop including <sector>", "broaden this lens to also include <sector>", "restrict this lens to <city/d\xE9partement/r\xE9gion/state>", "only companies in <place>", "exclude <region> from this lens".
6901
6901
 
6902
6902
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
6903
6903
 
@@ -6908,7 +6908,7 @@ Prefer when: user wants to change an EXISTING lens's sectors/sizes. If the user
6908
6908
  Examples that SHOULD invoke this tool:
6909
6909
  - "Add fintech to my Joinery lens."
6910
6910
  - "Narrow my audience to manufacturing companies, 50\u2013500 people."
6911
- - "Stop including retail in this lens."
6911
+ - "Restrict my rep's lens to Indre-et-Loire."
6912
6912
 
6913
6913
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
6914
6914
  - "Create a lens called Joinery for fintech."
@@ -6917,9 +6917,10 @@ Examples that should NOT invoke this tool (sound similar, route elsewhere):
6917
6917
 
6918
6918
  ## RENDER (quick)
6919
6919
 
6920
- On \`applied\`: confirm the lens edited (name) + the sectors/sizes added as
6921
- chips. On \`ambiguous_sectors\` / \`ambiguous_lens\` / \`lens_not_found\`: surface
6922
- the candidates and ask the user to pick, then re-call with the id/exact name.
6920
+ On \`applied\`: confirm the lens edited (name) + the sectors/sizes/locations
6921
+ added as chips. On \`ambiguous_sectors\` / \`ambiguous_locations\` /
6922
+ \`ambiguous_lens\` / \`lens_not_found\`: surface the candidates and ask the user
6923
+ to pick, then re-call with the id/exact name.
6923
6924
 
6924
6925
  ---
6925
6926
 
@@ -6927,7 +6928,9 @@ Restrict (or expand) the lens audience by sector / size. Free-text sectors are a
6927
6928
 
6928
6929
  **Targeting a lens \u2014 READ THIS.** By default this edits the user's ACTIVE lens. **If the user names a lens** ("add fintech to my **Joinery** lens", "in my Nordics lens, exclude retail"), you MUST pass \`lensName\` with that name (\`lensName:"Joinery"\`). Do NOT silently edit the active lens when a different one was named \u2014 that corrupts the wrong audience and is a top friction source. The name resolves against the user's lenses (case-insensitive, exact then unique-substring); it is edit-only and does NOT change which lens is active. An unmatched name returns \`status:"lens_not_found"\` with the lens list, and a name matching several returns \`status:"ambiguous_lens"\` with the candidates \u2014 surface them and re-call with the exact \`lensName\` or a \`lensId\`. Use \`leadbay_my_lenses\` if the user first wants to SEE or SWITCH lenses. To CREATE a brand-new lens, use \`leadbay_new_lens\` \u2014 not this tool.
6929
6930
 
6930
- WHEN TO USE: when the user wants to see different kinds of leads (sector / size / etc.).
6931
+ **Geography \u2014 scope a sales territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, \`["Austin"]\`, or admin-area ids) to restrict the lens to a region, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level \u2014 city, county, *d\xE9partement*, *r\xE9gion*, state, country. Place names go in \`locations\`, **never** in \`sectors\` or \`refine_prompt\`. Unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates \u2014 surface them and re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`location_ids\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`location_ids\`, which would include the area the user asked to exclude). The returned \`message\` names the right param per text. This is how a director scopes a rep's territory and then asks for net-new accounts there.
6932
+
6933
+ WHEN TO USE: when the user wants to see different kinds of leads (sector / size / geography / etc.).
6931
6934
 
6932
6935
  WHEN NOT TO USE: to refine BEYOND firmographics \u2014 that's leadbay_refine_prompt.
6933
6936
 
@@ -8475,7 +8478,7 @@ invent a tool that doesn't exist.
8475
8478
  `;
8476
8479
  var leadbay_new_lens = `## WHEN TO USE
8477
8480
 
8478
- Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>".
8481
+ Trigger phrases: "create a lens", "create a new lens called <name>", "create a lens specialized in/into <X>", "make me a new audience for <X>", "set up a lens for <sector>", "new lens named <name>", "I want a lens just for <X>", "create a lens for net-new accounts in <place>", "a lens scoped to <territory>".
8479
8482
 
8480
8483
  **Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
8481
8484
 
@@ -8486,7 +8489,7 @@ Prefer when: user wants a brand-new lens (create/make/set up, often 'specialized
8486
8489
  Examples that SHOULD invoke this tool:
8487
8490
  - "Create a lens called Joinery for the fintech sector."
8488
8491
  - "Make me a new audience for healthcare companies, 30\u2013300 people."
8489
- - "Set up a new lens named Nordics SaaS."
8492
+ - "Create a lens for net-new accounts in Indre-et-Loire."
8490
8493
 
8491
8494
  Examples that should NOT invoke this tool (sound similar, route elsewhere):
8492
8495
  - "Add fintech to my Joinery lens."
@@ -8499,7 +8502,8 @@ On \`preview\` (default \u2014 NOTHING created yet): show the lens that WILL be
8499
8502
  created (name + resolved sectors/sizes as chips) and ASK the user to confirm
8500
8503
  via ask_user_input_v0 ("Create this lens?" / "Change something"). Only on
8501
8504
  "yes" re-call with confirm:true. On \`created\`: confirm "Created **<name>**."
8502
- On \`ambiguous_sectors\`: surface the candidate sectors to pick from.
8505
+ On \`ambiguous_sectors\` / \`ambiguous_locations\`: surface the candidates to
8506
+ pick from.
8503
8507
 
8504
8508
  ---
8505
8509
 
@@ -8509,6 +8513,8 @@ Create a brand-new lens (saved audience) and apply its sector/size criteria. Clo
8509
8513
 
8510
8514
  **Sectors resolve first.** Free-text \`sectors\`/\`exclude_sectors\` are auto-resolved against the taxonomy. If any don't resolve, the tool returns \`status:"ambiguous_sectors"\` with the candidates and **does NOT create the lens** \u2014 so re-calling after picking the right sector won't leave orphan half-built lenses. To discover valid sector labels up front, use \`leadbay_list_sectors\`.
8511
8515
 
8516
+ **Geography \u2014 scope a territory.** Pass \`locations\` (free text like \`["Indre-et-Loire"]\`, \`["Bavaria"]\`, or admin-area ids) to scope the lens to a sales territory, and \`exclude_locations\` to carve one out. Free text auto-resolves via \`/geo/search\` across every admin level (city / county / *d\xE9partement* / *r\xE9gion* / state / country). Like sectors, locations resolve BEFORE the lens is created \u2014 unresolved/ambiguous text returns \`status:"ambiguous_locations"\` with candidates and **does NOT create the lens**. Re-call the chosen id via the SAME axis it came from: an INCLUDE pick \u2192 \`locations\`; an EXCLUDE pick \u2192 \`exclude_locations\` (**NOT** \`locations\`, which would include the area the user asked to exclude). This is how a director spins up a lens for a rep's zone to surface net-new accounts there.
8517
+
8512
8518
  **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.
8513
8519
 
8514
8520
  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>".
@@ -20415,7 +20421,7 @@ async function resolveLensByName(client, name) {
20415
20421
  return { ok: false, reason: "ambiguous", matches: partial };
20416
20422
  return { ok: false, reason: "not_found", lenses: all };
20417
20423
  }
20418
- function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
20424
+ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes, toAddLocations = [], toExcludeLocations = []) {
20419
20425
  const items = current?.lens_filter?.items ?? [];
20420
20426
  const item = items[0] ?? { criteria: [] };
20421
20427
  const criteria = item.criteria ? [...item.criteria] : [];
@@ -20447,6 +20453,34 @@ function mergeFilter(current, toAddSectors, toExcludeSectors, sizes) {
20447
20453
  });
20448
20454
  }
20449
20455
  }
20456
+ if (toAddLocations.length > 0) {
20457
+ const idx = criteria.findIndex((c) => c.type === "location_ids" && !c.is_excluded);
20458
+ if (idx >= 0) {
20459
+ const cur = criteria[idx];
20460
+ const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toAddLocations]));
20461
+ criteria[idx] = { ...cur, locations: merged };
20462
+ } else {
20463
+ criteria.push({
20464
+ type: "location_ids",
20465
+ is_excluded: false,
20466
+ locations: toAddLocations
20467
+ });
20468
+ }
20469
+ }
20470
+ if (toExcludeLocations.length > 0) {
20471
+ const idx = criteria.findIndex((c) => c.type === "location_ids" && c.is_excluded);
20472
+ if (idx >= 0) {
20473
+ const cur = criteria[idx];
20474
+ const merged = Array.from(/* @__PURE__ */ new Set([...cur.locations ?? [], ...toExcludeLocations]));
20475
+ criteria[idx] = { ...cur, locations: merged };
20476
+ } else {
20477
+ criteria.push({
20478
+ type: "location_ids",
20479
+ is_excluded: true,
20480
+ locations: toExcludeLocations
20481
+ });
20482
+ }
20483
+ }
20450
20484
  if (sizes && sizes.length > 0) {
20451
20485
  const normalizedSizes = sizes.map((s) => ({
20452
20486
  min: s.min ?? 0,
@@ -20507,6 +20541,21 @@ var adjustAudience = {
20507
20541
  },
20508
20542
  description: "Company size buckets, e.g. [{min:30,max:300}]"
20509
20543
  },
20544
+ locations: {
20545
+ type: "array",
20546
+ items: { type: "string" },
20547
+ description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria', 'Austin']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Place names go HERE, never in sectors/keywords."
20548
+ },
20549
+ location_ids: {
20550
+ type: "array",
20551
+ items: { type: "string" },
20552
+ description: "Explicit admin-area ids (skips /geo/search resolution)"
20553
+ },
20554
+ exclude_locations: {
20555
+ type: "array",
20556
+ items: { type: "string" },
20557
+ description: "Locations to exclude (free text or ids)"
20558
+ },
20510
20559
  lensId: { type: "number", description: "Lens id (escape hatch)" },
20511
20560
  lensName: {
20512
20561
  type: "string",
@@ -20525,17 +20574,22 @@ var adjustAudience = {
20525
20574
  },
20526
20575
  outputSchema: {
20527
20576
  type: "object",
20528
- description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
20577
+ description: "Return shapes: 'applied' on success; 'ambiguous_sectors' when free-text sectors matched multiple candidates (re-call with sector_ids); 'ambiguous_locations' when free-text locations didn't resolve to one area \u2014 re-call with the chosen id via the SAME axis it came from (an include pick \u2192 location_ids; an EXCLUDE pick \u2192 exclude_locations, NOT location_ids, which would include it); 'lens_not_found' / 'ambiguous_lens' when a lensName didn't resolve to exactly one lens (re-call with lensId or an exact lensName).",
20529
20578
  properties: {
20530
20579
  status: {
20531
20580
  type: "string",
20532
- description: "'applied', 'ambiguous_sectors', 'lens_not_found', or 'ambiguous_lens'."
20581
+ description: "'applied', 'ambiguous_sectors', 'ambiguous_locations', 'lens_not_found', or 'ambiguous_lens'."
20533
20582
  },
20534
20583
  sector_ambiguities: {
20535
20584
  type: "array",
20536
20585
  description: "Per ambiguous text: {sector_text, matches:[{id, name, score}]}. Agent picks an id and re-calls.",
20537
20586
  items: { type: "object" }
20538
20587
  },
20588
+ location_ambiguities: {
20589
+ type: "array",
20590
+ description: "On 'ambiguous_locations': per text {location_text, matches:[{id, name, country, level, score}]}. Agent picks an id and re-calls via the SAME axis the text came from \u2014 an include text \u2192 location_ids; a text from exclude_locations \u2192 exclude_locations (NOT location_ids, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
20591
+ items: { type: "object" }
20592
+ },
20539
20593
  lenses: {
20540
20594
  type: "array",
20541
20595
  description: "On 'lens_not_found': the user's lenses [{id, name}] to pick from.",
@@ -20619,9 +20673,45 @@ var adjustAudience = {
20619
20673
  message: parts.join(" ")
20620
20674
  };
20621
20675
  }
20676
+ const includeLocTexts = [
20677
+ ...params.locations ?? [],
20678
+ ...params.location_ids ?? []
20679
+ ];
20680
+ const excludeLocTexts = params.exclude_locations ?? [];
20681
+ const includeLocRes = await resolveLocations(client, includeLocTexts);
20682
+ const excludeLocRes = await resolveLocations(client, excludeLocTexts);
20683
+ const locAmbiguities = [
20684
+ ...includeLocRes.ambiguities,
20685
+ ...excludeLocRes.ambiguities
20686
+ ];
20687
+ if (locAmbiguities.length > 0) {
20688
+ const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
20689
+ const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
20690
+ const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
20691
+ const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
20692
+ const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
20693
+ const parts = [];
20694
+ if (incNone.length > 0) {
20695
+ parts.push(`Couldn't find a location matching ${quote(incNone)}. Ask the user to rephrase, then re-call with location_ids=...`);
20696
+ }
20697
+ if (incMatch.length > 0) {
20698
+ parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with location_ids=...`);
20699
+ }
20700
+ if (excNone.length > 0) {
20701
+ parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Ask the user to rephrase, then re-call with exclude_locations=... (the chosen id).`);
20702
+ }
20703
+ if (excMatch.length > 0) {
20704
+ parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with exclude_locations=... (the chosen id) \u2014 NOT location_ids, which would include it.`);
20705
+ }
20706
+ return {
20707
+ status: "ambiguous_locations",
20708
+ location_ambiguities: locAmbiguities,
20709
+ message: parts.join(" ")
20710
+ };
20711
+ }
20622
20712
  const lens = await client.request("GET", `/lenses/${startingLensId}`);
20623
20713
  const currentFilter = await client.request("GET", `/lenses/${startingLensId}/filter`);
20624
- const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes);
20714
+ const merged = mergeFilter(currentFilter, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
20625
20715
  const mergedBody = filterWriteBody(merged);
20626
20716
  const isDefault = lens.is_default || lens.default;
20627
20717
  const isUserLevel = lens.user_id != null;
@@ -21344,6 +21434,16 @@ var newLens = {
21344
21434
  },
21345
21435
  description: "Company size buckets, e.g. [{min:30,max:300}]."
21346
21436
  },
21437
+ locations: {
21438
+ type: "array",
21439
+ items: { type: "string" },
21440
+ description: "Geographic scope \u2014 free text (e.g. ['Indre-et-Loire', 'Bavaria']) or admin-area ids. Auto-resolved via /geo/search across all admin levels (city / county / d\xE9partement / r\xE9gion / state / country). Scopes the lens to a sales territory."
21441
+ },
21442
+ exclude_locations: {
21443
+ type: "array",
21444
+ items: { type: "string" },
21445
+ description: "Locations to exclude \u2014 free text or ids."
21446
+ },
21347
21447
  base: {
21348
21448
  type: "number",
21349
21449
  description: "Lens id to clone from. Defaults to the active/default lens."
@@ -21359,12 +21459,12 @@ var newLens = {
21359
21459
  },
21360
21460
  outputSchema: {
21361
21461
  type: "object",
21362
- description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' when free-text sectors didn't resolve (re-call with sector ids \u2014 the lens was NOT created).",
21462
+ description: "'preview' (default, NOTHING created \u2014 confirm with the user then re-call with confirm:true); 'created' on success; 'ambiguous_sectors' / 'ambiguous_locations' when free-text sectors / locations didn't resolve (re-call with ids \u2014 the lens was NOT created).",
21363
21463
  properties: {
21364
- status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', or 'orphan_created' (filter write failed + cleanup failed)." },
21464
+ status: { type: "string", description: "'preview', 'created', 'ambiguous_sectors', 'ambiguous_locations', or 'orphan_created' (filter write failed + cleanup failed)." },
21365
21465
  will_create: {
21366
21466
  type: "object",
21367
- description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes}. Nothing has been written yet."
21467
+ description: "On 'preview': what WILL be created \u2014 {name, description, sectors, exclude_sectors, sizes, locations, exclude_locations}. Nothing has been written yet."
21368
21468
  },
21369
21469
  filter_preview: { type: "object", description: "On 'preview': the FilterPayload that would be applied." },
21370
21470
  lens: {
@@ -21376,6 +21476,11 @@ var newLens = {
21376
21476
  description: "On 'ambiguous_sectors': per text {sector_text, matches:[{id,name,score}]}.",
21377
21477
  items: { type: "object" }
21378
21478
  },
21479
+ location_ambiguities: {
21480
+ type: "array",
21481
+ description: "On 'ambiguous_locations': per text {location_text, matches:[{id,name,country,level,score}]}. Re-call the chosen id via the SAME axis the text came from \u2014 an include text \u2192 locations; a text from exclude_locations \u2192 exclude_locations (NOT locations, which would include the area the user asked to exclude). The `message` field names the correct param per text.",
21482
+ items: { type: "object" }
21483
+ },
21379
21484
  filter_applied: { type: "object", description: "On 'created': the FilterPayload POSTed to the new lens." },
21380
21485
  message: { type: "string" },
21381
21486
  _meta: { type: "object" }
@@ -21402,7 +21507,38 @@ var newLens = {
21402
21507
  message: parts.join(" ")
21403
21508
  };
21404
21509
  }
21405
- const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes);
21510
+ const includeLocRes = await resolveLocations(client, params.locations ?? []);
21511
+ const excludeLocRes = await resolveLocations(client, params.exclude_locations ?? []);
21512
+ const locAmbiguities = [
21513
+ ...includeLocRes.ambiguities,
21514
+ ...excludeLocRes.ambiguities
21515
+ ];
21516
+ if (locAmbiguities.length > 0) {
21517
+ const incMatch = includeLocRes.ambiguities.filter((a) => a.matches.length > 0);
21518
+ const incNone = includeLocRes.ambiguities.filter((a) => a.matches.length === 0);
21519
+ const excMatch = excludeLocRes.ambiguities.filter((a) => a.matches.length > 0);
21520
+ const excNone = excludeLocRes.ambiguities.filter((a) => a.matches.length === 0);
21521
+ const quote = (as) => as.map((a) => `"${a.location_text}"`).join(", ");
21522
+ const parts = [];
21523
+ if (incNone.length > 0) {
21524
+ parts.push(`Couldn't find a location matching ${quote(incNone)}. Pick a known area and re-call via locations (lens not yet created).`);
21525
+ }
21526
+ if (incMatch.length > 0) {
21527
+ parts.push(`${quote(incMatch)} matched multiple areas. Pick from the matches and re-call with the chosen id in locations.`);
21528
+ }
21529
+ if (excNone.length > 0) {
21530
+ parts.push(`Couldn't find a location to exclude matching ${quote(excNone)}. Pick a known area and re-call via exclude_locations (lens not yet created).`);
21531
+ }
21532
+ if (excMatch.length > 0) {
21533
+ parts.push(`${quote(excMatch)} (to exclude) matched multiple areas. Pick from the matches and re-call with the chosen id in exclude_locations \u2014 NOT locations, which would include it.`);
21534
+ }
21535
+ return {
21536
+ status: "ambiguous_locations",
21537
+ location_ambiguities: locAmbiguities,
21538
+ message: parts.join(" ")
21539
+ };
21540
+ }
21541
+ const merged = mergeFilter(EMPTY_FILTER, includeRes.resolved, excludeRes.resolved, params.sizes, includeLocRes.resolved, excludeLocRes.resolved);
21406
21542
  if (params.confirm !== true) {
21407
21543
  return {
21408
21544
  status: "preview",
@@ -21411,7 +21547,9 @@ var newLens = {
21411
21547
  description: params.description ?? null,
21412
21548
  sectors: includeRes.resolved,
21413
21549
  exclude_sectors: excludeRes.resolved,
21414
- sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null
21550
+ sizes: merged.lens_filter.items[0].criteria.find((c) => c.type === "size") ?? null,
21551
+ locations: includeLocRes.resolved,
21552
+ exclude_locations: excludeLocRes.resolved
21415
21553
  },
21416
21554
  filter_preview: merged,
21417
21555
  message: `About to create "${params.name}". Confirm with the user, then re-call with confirm:true.`,
@@ -23750,7 +23888,7 @@ function parseWriteEnv(env = process.env) {
23750
23888
  }
23751
23889
 
23752
23890
  // src/http-server.ts
23753
- var VERSION = true ? "0.23.4" : "0.0.0-dev";
23891
+ var VERSION = true ? "0.23.6" : "0.0.0-dev";
23754
23892
  var PORT = Number(process.env.PORT ?? 8080);
23755
23893
  var HOST = process.env.HOST ?? "0.0.0.0";
23756
23894
  var sseSessions = /* @__PURE__ */ new Map();