@leadbay/mcp 0.4.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/CHANGELOG.md +98 -0
- package/MIGRATION.md +236 -0
- package/README.md +217 -11
- package/dist/bin.js +444 -15
- package/dist/{chunk-O2UOXRZO.js → chunk-NLG7GUZ3.js} +5393 -2221
- package/dist/{dist-RONMQBYU.js → dist-JUTSXWBL.js} +7 -1
- package/package.json +1 -1
package/dist/bin.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
granularReadTools,
|
|
9
9
|
granularWriteTools,
|
|
10
10
|
resolveRegion
|
|
11
|
-
} from "./chunk-
|
|
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
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.
|
|
390
|
+
{ name: "leadbay", version: "0.6.0" },
|
|
106
391
|
{
|
|
107
|
-
capabilities: {
|
|
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(
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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-
|
|
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;
|