@leadbay/mcp 0.5.0 → 0.6.0

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/dist/bin.js CHANGED
@@ -8,7 +8,7 @@ import {
8
8
  granularReadTools,
9
9
  granularWriteTools,
10
10
  resolveRegion
11
- } from "./chunk-MCTWP5F2.js";
11
+ } from "./chunk-NLG7GUZ3.js";
12
12
 
13
13
  // src/bin.ts
14
14
  import { realpathSync } from "fs";
@@ -19,8 +19,220 @@ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"
19
19
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
20
20
  import {
21
21
  CallToolRequestSchema,
22
- ListToolsRequestSchema
22
+ ListToolsRequestSchema,
23
+ ListPromptsRequestSchema,
24
+ GetPromptRequestSchema,
25
+ ListResourcesRequestSchema,
26
+ ListResourceTemplatesRequestSchema,
27
+ ReadResourceRequestSchema,
28
+ ElicitResultSchema,
29
+ SubscribeRequestSchema,
30
+ UnsubscribeRequestSchema,
31
+ CompleteRequestSchema
23
32
  } from "@modelcontextprotocol/sdk/types.js";
33
+
34
+ // src/prompts.ts
35
+ function userMessage(text) {
36
+ return { role: "user", content: { type: "text", text } };
37
+ }
38
+ var CATALOG = [
39
+ {
40
+ name: "leadbay_daily_check_in",
41
+ description: "Run the canonical daily check-in: see account state, pull fresh leads, and surface the most-promising one for review. The user's typical morning workflow.",
42
+ arguments: [],
43
+ render: () => [
44
+ userMessage(
45
+ "Run the Leadbay daily check-in for me:\n1. Call leadbay_account_status to see what quota I have left and which lens is active.\n2. Call leadbay_pull_leads to get today's fresh batch.\n3. Show me the top 3 \u2014 by ai_agent_lead_score when present, otherwise by score. For each, summarize qualification_summary in one sentence.\n4. Recommend ONE lead to research deeply, and call leadbay_research_lead on it. Tell me what makes it promising, what signals stand out, and what would be the right outreach move.\n5. Stop. Wait for me to decide what to do next. Do not call leadbay_report_outreach unless I explicitly say so."
46
+ )
47
+ ]
48
+ },
49
+ {
50
+ name: "leadbay_research_a_domain",
51
+ description: "Import a company by domain and run deep qualification + research in one pass. Use when a colleague mentions a name and you want everything Leadbay knows about it.",
52
+ arguments: [
53
+ {
54
+ name: "domain",
55
+ description: "The company's primary domain (e.g. 'acme.com'). Protocol/path are stripped.",
56
+ required: true
57
+ }
58
+ ],
59
+ render: (args) => [
60
+ userMessage(
61
+ `Research the company with domain '${args.domain ?? "<missing>"}' for me using Leadbay:
62
+ 1. Call leadbay_import_and_qualify with domains=[{domain:'${args.domain ?? ""}'}]. This imports the lead AND runs AI qualification.
63
+ 2. When the import resolves, call leadbay_research_lead on the new leadId.
64
+ 3. Summarize: who is this company, what's their fit (qualification answers), what signals stand out, and which contact would I email first. Be honest about uncertainty.`
65
+ )
66
+ ]
67
+ },
68
+ {
69
+ name: "leadbay_refine_audience",
70
+ description: "Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.",
71
+ arguments: [
72
+ {
73
+ name: "instruction",
74
+ description: "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.",
75
+ required: true
76
+ }
77
+ ],
78
+ render: (args) => [
79
+ userMessage(
80
+ `Refine the Leadbay audience prompt to: ${args.instruction ?? "<missing>"}
81
+
82
+ 1. Call leadbay_refine_prompt with prompt=<the instruction above>.
83
+ 2. If the response includes a 'clarification' block, surface the question + options to me VERBATIM and wait. Do NOT call leadbay_answer_clarification on my behalf \u2014 I want to choose.
84
+ 3. If the response status is 'applied', tell me Leadbay is regenerating intelligence and recommend I check back in a few minutes via leadbay_account_status (computing_intelligence flips to false when ready).`
85
+ )
86
+ ]
87
+ },
88
+ {
89
+ name: "leadbay_log_outreach",
90
+ description: "Log outreach (an email I sent, a call I made, a meeting I had) on a specific lead. Captures verification so the SDR pipeline trusts the entry.",
91
+ arguments: [
92
+ {
93
+ name: "lead_id",
94
+ description: "The lead UUID. Get it from leadbay_pull_leads or leadbay_research_lead.",
95
+ required: true
96
+ },
97
+ {
98
+ name: "summary",
99
+ description: "1-2 sentences describing what I did (e.g. 'Sent intro email to CTO citing recent Hornsea contract').",
100
+ required: true
101
+ }
102
+ ],
103
+ render: (args) => [
104
+ userMessage(
105
+ `Log this outreach on Leadbay lead ${args.lead_id ?? "<missing>"}:
106
+ Summary: ${args.summary ?? "<missing>"}
107
+
108
+ Before calling leadbay_report_outreach, ask me ONCE for verification:
109
+ - If I sent an email: ask for the Gmail message id (verification.source = 'gmail_message_id').
110
+ - If I booked a meeting: ask for the calendar event id (verification.source = 'calendar_event_id').
111
+ - Otherwise: ask me for a literal one-sentence confirmation that the outreach happened (verification.source = 'user_confirmed', verification.ref = my exact words).
112
+
113
+ After I answer, call leadbay_report_outreach({lead_id, note: <summary>, verification: {source, ref}}). Optionally pass dry_run:true first to confirm what would be sent.`
114
+ )
115
+ ]
116
+ },
117
+ {
118
+ name: "leadbay_qualify_top_n",
119
+ description: "Bulk-qualify the top N un-qualified leads in the active lens. Uses leadbay_bulk_qualify_leads with a sensible default budget.",
120
+ arguments: [
121
+ {
122
+ name: "count",
123
+ description: "How many leads to qualify (default 10, max 25). Higher counts may take 5+ minutes.",
124
+ required: false
125
+ }
126
+ ],
127
+ render: (args) => {
128
+ const n = args.count ?? "10";
129
+ return [
130
+ userMessage(
131
+ `Qualify the top ${n} un-qualified leads in the active Leadbay lens:
132
+ 1. Call leadbay_bulk_qualify_leads with count=${n}.
133
+ 2. While it polls, expect notifications/progress events showing per-lead transitions.
134
+ 3. When it returns, summarize: how many qualified, how many still running, and the 3 highest-boost-score leads with their qualification_summary.
135
+ 4. Recommend the single most promising lead and offer to research it deeply with leadbay_research_lead.`
136
+ )
137
+ ];
138
+ }
139
+ }
140
+ ];
141
+ function listPrompts() {
142
+ return CATALOG.map((c) => ({
143
+ name: c.name,
144
+ description: c.description,
145
+ arguments: c.arguments
146
+ }));
147
+ }
148
+ function getPrompt(name, args = {}) {
149
+ const entry = CATALOG.find((c) => c.name === name);
150
+ if (!entry) {
151
+ throw new Error(`Unknown prompt: ${name}`);
152
+ }
153
+ const missing = entry.arguments.filter((a) => a.required && (args[a.name] === void 0 || args[a.name] === "")).map((a) => a.name);
154
+ if (missing.length > 0) {
155
+ throw new Error(
156
+ `Missing required prompt arguments: ${missing.join(", ")}`
157
+ );
158
+ }
159
+ return {
160
+ description: entry.description,
161
+ messages: entry.render(args)
162
+ };
163
+ }
164
+
165
+ // src/resources.ts
166
+ var LEAD_URI_RE = /^lead:\/\/([0-9a-f-]{36})\/profile$/i;
167
+ var LENS_URI_RE = /^lens:\/\/(\d+)\/definition$/;
168
+ var ORG_TASTE_URI = "org://taste-profile";
169
+ function listResources() {
170
+ return [
171
+ {
172
+ uri: ORG_TASTE_URI,
173
+ name: "Org taste profile",
174
+ description: "The org's qualification questions, intent tags, and ICP signals \u2014 the agent's knowledge base for what makes a lead a fit.",
175
+ mimeType: "application/json"
176
+ }
177
+ ];
178
+ }
179
+ function listResourceTemplates() {
180
+ return [
181
+ {
182
+ uriTemplate: "lead://{uuid}/profile",
183
+ name: "Lead profile",
184
+ description: "Full profile for a single Leadbay lead by UUID. Read-only. Cached client-side; cheaper than calling leadbay_research_lead when you already have the id.",
185
+ mimeType: "application/json"
186
+ },
187
+ {
188
+ uriTemplate: "lens://{id}/definition",
189
+ name: "Lens definition",
190
+ description: "Filter criteria + scoring config for a Leadbay lens by id. Useful for explaining the active lens or auditing why specific leads surfaced.",
191
+ mimeType: "application/json"
192
+ }
193
+ ];
194
+ }
195
+ function jsonContent(uri, value) {
196
+ return {
197
+ contents: [
198
+ {
199
+ uri,
200
+ mimeType: "application/json",
201
+ text: JSON.stringify(value, null, 2)
202
+ }
203
+ ]
204
+ };
205
+ }
206
+ async function readResource(uri, client) {
207
+ if (uri === ORG_TASTE_URI) {
208
+ const taste = await client.resolveTasteProfile();
209
+ return jsonContent(uri, taste);
210
+ }
211
+ const leadMatch = LEAD_URI_RE.exec(uri);
212
+ if (leadMatch) {
213
+ const leadId = leadMatch[1];
214
+ const lensId = await client.resolveDefaultLens();
215
+ const profile = await client.request(
216
+ "GET",
217
+ `/lenses/${lensId}/leads/${leadId}`
218
+ );
219
+ return jsonContent(uri, profile);
220
+ }
221
+ const lensMatch = LENS_URI_RE.exec(uri);
222
+ if (lensMatch) {
223
+ const lensId = Number(lensMatch[1]);
224
+ const [filter, scoring] = await Promise.all([
225
+ client.request("GET", `/lenses/${lensId}/filter`).catch(() => null),
226
+ client.request("GET", `/lenses/${lensId}/scoring`).catch(() => null)
227
+ ]);
228
+ return jsonContent(uri, { lensId, filter, scoring });
229
+ }
230
+ throw new Error(
231
+ `Unsupported resource URI: ${uri}. Supported schemes: lead://{uuid}/profile, lens://{id}/definition, org://taste-profile.`
232
+ );
233
+ }
234
+
235
+ // src/server.ts
24
236
  var VERIFICATION_MANDATE = "After every email, call, message, or meeting with a lead's contact, you MUST call leadbay_report_outreach with verification={source, ref} (gmail_message_id from the Gmail send, calendar_event_id from a booking, or user_confirmed='<the user's literal confirmation>'). Skipping or fabricating verification poisons the human team's pipeline.";
25
237
  var MENTAL_MODEL_PARAGRAPH = "How Leadbay works (mental model): Leadbay is a sales inbox, not a queryable database. Each day the user logs back in, a fresh batch of leads is delivered. Batch size is paced by how many leads the user has actually acted on recently \u2014 some workflows produce a big stream of smaller prospects, others a narrow stream of bigger ones. Pulling more won't produce more; the user acting on leads (outreach, skips, saves) does.";
26
238
  function buildScoringParagraph(has) {
@@ -47,6 +259,68 @@ function buildRhythmParagraph(has) {
47
259
  }
48
260
  return "Suggested rhythm: a healthy agent pattern is a daily check-in \u2014 pull fresh leads, skim the auto-qualified top, deepen 1-3 promising ones, propose outreach to the user. If your host supports scheduling, offer to set up a daily run.";
49
261
  }
262
+ function buildSlashCommandsParagraph(has) {
263
+ const commands = [];
264
+ commands.push("`/leadbay daily-check-in` (chains account_status \u2192 pull_leads \u2192 research_lead on the top hit)");
265
+ if (has("leadbay_import_and_qualify")) {
266
+ commands.push("`/leadbay research-a-domain {domain}` (import_and_qualify \u2192 research_lead)");
267
+ }
268
+ if (has("leadbay_refine_prompt")) {
269
+ commands.push("`/leadbay refine-audience {instruction}` (refine_prompt with clarification handling)");
270
+ }
271
+ if (has("leadbay_report_outreach")) {
272
+ commands.push("`/leadbay log-outreach {lead_id, summary}` (gathers verification then report_outreach)");
273
+ }
274
+ if (has("leadbay_bulk_qualify_leads")) {
275
+ commands.push("`/leadbay qualify-top-n {count}` (bulk_qualify_leads with progress streaming)");
276
+ }
277
+ return "Slash commands (`prompts/*`): the user's MCP client may surface registered prompts as slash commands. Available: " + commands.join(", ") + ". When the user invokes one, the rendered messages tell you which tools to call and in what order \u2014 follow them.";
278
+ }
279
+ var RESOURCES_PARAGRAPH = "Read-only resources (`resources/*`): three URI schemes are available \u2014 `lead://{uuid}/profile` (lead profile by id), `lens://{id}/definition` (filter + scoring config), `org://taste-profile` (qualification questions + intent tags). Capable clients cache these across turns \u2014 cheaper than re-running pull_leads / research_lead when the agent already has the id. Capable clients can also call `resources/subscribe` (the server stores the subscription; Leadbay's backend doesn't push deltas yet so notifications are not currently emitted) and `completion/complete` for URI auto-complete on the templates.";
280
+ function buildProtocolPrimitivesParagraph(has) {
281
+ const longRunners = [
282
+ "bulk_qualify_leads",
283
+ "import_and_qualify",
284
+ "enrich_titles",
285
+ "bulk_enrich_status",
286
+ "qualify_status"
287
+ ].filter((n) => has(`leadbay_${n}`));
288
+ const elicitTools = [
289
+ "refine_prompt clarifications",
290
+ "report_outreach.user_confirmed"
291
+ ].filter((label) => {
292
+ if (label.startsWith("refine_prompt")) return has("leadbay_refine_prompt");
293
+ if (label.startsWith("report_outreach")) return has("leadbay_report_outreach");
294
+ return false;
295
+ });
296
+ const parts = ["Protocol primitives the server supports:"];
297
+ if (longRunners.length > 0) {
298
+ parts.push(
299
+ `(1) \`notifications/progress\` \u2014 when you pass \`_meta.progressToken\` on a tools/call, long-running composites stream per-unit-of-work progress with \`progress\`, \`total\`, and human-readable \`message\` (e.g. 'Qualified Acme Corp (3/10)'). Pass a progressToken on ${longRunners.map((n) => `leadbay_${n}`).join(", ")}.`
300
+ );
301
+ } else {
302
+ parts.push(
303
+ "(1) `notifications/progress` \u2014 when you pass `_meta.progressToken` on a tools/call, long-running composites stream per-unit-of-work progress (none of the long-runners are currently exposed in this configuration)."
304
+ );
305
+ }
306
+ if (longRunners.length > 0) {
307
+ parts.push(
308
+ "(2) `notifications/cancelled` \u2014 when the user clicks Cancel in the host UI, the polling loop exits within \u22642 seconds AND the bulk-store entry transitions to 'cancelled'; subsequent status polls return `BULK_CANCELLED` so the agent stops polling."
309
+ );
310
+ } else {
311
+ parts.push(
312
+ "(2) `notifications/cancelled` \u2014 supported (no long-runners exposed in this configuration)."
313
+ );
314
+ }
315
+ if (elicitTools.length > 0) {
316
+ parts.push(
317
+ `(3) \`elicitation/create\` \u2014 for ${elicitTools.join(
318
+ " and "
319
+ )} the SERVER asks the user via the client UI. The agent doesn't author the prompt or fabricate the response \u2014 the user types directly. The response carries \`confirmed_via: 'elicit' | 'agent_supplied' | 'non_user_confirmed'\` so the audit trail records which path was actually taken.`
320
+ );
321
+ }
322
+ return parts.join(" ");
323
+ }
50
324
  function buildServerInstructions(exposed) {
51
325
  const has = (name) => exposed.has(name);
52
326
  const parts = [];
@@ -57,6 +331,9 @@ function buildServerInstructions(exposed) {
57
331
  parts.push(buildScoringParagraph(has));
58
332
  parts.push(buildStartHereParagraph(has));
59
333
  parts.push(buildRhythmParagraph(has));
334
+ parts.push(buildSlashCommandsParagraph(has));
335
+ parts.push(RESOURCES_PARAGRAPH);
336
+ parts.push(buildProtocolPrimitivesParagraph(has));
60
337
  return parts.join("\n\n");
61
338
  }
62
339
  function formatErrorForLLM(err) {
@@ -76,11 +353,16 @@ function formatErrorForLLM(err) {
76
353
  return String(err);
77
354
  }
78
355
  function toolsListPayload(tools) {
79
- return tools.map((t) => ({
80
- name: t.name,
81
- description: t.description,
82
- inputSchema: t.inputSchema
83
- }));
356
+ return tools.map((t) => {
357
+ const out = {
358
+ name: t.name,
359
+ description: t.description,
360
+ inputSchema: t.inputSchema
361
+ };
362
+ if (t.annotations) out.annotations = t.annotations;
363
+ if (t.outputSchema) out.outputSchema = t.outputSchema;
364
+ return out;
365
+ });
84
366
  }
85
367
  function buildServer(client, opts = {}) {
86
368
  const exposedTools = [];
@@ -94,6 +376,9 @@ function buildServer(client, opts = {}) {
94
376
  exposedTools.push(...granularWriteTools);
95
377
  }
96
378
  }
379
+ if (opts.extraTools) {
380
+ exposedTools.push(...opts.extraTools);
381
+ }
97
382
  const toolByName = /* @__PURE__ */ new Map();
98
383
  for (const t of exposedTools) {
99
384
  if (!toolByName.has(t.name) && t.name !== "leadbay_login") {
@@ -102,16 +387,85 @@ function buildServer(client, opts = {}) {
102
387
  }
103
388
  const exposedNames = new Set(toolByName.keys());
104
389
  const server = new Server(
105
- { name: "leadbay", version: "0.3.0" },
390
+ { name: "leadbay", version: "0.6.0" },
106
391
  {
107
- capabilities: { tools: {} },
392
+ capabilities: {
393
+ tools: {},
394
+ prompts: {},
395
+ // iter-28: advertise subscribe + listChanged on resources, plus
396
+ // completions provider for URI auto-complete.
397
+ resources: { subscribe: true, listChanged: true },
398
+ completions: {}
399
+ },
108
400
  instructions: buildServerInstructions(exposedNames)
109
401
  }
110
402
  );
111
403
  server.setRequestHandler(ListToolsRequestSchema, async () => ({
112
404
  tools: toolsListPayload([...toolByName.values()])
113
405
  }));
114
- server.setRequestHandler(CallToolRequestSchema, async (req) => {
406
+ server.setRequestHandler(ListPromptsRequestSchema, async () => ({
407
+ prompts: listPrompts()
408
+ }));
409
+ server.setRequestHandler(GetPromptRequestSchema, async (req) => {
410
+ return getPrompt(req.params.name, req.params.arguments ?? {});
411
+ });
412
+ server.setRequestHandler(ListResourcesRequestSchema, async () => ({
413
+ resources: listResources()
414
+ }));
415
+ server.setRequestHandler(ListResourceTemplatesRequestSchema, async () => ({
416
+ resourceTemplates: listResourceTemplates()
417
+ }));
418
+ server.setRequestHandler(ReadResourceRequestSchema, async (req) => {
419
+ return readResource(req.params.uri, client);
420
+ });
421
+ const subscribers = /* @__PURE__ */ new Set();
422
+ server.setRequestHandler(SubscribeRequestSchema, async (req) => {
423
+ subscribers.add(req.params.uri);
424
+ opts.logger?.info?.(`resources.subscribe uri=${req.params.uri} subs=${subscribers.size}`);
425
+ return {};
426
+ });
427
+ server.setRequestHandler(UnsubscribeRequestSchema, async (req) => {
428
+ subscribers.delete(req.params.uri);
429
+ opts.logger?.info?.(`resources.unsubscribe uri=${req.params.uri} subs=${subscribers.size}`);
430
+ return {};
431
+ });
432
+ server.setRequestHandler(CompleteRequestSchema, async (req) => {
433
+ const ref = req.params.ref;
434
+ const argName = req.params.argument?.name;
435
+ const argValue = String(req.params.argument?.value ?? "");
436
+ if (ref.type !== "ref/resource") {
437
+ return { completion: { values: [], total: 0, hasMore: false } };
438
+ }
439
+ try {
440
+ if (ref.uri === "lead://{uuid}/profile" && argName === "uuid") {
441
+ const lensId = await client.resolveDefaultLens();
442
+ const wish = await client.request(
443
+ "GET",
444
+ `/lenses/${lensId}/leads/wishlist?count=50&page=0`
445
+ );
446
+ const ids = (wish?.items ?? []).map((i) => i.id).filter((id) => id.toLowerCase().startsWith(argValue.toLowerCase())).slice(0, 20);
447
+ return {
448
+ completion: { values: ids, total: ids.length, hasMore: false }
449
+ };
450
+ }
451
+ if (ref.uri === "lens://{id}/definition" && argName === "id") {
452
+ const lenses = await client.request("GET", "/lenses");
453
+ const ids = (lenses ?? []).map((l) => String(l.id)).filter((id) => id.startsWith(argValue)).slice(0, 20);
454
+ return {
455
+ completion: { values: ids, total: ids.length, hasMore: false }
456
+ };
457
+ }
458
+ } catch (err) {
459
+ opts.logger?.warn?.(
460
+ `completion provider error: ${err?.message ?? err?.code ?? err}`
461
+ );
462
+ }
463
+ return { completion: { values: [], total: 0, hasMore: false } };
464
+ });
465
+ const DEBUG_RAW = process.env.LEADBAY_DEBUG ?? "";
466
+ const DEBUG_ON = DEBUG_RAW === "1" || DEBUG_RAW.toLowerCase() === "true";
467
+ server.setRequestHandler(CallToolRequestSchema, async (req, extra) => {
468
+ const debugStart = DEBUG_ON ? Date.now() : 0;
115
469
  const name = req.params.name;
116
470
  const tool = toolByName.get(name);
117
471
  if (!tool) {
@@ -126,10 +480,45 @@ function buildServer(client, opts = {}) {
126
480
  };
127
481
  }
128
482
  const args = req.params.arguments ?? {};
483
+ const progressToken = req.params?._meta?.progressToken;
484
+ const progress = progressToken !== void 0 ? (params) => {
485
+ extra.sendNotification({
486
+ method: "notifications/progress",
487
+ params: {
488
+ progressToken,
489
+ progress: params.progress,
490
+ ...params.total !== void 0 ? { total: params.total } : {},
491
+ ...params.message !== void 0 ? { message: params.message } : {}
492
+ }
493
+ }).catch((err) => {
494
+ opts.logger?.warn?.(
495
+ `progress emit failed: ${err?.message ?? err?.code ?? String(err)}`
496
+ );
497
+ });
498
+ } : void 0;
499
+ const elicit = async (params) => {
500
+ const result = await extra.sendRequest(
501
+ {
502
+ method: "elicitation/create",
503
+ params: {
504
+ message: params.message,
505
+ requestedSchema: params.requestedSchema
506
+ }
507
+ },
508
+ ElicitResultSchema
509
+ );
510
+ return {
511
+ action: result.action,
512
+ content: result.content
513
+ };
514
+ };
129
515
  try {
130
516
  const result = await tool.execute(client, args, {
131
517
  logger: opts.logger,
132
- bulkTracker: opts.bulkTracker
518
+ bulkTracker: opts.bulkTracker,
519
+ signal: extra.signal,
520
+ progress,
521
+ elicit
133
522
  });
134
523
  if (result && typeof result === "object" && result.error === true) {
135
524
  return {
@@ -139,12 +528,52 @@ function buildServer(client, opts = {}) {
139
528
  isError: true
140
529
  };
141
530
  }
142
- return {
531
+ const isMarkdownEnvelope = result && typeof result === "object" && result.__markdown_envelope === true && typeof result.markdown === "string";
532
+ if (isMarkdownEnvelope) {
533
+ const env = result;
534
+ const out = {
535
+ content: [{ type: "text", text: env.markdown }]
536
+ };
537
+ if (tool.outputSchema && env.structured !== null && typeof env.structured === "object" && !Array.isArray(env.structured)) {
538
+ out.structuredContent = env.structured;
539
+ }
540
+ if (DEBUG_ON) {
541
+ const dur = Date.now() - debugStart;
542
+ const bytes = env.markdown.length;
543
+ process.stderr.write(
544
+ `[leadbay-mcp debug] tool=${name} dur=${dur}ms ok=true bytes=${bytes} format=markdown
545
+ `
546
+ );
547
+ }
548
+ return out;
549
+ }
550
+ const response = {
143
551
  content: [
144
552
  { type: "text", text: JSON.stringify(result, null, 2) }
145
553
  ]
146
554
  };
555
+ if (tool.outputSchema && result !== null && typeof result === "object" && !Array.isArray(result)) {
556
+ response.structuredContent = result;
557
+ }
558
+ if (DEBUG_ON) {
559
+ const dur = Date.now() - debugStart;
560
+ const text = response.content[0]?.text ?? "";
561
+ const bytes = typeof text === "string" ? text.length : 0;
562
+ process.stderr.write(
563
+ `[leadbay-mcp debug] tool=${name} dur=${dur}ms ok=true bytes=${bytes}
564
+ `
565
+ );
566
+ }
567
+ return response;
147
568
  } catch (err) {
569
+ if (DEBUG_ON) {
570
+ const dur = Date.now() - debugStart;
571
+ const code = err?.code ?? err?.name ?? "Error";
572
+ process.stderr.write(
573
+ `[leadbay-mcp debug] tool=${name} dur=${dur}ms ok=false code=${code}
574
+ `
575
+ );
576
+ }
148
577
  return {
149
578
  content: [
150
579
  { type: "text", text: formatErrorForLLM(err) }
@@ -158,7 +587,7 @@ function buildServer(client, opts = {}) {
158
587
 
159
588
  // src/bin.ts
160
589
  import { createRequire } from "module";
161
- var VERSION = "0.5.0";
590
+ var VERSION = "0.6.0";
162
591
  var HELP = `
163
592
  leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
164
593
 
@@ -447,7 +876,7 @@ async function runLogin(args) {
447
876
  let result;
448
877
  try {
449
878
  if (pinnedRegion && !allowFallback) {
450
- const { REGIONS } = await import("./dist-GUZQWQOO.js");
879
+ const { REGIONS } = await import("./dist-JUTSXWBL.js");
451
880
  const baseUrl = REGIONS[pinnedRegion];
452
881
  const c = createClient({ region: pinnedRegion });
453
882
  const token = await loginAt(baseUrl, email, password);
@@ -939,7 +1368,7 @@ leadbay-mcp install \u2014 detected MCP clients on this machine:
939
1368
  let region;
940
1369
  try {
941
1370
  if (pinnedRegion && !allowFallback) {
942
- const { REGIONS } = await import("./dist-GUZQWQOO.js");
1371
+ const { REGIONS } = await import("./dist-JUTSXWBL.js");
943
1372
  const baseUrl = REGIONS[pinnedRegion];
944
1373
  token = await loginAt(baseUrl, email, password);
945
1374
  region = pinnedRegion;