@leadbay/mcp 0.19.2 → 0.19.3
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 +4 -0
- package/dist/bin.js +251 -9
- package/dist/http-server.js +205 -7
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.19.3 — 2026-06-15
|
|
4
|
+
|
|
5
|
+
- **New tool `leadbay_send_feedback`**: delivers a user-authored message to the same destination as the web app's "Send feedback" form — the team's Sentry feedback inbox (the website form calls `Sentry.captureFeedback`; there is no Leadbay API endpoint, so the MCP reuses its already-initialized `@sentry/node`). User-initiated ("send feedback / report a bug / tell Leadbay…"), or offered on a tool error and sent only on explicit yes. Distinct from the silent, agent-detected, PostHog-only `leadbay_report_friction`: feedback is explicit, user-authored, and reaches the team's inbox. Honest delivery — if the Sentry transport isn't available it returns `sent:false`, never a false success; Sentry is flushed after capture so the event actually ships; identity is attached when it resolves (anonymous fallback rather than dropping the message). Write-gated (`LEADBAY_MCP_WRITE=1`) since it sends data outward.
|
|
6
|
+
|
|
3
7
|
## 0.19.2 — 2026-06-10
|
|
4
8
|
|
|
5
9
|
- **Stop paging Sentry on a missing `_triggered_by`**: a composite tool called without `_triggered_by` is a recoverable agent mistake — the host just re-calls with the field set. The guard used to `throw` an `{error:true, code:"LAST_PROMPT_REQUIRED"}` envelope into the shared catch, where `isLeadbayBusinessError` matched it and fired `captureException`, auto-opening a top-priority Sentry/GitHub bug (product#3718) on every dropped field. The guard now returns the `isError` envelope directly. Behavior toward the LLM is unchanged (same text, same `isError`, same recovery hint), and PostHog visibility is preserved (`captureToolCall` + `captureCompositeCall` still fire `ok:false` / `LAST_PROMPT_REQUIRED`, so the mandate-ignore rate stays observable); only `captureException` is dropped.
|
package/dist/bin.js
CHANGED
|
@@ -277,13 +277,40 @@ var init_client = __esm({
|
|
|
277
277
|
if (next)
|
|
278
278
|
next();
|
|
279
279
|
}
|
|
280
|
-
|
|
280
|
+
// Leadbay tokens don't expire, so a 401 is almost always a transient
|
|
281
|
+
// server-side blip. Retry the request ONCE before surfacing it — a single
|
|
282
|
+
// retry clears the vast majority of these without the agent ever seeing an
|
|
283
|
+
// error. If the retry also 401s, it's a real Leadbay-side problem and the
|
|
284
|
+
// error envelope says so.
|
|
285
|
+
//
|
|
286
|
+
// Arrow-function field so `this` stays bound even when the method is passed
|
|
287
|
+
// as a bare reference (see request()'s ternary). Retries are GET-ONLY: a 401
|
|
288
|
+
// on a write (POST/PUT/DELETE) may arrive AFTER the mutation already committed
|
|
289
|
+
// server-side, so blindly re-sending it would double-execute the write. Reads
|
|
290
|
+
// are idempotent, so retrying them is safe. The 250ms backoff releases the
|
|
291
|
+
// concurrency slot first (release → sleep → re-acquire) so a wave of 401s
|
|
292
|
+
// doesn't pin all MAX_CONCURRENT slots in setTimeout and stall the queue.
|
|
293
|
+
httpsRequestWithRetry = async (method, url, headers, body) => {
|
|
294
|
+
const res = await httpsRequest(method, url, headers, body);
|
|
295
|
+
if (res.status === 401 && method.toUpperCase() === "GET") {
|
|
296
|
+
this.releaseSemaphore();
|
|
297
|
+
try {
|
|
298
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
299
|
+
} finally {
|
|
300
|
+
await this.acquireSemaphore();
|
|
301
|
+
}
|
|
302
|
+
return httpsRequest(method, url, headers, body);
|
|
303
|
+
}
|
|
304
|
+
return res;
|
|
305
|
+
};
|
|
306
|
+
async request(method, path, body, opts) {
|
|
281
307
|
if (process.env.LEADBAY_MOCK === "1") {
|
|
282
308
|
return this.mockRequest(method, path, body);
|
|
283
309
|
}
|
|
284
310
|
if (!this.token) {
|
|
285
311
|
throw this.makeError("NOT_AUTHENTICATED", "Not logged in to Leadbay", "Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>", path);
|
|
286
312
|
}
|
|
313
|
+
const retryOn401 = opts?.retryOn401 !== false;
|
|
287
314
|
await this.acquireSemaphore();
|
|
288
315
|
try {
|
|
289
316
|
const url = `${this._baseUrl}/1.5${path}`;
|
|
@@ -293,7 +320,7 @@ var init_client = __esm({
|
|
|
293
320
|
if (body) {
|
|
294
321
|
headers["Content-Type"] = "application/json";
|
|
295
322
|
}
|
|
296
|
-
const res = await httpsRequest(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
323
|
+
const res = await (retryOn401 ? this.httpsRequestWithRetry : httpsRequest)(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
297
324
|
this._lastMeta = {
|
|
298
325
|
region: this._region,
|
|
299
326
|
endpoint: `${method} ${path}`,
|
|
@@ -328,7 +355,7 @@ var init_client = __esm({
|
|
|
328
355
|
if (body) {
|
|
329
356
|
headers["Content-Type"] = "application/json";
|
|
330
357
|
}
|
|
331
|
-
const res = await
|
|
358
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
332
359
|
this._lastMeta = {
|
|
333
360
|
region: this._region,
|
|
334
361
|
endpoint: `${method} ${path}`,
|
|
@@ -361,7 +388,7 @@ var init_client = __esm({
|
|
|
361
388
|
Authorization: `Bearer ${this.token}`,
|
|
362
389
|
"Content-Type": contentType
|
|
363
390
|
};
|
|
364
|
-
const res = await
|
|
391
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body);
|
|
365
392
|
this._lastMeta = {
|
|
366
393
|
region: this._region,
|
|
367
394
|
endpoint: `${method} ${path}`,
|
|
@@ -444,7 +471,7 @@ var init_client = __esm({
|
|
|
444
471
|
}
|
|
445
472
|
const retryAfter = parseRetryAfter(headers["retry-after"]);
|
|
446
473
|
if (status === 401) {
|
|
447
|
-
return this.makeError("AUTH_EXPIRED", "
|
|
474
|
+
return this.makeError("AUTH_EXPIRED", "Leadbay rejected this request (401)", "Leadbay tokens don't expire on a timer, so this isn't a stale token. A 401 here is usually a Leadbay-side hiccup, but can also mean the user logged out. Try again shortly; if it persists, offer to report it to the team.", endpoint, null, status);
|
|
448
475
|
}
|
|
449
476
|
if (status === 429 || status === 402 || parsed?.error === "quota_exceeded" || parsed?.error?.code === "quota_exceeded") {
|
|
450
477
|
const hintBase = retryAfter ? `Wait ${retryAfter}s before retrying` : "Wait, then retry";
|
|
@@ -5440,7 +5467,7 @@ var init_notifications = __esm({
|
|
|
5440
5467
|
});
|
|
5441
5468
|
|
|
5442
5469
|
// ../core/dist/tool-descriptions.generated.js
|
|
5443
|
-
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5470
|
+
var leadbay_account_history, leadbay_account_status, leadbay_acknowledge_notification, leadbay_add_contact, leadbay_add_leads_to_campaign, leadbay_add_note, leadbay_adjust_audience, leadbay_agent_memory_capture, leadbay_agent_memory_recall, leadbay_agent_memory_review, leadbay_answer_clarification, leadbay_bulk_enrich_status, leadbay_bulk_qualify_leads, leadbay_campaign_call_sheet, leadbay_campaign_progression, leadbay_clear_selection, leadbay_clear_user_prompt, leadbay_create_campaign, leadbay_create_custom_field, leadbay_create_lens, leadbay_create_lens_draft, leadbay_create_topup_link, leadbay_deselect_leads, leadbay_discover_leads, leadbay_dislike_lead, leadbay_dismiss_clarification, leadbay_enrich_contacts, leadbay_enrich_titles, leadbay_extend_lens, leadbay_followups_map, leadbay_get_clarification, leadbay_get_contacts, leadbay_get_enrichment_job_titles, leadbay_get_epilogue_responses, leadbay_get_lead_activities, leadbay_get_lead_notes, leadbay_get_lead_profile, leadbay_get_lens_filter, leadbay_get_lens_scoring, leadbay_get_prospecting_actions, leadbay_get_quota, leadbay_get_selection_ids, leadbay_get_taste_profile, leadbay_get_user_prompt, leadbay_get_web_fetch, leadbay_import_and_qualify, leadbay_import_leads, leadbay_import_status, leadbay_launch_bulk_enrichment, leadbay_like_lead, leadbay_list_campaigns, leadbay_list_lenses, leadbay_list_locations, leadbay_list_mappable_fields, leadbay_list_sectors, leadbay_login, leadbay_my_lenses, leadbay_new_lens, leadbay_open_billing_portal, leadbay_pick_clarification, leadbay_pin_contact, leadbay_prepare_outreach, leadbay_preview_bulk_enrichment, leadbay_promote_lens, leadbay_pull_followups, leadbay_pull_leads, leadbay_qualify_lead, leadbay_qualify_status, leadbay_recall_ordered_titles, leadbay_refine_prompt, leadbay_remove_contact, leadbay_remove_epilogue, leadbay_remove_leads_from_campaign, leadbay_remove_pushback, leadbay_report_friction, leadbay_report_outreach, leadbay_research_lead_by_id, leadbay_research_lead_by_name_fuzzy, leadbay_resolve_import_rows, leadbay_scan_portfolio_signals, leadbay_seed_candidates, leadbay_select_leads, leadbay_send_feedback, leadbay_set_active_lens, leadbay_set_epilogue_status, leadbay_set_pushback, leadbay_set_user_prompt, leadbay_tour_plan, leadbay_unpin_contact, leadbay_update_contact, leadbay_update_lens, leadbay_update_lens_filter;
|
|
5444
5471
|
var init_tool_descriptions_generated = __esm({
|
|
5445
5472
|
"../core/dist/tool-descriptions.generated.js"() {
|
|
5446
5473
|
"use strict";
|
|
@@ -8722,6 +8749,88 @@ WHEN TO USE: low-level. The user's selection is a per-token global state \u2014
|
|
|
8722
8749
|
WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192 action \u2192 clear in one call with proper Mutex protection. Calling this directly without acquiring the selection lock can clobber concurrent composite calls.
|
|
8723
8750
|
|
|
8724
8751
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
8752
|
+
`;
|
|
8753
|
+
leadbay_send_feedback = `## WHEN TO USE
|
|
8754
|
+
|
|
8755
|
+
Trigger phrases: "send feedback", "I want to report a bug", "tell the Leadbay team", "let Leadbay know", "give feedback", "report this to support", "I have a feature request".
|
|
8756
|
+
|
|
8757
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
8758
|
+
|
|
8759
|
+
Do NOT use for: "no, I meant / still nothing / ugh" \u2192 \`leadbay_report_friction\`; "log the email I sent" \u2192 \`leadbay_report_outreach\`.
|
|
8760
|
+
|
|
8761
|
+
Prefer when: the user explicitly wants the Leadbay TEAM to receive a message they authored \u2014 or accepts your offer to report an error. For silent, agent-detected friction signals use leadbay_report_friction instead.
|
|
8762
|
+
|
|
8763
|
+
Examples that SHOULD invoke this tool:
|
|
8764
|
+
- "Send feedback to the team: the lead scores feel off this week."
|
|
8765
|
+
- "Can you report a bug? Pulling leads in Lyon returns nothing."
|
|
8766
|
+
- "Tell Leadbay I'd love a way to schedule my morning check-in."
|
|
8767
|
+
|
|
8768
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
8769
|
+
- "No, I meant Wisconsin not Wyoming."
|
|
8770
|
+
- "I emailed Acme \u2014 log that outreach."
|
|
8771
|
+
- "Thumbs down on this lead."
|
|
8772
|
+
|
|
8773
|
+
## RENDER (quick)
|
|
8774
|
+
|
|
8775
|
+
Confirm the exact wording with the user BEFORE calling (this is sent to
|
|
8776
|
+
the team). After sending, show a one-line confirmation from the result's
|
|
8777
|
+
\`message\` (e.g. "\u2713 Sent to the Leadbay team"). If \`sent\` is false, tell
|
|
8778
|
+
the user it could NOT be delivered \u2014 never imply it was sent.
|
|
8779
|
+
|
|
8780
|
+
---
|
|
8781
|
+
|
|
8782
|
+
Deliver a user-authored message to the Leadbay team's feedback inbox \u2014 the same
|
|
8783
|
+
destination as the web app's feedback form. **You do not write the feedback;
|
|
8784
|
+
the user does.** Capture their words, confirm the phrasing, then send.
|
|
8785
|
+
|
|
8786
|
+
## Parameters
|
|
8787
|
+
- \`message\` (required) \u2014 the user's feedback, in their own words. Confirm it
|
|
8788
|
+
with the user before sending. Cap 4000 chars.
|
|
8789
|
+
- \`associated_error_id\` (optional) \u2014 a Sentry event id to attach the feedback
|
|
8790
|
+
to (e.g. the id surfaced by an error the user just hit), so the team sees the
|
|
8791
|
+
feedback on that exact issue.
|
|
8792
|
+
|
|
8793
|
+
## When a tool errors \u2014 OFFER, don't auto-send
|
|
8794
|
+
When a Leadbay tool returns an error and the user might want the team to know,
|
|
8795
|
+
you may OFFER: *"Want me to send feedback about this to the Leadbay team?"*
|
|
8796
|
+
- Send ONLY if the user says yes AND gives (or approves) a message.
|
|
8797
|
+
- Never send feedback the user didn't author or approve.
|
|
8798
|
+
- If an error event id is available, pass it as \`associated_error_id\`.
|
|
8799
|
+
|
|
8800
|
+
## Result
|
|
8801
|
+
- \`sent: true\` \u2192 it reached the team. Show the confirmation from \`message\`.
|
|
8802
|
+
- \`sent: false\` \u2192 delivery wasn't possible (feedback not available on this
|
|
8803
|
+
client). Tell the user it was NOT sent. Do not claim success.
|
|
8804
|
+
|
|
8805
|
+
This is the only "talk to the Leadbay team" tool. It does not mutate any
|
|
8806
|
+
Leadbay data. For silent friction signals you detect yourself, use
|
|
8807
|
+
\`leadbay_report_friction\` instead.
|
|
8808
|
+
|
|
8809
|
+
## NEXT STEPS \u2014 after sending feedback
|
|
8810
|
+
|
|
8811
|
+
**ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
|
|
8812
|
+
|
|
8813
|
+
**If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
|
|
8814
|
+
|
|
8815
|
+
**One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
|
|
8816
|
+
- Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
|
|
8817
|
+
- Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
|
|
8818
|
+
|
|
8819
|
+
Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
|
|
8820
|
+
- \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
|
|
8821
|
+
- \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
|
|
8822
|
+
|
|
8823
|
+
User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
|
|
8824
|
+
|
|
8825
|
+
---
|
|
8826
|
+
|
|
8827
|
+
|
|
8828
|
+
|
|
8829
|
+
| Observation | Suggest | Calls |
|
|
8830
|
+
|----------------------------------------------|------------------------------------------------------|-----------------------------------------|
|
|
8831
|
+
| \`sent == true\` | "Anything else you'd like to flag to the team?" | leadbay_send_feedback(message) |
|
|
8832
|
+
| \`sent == false\` | "It didn't go through \u2014 want to try sending again?" | leadbay_send_feedback(message) |
|
|
8833
|
+
| Feedback was about an error the user hit | "Want me to retry the action that failed?" | (re-call the tool that errored) |
|
|
8725
8834
|
`;
|
|
8726
8835
|
leadbay_set_active_lens = `Mark a lens as last-used. Subsequent \`/me\` reads return it as \`last_requested_lens\`, so all composite tools default to it.
|
|
8727
8836
|
|
|
@@ -21240,6 +21349,90 @@ var init_report_friction = __esm({
|
|
|
21240
21349
|
}
|
|
21241
21350
|
});
|
|
21242
21351
|
|
|
21352
|
+
// ../core/dist/tools/send-feedback.js
|
|
21353
|
+
var MESSAGE_MAX, sendFeedback;
|
|
21354
|
+
var init_send_feedback = __esm({
|
|
21355
|
+
"../core/dist/tools/send-feedback.js"() {
|
|
21356
|
+
"use strict";
|
|
21357
|
+
init_tool_descriptions_generated();
|
|
21358
|
+
MESSAGE_MAX = 4e3;
|
|
21359
|
+
sendFeedback = {
|
|
21360
|
+
name: "leadbay_send_feedback",
|
|
21361
|
+
annotations: {
|
|
21362
|
+
title: "Send feedback to the Leadbay team",
|
|
21363
|
+
readOnlyHint: false,
|
|
21364
|
+
destructiveHint: false,
|
|
21365
|
+
idempotentHint: false,
|
|
21366
|
+
openWorldHint: true
|
|
21367
|
+
},
|
|
21368
|
+
description: leadbay_send_feedback,
|
|
21369
|
+
// Write-gated: it sends data outward to the Leadbay team. Registered in
|
|
21370
|
+
// compositeWriteTools (default-on since 0.3.0).
|
|
21371
|
+
write: true,
|
|
21372
|
+
inputSchema: {
|
|
21373
|
+
type: "object",
|
|
21374
|
+
properties: {
|
|
21375
|
+
message: {
|
|
21376
|
+
type: "string",
|
|
21377
|
+
description: "The user's feedback, in their own words. Confirm the wording with the user BEFORE calling \u2014 this is sent to the Leadbay team. Cap 4000 chars."
|
|
21378
|
+
},
|
|
21379
|
+
associated_error_id: {
|
|
21380
|
+
type: "string",
|
|
21381
|
+
description: "Optional: a Sentry event id to attach this feedback to (e.g. the id from an error the user just hit), so the team sees the feedback on that exact issue."
|
|
21382
|
+
}
|
|
21383
|
+
},
|
|
21384
|
+
required: ["message"],
|
|
21385
|
+
additionalProperties: false
|
|
21386
|
+
},
|
|
21387
|
+
outputSchema: {
|
|
21388
|
+
type: "object",
|
|
21389
|
+
description: "Whether the feedback reached the Leadbay team. `sent: true` means it landed in the team's inbox.",
|
|
21390
|
+
properties: {
|
|
21391
|
+
sent: { type: "boolean" },
|
|
21392
|
+
message: { type: "string" },
|
|
21393
|
+
_meta: {
|
|
21394
|
+
type: "object",
|
|
21395
|
+
properties: { region: { type: "string" } }
|
|
21396
|
+
}
|
|
21397
|
+
}
|
|
21398
|
+
},
|
|
21399
|
+
execute: async (client, params, ctx) => {
|
|
21400
|
+
const text = typeof params.message === "string" ? params.message.trim() : "";
|
|
21401
|
+
if (!text) {
|
|
21402
|
+
return {
|
|
21403
|
+
error: true,
|
|
21404
|
+
code: "BAD_INPUT",
|
|
21405
|
+
message: "message is required \u2014 pass the user's feedback text.",
|
|
21406
|
+
hint: "Ask the user what they'd like to tell the Leadbay team, then call again with their words in `message`."
|
|
21407
|
+
};
|
|
21408
|
+
}
|
|
21409
|
+
const message = text.length > MESSAGE_MAX ? `${text.slice(0, MESSAGE_MAX - 1)}\u2026` : text;
|
|
21410
|
+
if (!ctx?.sendFeedback) {
|
|
21411
|
+
return {
|
|
21412
|
+
sent: false,
|
|
21413
|
+
message: "Feedback could not be sent from this client (feedback delivery isn't available here). Let the user know it wasn't delivered.",
|
|
21414
|
+
_meta: { region: client.region }
|
|
21415
|
+
};
|
|
21416
|
+
}
|
|
21417
|
+
const errorId = typeof params.associated_error_id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(params.associated_error_id) ? params.associated_error_id : void 0;
|
|
21418
|
+
const sent = await ctx.sendFeedback(message, {
|
|
21419
|
+
...errorId ? { associatedEventId: errorId } : {}
|
|
21420
|
+
});
|
|
21421
|
+
return {
|
|
21422
|
+
sent,
|
|
21423
|
+
message: sent ? "Sent to the Leadbay team \u2014 thanks for the feedback." : (
|
|
21424
|
+
// `sent:false` means the bounded flush didn't confirm within the
|
|
21425
|
+
// window — the envelope may still drain on shutdown. Don't assert it
|
|
21426
|
+
// failed (that trains users to re-send and spam the inbox).
|
|
21427
|
+
"Delivery not confirmed \u2014 it may still reach the Leadbay team. Avoid re-sending unless the user wants to."
|
|
21428
|
+
),
|
|
21429
|
+
_meta: { region: client.region }
|
|
21430
|
+
};
|
|
21431
|
+
}
|
|
21432
|
+
};
|
|
21433
|
+
}
|
|
21434
|
+
});
|
|
21435
|
+
|
|
21243
21436
|
// ../core/dist/index.js
|
|
21244
21437
|
var dist_exports = {};
|
|
21245
21438
|
__export(dist_exports, {
|
|
@@ -21370,6 +21563,7 @@ __export(dist_exports, {
|
|
|
21370
21563
|
scanPortfolioSignals: () => scanPortfolioSignals,
|
|
21371
21564
|
seedCandidates: () => seedCandidates,
|
|
21372
21565
|
selectLeads: () => selectLeads,
|
|
21566
|
+
sendFeedback: () => sendFeedback,
|
|
21373
21567
|
setActiveLens: () => setActiveLens,
|
|
21374
21568
|
setEpilogueStatus: () => setEpilogueStatus,
|
|
21375
21569
|
setPushback: () => setPushback,
|
|
@@ -21481,6 +21675,7 @@ var init_dist = __esm({
|
|
|
21481
21675
|
init_answer_clarification();
|
|
21482
21676
|
init_report_outreach();
|
|
21483
21677
|
init_report_friction();
|
|
21678
|
+
init_send_feedback();
|
|
21484
21679
|
init_bulk_store();
|
|
21485
21680
|
agentMemoryTools = [
|
|
21486
21681
|
agentMemoryRecall,
|
|
@@ -21612,6 +21807,13 @@ var init_dist = __esm({
|
|
|
21612
21807
|
refinePrompt,
|
|
21613
21808
|
answerClarification,
|
|
21614
21809
|
reportOutreach,
|
|
21810
|
+
// sendFeedback is granular-shaped (a single call to the telemetry seam →
|
|
21811
|
+
// Sentry.captureFeedback, same inbox as the web app's feedback form), so it
|
|
21812
|
+
// lives in tools/, NOT composite/. Registered here (not advanced-gated) so
|
|
21813
|
+
// users can send feedback in-conversation without LEADBAY_MCP_ADVANCED.
|
|
21814
|
+
// Write-gated since it sends data outward. Not in COMPOSITE_FILE_TOOL_NAMES,
|
|
21815
|
+
// so _triggered_by is optional (no orchestration / mandatory-intent-trace).
|
|
21816
|
+
sendFeedback,
|
|
21615
21817
|
importLeads,
|
|
21616
21818
|
importAndQualify,
|
|
21617
21819
|
// Contact management (product#3703) — each is a single-call relay, so
|
|
@@ -23081,6 +23283,7 @@ var NOOP_TELEMETRY = {
|
|
|
23081
23283
|
},
|
|
23082
23284
|
captureException: () => {
|
|
23083
23285
|
},
|
|
23286
|
+
captureFeedback: async () => false,
|
|
23084
23287
|
captureUpdateCheck: () => {
|
|
23085
23288
|
},
|
|
23086
23289
|
captureUpdatePrompted: () => {
|
|
@@ -23335,6 +23538,41 @@ function initTelemetry(opts) {
|
|
|
23335
23538
|
logger?.warn?.(`sentry captureException failed: ${e?.message ?? e}`);
|
|
23336
23539
|
}
|
|
23337
23540
|
},
|
|
23541
|
+
async captureFeedback(message, opts2) {
|
|
23542
|
+
if (!sentryReady) return false;
|
|
23543
|
+
const trimmed = (message ?? "").trim();
|
|
23544
|
+
if (!trimmed) return false;
|
|
23545
|
+
if (identityPromise) {
|
|
23546
|
+
let waitTimer;
|
|
23547
|
+
try {
|
|
23548
|
+
await Promise.race([
|
|
23549
|
+
identityPromise,
|
|
23550
|
+
new Promise((resolve) => {
|
|
23551
|
+
waitTimer = setTimeout(resolve, 2e3);
|
|
23552
|
+
})
|
|
23553
|
+
]);
|
|
23554
|
+
} catch {
|
|
23555
|
+
} finally {
|
|
23556
|
+
if (waitTimer) clearTimeout(waitTimer);
|
|
23557
|
+
}
|
|
23558
|
+
}
|
|
23559
|
+
try {
|
|
23560
|
+
Sentry.captureFeedback({
|
|
23561
|
+
message: trimmed,
|
|
23562
|
+
...me?.name ? { name: me.name } : {},
|
|
23563
|
+
...me?.email ? { email: me.email } : {},
|
|
23564
|
+
...opts2?.associatedEventId ? { associatedEventId: opts2.associatedEventId } : {}
|
|
23565
|
+
});
|
|
23566
|
+
const flushed = await Sentry.flush(4e3);
|
|
23567
|
+
if (!flushed) {
|
|
23568
|
+
logger?.warn?.("sentry feedback flush timed out (event may be buffered)");
|
|
23569
|
+
}
|
|
23570
|
+
return flushed;
|
|
23571
|
+
} catch (e) {
|
|
23572
|
+
logger?.warn?.(`sentry captureFeedback failed: ${e?.message ?? e}`);
|
|
23573
|
+
return false;
|
|
23574
|
+
}
|
|
23575
|
+
},
|
|
23338
23576
|
async shutdown() {
|
|
23339
23577
|
const tasks = [];
|
|
23340
23578
|
if (posthog) tasks.push(posthog.shutdown(2e3).catch(() => void 0));
|
|
@@ -24207,7 +24445,11 @@ function buildServer(client, opts = {}) {
|
|
|
24207
24445
|
notificationsInbox: opts.notificationsInbox,
|
|
24208
24446
|
signal: extra.signal,
|
|
24209
24447
|
progress,
|
|
24210
|
-
elicit
|
|
24448
|
+
elicit,
|
|
24449
|
+
// Route leadbay_send_feedback to Sentry's feedback inbox (same place
|
|
24450
|
+
// the web app's form lands). NOOP_TELEMETRY returns false, so the
|
|
24451
|
+
// tool reports honestly when telemetry is off.
|
|
24452
|
+
sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
|
|
24211
24453
|
});
|
|
24212
24454
|
maybeAttachUpdate(name, result);
|
|
24213
24455
|
maybeAttachNotifications(result);
|
|
@@ -25648,7 +25890,7 @@ var OAUTH_BASE_URLS = {
|
|
|
25648
25890
|
fr: "https://staging.api.leadbay.app"
|
|
25649
25891
|
}
|
|
25650
25892
|
};
|
|
25651
|
-
var VERSION = "0.19.
|
|
25893
|
+
var VERSION = "0.19.3";
|
|
25652
25894
|
var HELP = `
|
|
25653
25895
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
25654
25896
|
|
|
@@ -25923,7 +26165,7 @@ async function resolveClientFromEnv(logger) {
|
|
|
25923
26165
|
logger.info?.("Auto-detecting region via /users/me on us and fr...");
|
|
25924
26166
|
const probe = async (region) => {
|
|
25925
26167
|
const c = createClient({ token, region });
|
|
25926
|
-
await c.request("GET", "/users/me");
|
|
26168
|
+
await c.request("GET", "/users/me", void 0, { retryOn401: false });
|
|
25927
26169
|
return c;
|
|
25928
26170
|
};
|
|
25929
26171
|
try {
|
package/dist/http-server.js
CHANGED
|
@@ -1519,13 +1519,40 @@ var LeadbayClient = class {
|
|
|
1519
1519
|
if (next)
|
|
1520
1520
|
next();
|
|
1521
1521
|
}
|
|
1522
|
-
|
|
1522
|
+
// Leadbay tokens don't expire, so a 401 is almost always a transient
|
|
1523
|
+
// server-side blip. Retry the request ONCE before surfacing it — a single
|
|
1524
|
+
// retry clears the vast majority of these without the agent ever seeing an
|
|
1525
|
+
// error. If the retry also 401s, it's a real Leadbay-side problem and the
|
|
1526
|
+
// error envelope says so.
|
|
1527
|
+
//
|
|
1528
|
+
// Arrow-function field so `this` stays bound even when the method is passed
|
|
1529
|
+
// as a bare reference (see request()'s ternary). Retries are GET-ONLY: a 401
|
|
1530
|
+
// on a write (POST/PUT/DELETE) may arrive AFTER the mutation already committed
|
|
1531
|
+
// server-side, so blindly re-sending it would double-execute the write. Reads
|
|
1532
|
+
// are idempotent, so retrying them is safe. The 250ms backoff releases the
|
|
1533
|
+
// concurrency slot first (release → sleep → re-acquire) so a wave of 401s
|
|
1534
|
+
// doesn't pin all MAX_CONCURRENT slots in setTimeout and stall the queue.
|
|
1535
|
+
httpsRequestWithRetry = async (method, url, headers, body) => {
|
|
1536
|
+
const res = await httpsRequest(method, url, headers, body);
|
|
1537
|
+
if (res.status === 401 && method.toUpperCase() === "GET") {
|
|
1538
|
+
this.releaseSemaphore();
|
|
1539
|
+
try {
|
|
1540
|
+
await new Promise((r) => setTimeout(r, 250));
|
|
1541
|
+
} finally {
|
|
1542
|
+
await this.acquireSemaphore();
|
|
1543
|
+
}
|
|
1544
|
+
return httpsRequest(method, url, headers, body);
|
|
1545
|
+
}
|
|
1546
|
+
return res;
|
|
1547
|
+
};
|
|
1548
|
+
async request(method, path, body, opts) {
|
|
1523
1549
|
if (process.env.LEADBAY_MOCK === "1") {
|
|
1524
1550
|
return this.mockRequest(method, path, body);
|
|
1525
1551
|
}
|
|
1526
1552
|
if (!this.token) {
|
|
1527
1553
|
throw this.makeError("NOT_AUTHENTICATED", "Not logged in to Leadbay", "Set LEADBAY_TOKEN in your MCP client config, or run: npx -y @leadbay/mcp install --email <you> --region <us|fr>", path);
|
|
1528
1554
|
}
|
|
1555
|
+
const retryOn401 = opts?.retryOn401 !== false;
|
|
1529
1556
|
await this.acquireSemaphore();
|
|
1530
1557
|
try {
|
|
1531
1558
|
const url = `${this._baseUrl}/1.5${path}`;
|
|
@@ -1535,7 +1562,7 @@ var LeadbayClient = class {
|
|
|
1535
1562
|
if (body) {
|
|
1536
1563
|
headers["Content-Type"] = "application/json";
|
|
1537
1564
|
}
|
|
1538
|
-
const res = await httpsRequest(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1565
|
+
const res = await (retryOn401 ? this.httpsRequestWithRetry : httpsRequest)(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1539
1566
|
this._lastMeta = {
|
|
1540
1567
|
region: this._region,
|
|
1541
1568
|
endpoint: `${method} ${path}`,
|
|
@@ -1570,7 +1597,7 @@ var LeadbayClient = class {
|
|
|
1570
1597
|
if (body) {
|
|
1571
1598
|
headers["Content-Type"] = "application/json";
|
|
1572
1599
|
}
|
|
1573
|
-
const res = await
|
|
1600
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body ? JSON.stringify(body) : void 0);
|
|
1574
1601
|
this._lastMeta = {
|
|
1575
1602
|
region: this._region,
|
|
1576
1603
|
endpoint: `${method} ${path}`,
|
|
@@ -1603,7 +1630,7 @@ var LeadbayClient = class {
|
|
|
1603
1630
|
Authorization: `Bearer ${this.token}`,
|
|
1604
1631
|
"Content-Type": contentType
|
|
1605
1632
|
};
|
|
1606
|
-
const res = await
|
|
1633
|
+
const res = await this.httpsRequestWithRetry(method, url, headers, body);
|
|
1607
1634
|
this._lastMeta = {
|
|
1608
1635
|
region: this._region,
|
|
1609
1636
|
endpoint: `${method} ${path}`,
|
|
@@ -1686,7 +1713,7 @@ var LeadbayClient = class {
|
|
|
1686
1713
|
}
|
|
1687
1714
|
const retryAfter = parseRetryAfter(headers["retry-after"]);
|
|
1688
1715
|
if (status === 401) {
|
|
1689
|
-
return this.makeError("AUTH_EXPIRED", "
|
|
1716
|
+
return this.makeError("AUTH_EXPIRED", "Leadbay rejected this request (401)", "Leadbay tokens don't expire on a timer, so this isn't a stale token. A 401 here is usually a Leadbay-side hiccup, but can also mean the user logged out. Try again shortly; if it persists, offer to report it to the team.", endpoint, null, status);
|
|
1690
1717
|
}
|
|
1691
1718
|
if (status === 429 || status === 402 || parsed?.error === "quota_exceeded" || parsed?.error?.code === "quota_exceeded") {
|
|
1692
1719
|
const hintBase = retryAfter ? `Wait ${retryAfter}s before retrying` : "Wait, then retry";
|
|
@@ -9546,6 +9573,88 @@ WHEN NOT TO USE: in normal flow \u2014 leadbay_enrich_titles wraps select \u2192
|
|
|
9546
9573
|
|
|
9547
9574
|
This tool MUTATES state. The caller (agent or human-in-the-loop) is responsible for confirming intent before invocation; the MCP server does not soft-prompt for confirmation. See \`annotations.destructiveHint\`.
|
|
9548
9575
|
`;
|
|
9576
|
+
var leadbay_send_feedback = `## WHEN TO USE
|
|
9577
|
+
|
|
9578
|
+
Trigger phrases: "send feedback", "I want to report a bug", "tell the Leadbay team", "let Leadbay know", "give feedback", "report this to support", "I have a feature request".
|
|
9579
|
+
|
|
9580
|
+
**Memory:** recall + capture via \`leadbay_agent_memory_*\` tools.
|
|
9581
|
+
|
|
9582
|
+
Do NOT use for: "no, I meant / still nothing / ugh" \u2192 \`leadbay_report_friction\`; "log the email I sent" \u2192 \`leadbay_report_outreach\`.
|
|
9583
|
+
|
|
9584
|
+
Prefer when: the user explicitly wants the Leadbay TEAM to receive a message they authored \u2014 or accepts your offer to report an error. For silent, agent-detected friction signals use leadbay_report_friction instead.
|
|
9585
|
+
|
|
9586
|
+
Examples that SHOULD invoke this tool:
|
|
9587
|
+
- "Send feedback to the team: the lead scores feel off this week."
|
|
9588
|
+
- "Can you report a bug? Pulling leads in Lyon returns nothing."
|
|
9589
|
+
- "Tell Leadbay I'd love a way to schedule my morning check-in."
|
|
9590
|
+
|
|
9591
|
+
Examples that should NOT invoke this tool (sound similar, route elsewhere):
|
|
9592
|
+
- "No, I meant Wisconsin not Wyoming."
|
|
9593
|
+
- "I emailed Acme \u2014 log that outreach."
|
|
9594
|
+
- "Thumbs down on this lead."
|
|
9595
|
+
|
|
9596
|
+
## RENDER (quick)
|
|
9597
|
+
|
|
9598
|
+
Confirm the exact wording with the user BEFORE calling (this is sent to
|
|
9599
|
+
the team). After sending, show a one-line confirmation from the result's
|
|
9600
|
+
\`message\` (e.g. "\u2713 Sent to the Leadbay team"). If \`sent\` is false, tell
|
|
9601
|
+
the user it could NOT be delivered \u2014 never imply it was sent.
|
|
9602
|
+
|
|
9603
|
+
---
|
|
9604
|
+
|
|
9605
|
+
Deliver a user-authored message to the Leadbay team's feedback inbox \u2014 the same
|
|
9606
|
+
destination as the web app's feedback form. **You do not write the feedback;
|
|
9607
|
+
the user does.** Capture their words, confirm the phrasing, then send.
|
|
9608
|
+
|
|
9609
|
+
## Parameters
|
|
9610
|
+
- \`message\` (required) \u2014 the user's feedback, in their own words. Confirm it
|
|
9611
|
+
with the user before sending. Cap 4000 chars.
|
|
9612
|
+
- \`associated_error_id\` (optional) \u2014 a Sentry event id to attach the feedback
|
|
9613
|
+
to (e.g. the id surfaced by an error the user just hit), so the team sees the
|
|
9614
|
+
feedback on that exact issue.
|
|
9615
|
+
|
|
9616
|
+
## When a tool errors \u2014 OFFER, don't auto-send
|
|
9617
|
+
When a Leadbay tool returns an error and the user might want the team to know,
|
|
9618
|
+
you may OFFER: *"Want me to send feedback about this to the Leadbay team?"*
|
|
9619
|
+
- Send ONLY if the user says yes AND gives (or approves) a message.
|
|
9620
|
+
- Never send feedback the user didn't author or approve.
|
|
9621
|
+
- If an error event id is available, pass it as \`associated_error_id\`.
|
|
9622
|
+
|
|
9623
|
+
## Result
|
|
9624
|
+
- \`sent: true\` \u2192 it reached the team. Show the confirmation from \`message\`.
|
|
9625
|
+
- \`sent: false\` \u2192 delivery wasn't possible (feedback not available on this
|
|
9626
|
+
client). Tell the user it was NOT sent. Do not claim success.
|
|
9627
|
+
|
|
9628
|
+
This is the only "talk to the Leadbay team" tool. It does not mutate any
|
|
9629
|
+
Leadbay data. For silent friction signals you detect yourself, use
|
|
9630
|
+
\`leadbay_report_friction\` instead.
|
|
9631
|
+
|
|
9632
|
+
## NEXT STEPS \u2014 after sending feedback
|
|
9633
|
+
|
|
9634
|
+
**ALWAYS render NEXT STEPS via your host's next-step widget.** Use whichever is in your tool set \u2014 the NAME and SCHEMA differ: **\`ask_user_input_v0\`** (Claude chat / ChatGPT) takes plain-string options with \`type:"single_select"\`; **\`AskUserQuestion\`** (Claude cowork / Claude Code) takes object options \`{label, description}\` plus a required short \`header\` (\u226412 chars) and \`multiSelect\`, NO \`type\` field, and never add an "Other" option (the host adds it). Match the schema to the tool you actually have \u2014 the wrong schema fails silently and you fall back to prose. Prose bullets are the fallback ONLY when NEITHER widget exists. Any turn that would end with a choice must be the widget \u2014 the widget IS the question.
|
|
9635
|
+
|
|
9636
|
+
**If the tool result carries a \`next_steps\` object, that is the source of truth \u2014 use it directly.** Each option has a short \`.label\` (\u22645 words) and a full \`.description\`. Map \`next_steps.options[]\` into your host widget VERBATIM and in order: for \`AskUserQuestion\` (cowork / Claude Code) pass each as \`{label, description}\`; for \`ask_user_input_v0\` (Claude chat / ChatGPT, string options only) pass each option's \`.description\` as the string (it's the full sentence). Do NOT reword, reorder, drop, or prose-ify them \u2014 they're built deterministically by the server so the offer (incl. the artifact option at position 0) fires every time. Fall back to the table below only when there is NO \`next_steps\` field.
|
|
9637
|
+
|
|
9638
|
+
**One exception \u2014 skip the widget** when the user's original message contained a complete sequential instruction chain ("show me X and then do Y") AND all stated steps have been completed. In that case, end with STOP directly \u2014 the user stated their full plan and does not need a "what next?" prompt.
|
|
9639
|
+
- Skip example: "Show me today's leads and then research the top one for me." \u2192 after research completes, emit STOP without the widget.
|
|
9640
|
+
- Do NOT skip for: plain requests ("show me today's leads", "run my check-in"), recurring-language requests ("I do this every day"), or requests where only one action was stated.
|
|
9641
|
+
|
|
9642
|
+
Pick 2\u20134 rows from the (Observation, Suggest, Calls) table below most relevant to the response, then call your host's widget with ITS schema (per the schema rules above \u2014 wrong schema fails silently):
|
|
9643
|
+
- \`ask_user_input_v0\`: \`{questions:[{question,type:"single_select",options:["<Suggest 1>","<Suggest 2>"]}]}\`
|
|
9644
|
+
- \`AskUserQuestion\`: \`{questions:[{question,header:"Next step",multiSelect:false,options:[{label:"<\u22645 words>",description:"<Suggest 1>"}]}]}\`
|
|
9645
|
+
|
|
9646
|
+
User picks \u2192 call the matching \`Calls\` tool. Constraints: 2\u20134 mutually-exclusive options, AskUserQuestion labels \u22645 words (full text in \`description\`), max 3 questions. Table stays internal; never recite it.
|
|
9647
|
+
|
|
9648
|
+
---
|
|
9649
|
+
|
|
9650
|
+
|
|
9651
|
+
|
|
9652
|
+
| Observation | Suggest | Calls |
|
|
9653
|
+
|----------------------------------------------|------------------------------------------------------|-----------------------------------------|
|
|
9654
|
+
| \`sent == true\` | "Anything else you'd like to flag to the team?" | leadbay_send_feedback(message) |
|
|
9655
|
+
| \`sent == false\` | "It didn't go through \u2014 want to try sending again?" | leadbay_send_feedback(message) |
|
|
9656
|
+
| Feedback was about an error the user hit | "Want me to retry the action that failed?" | (re-call the tool that errored) |
|
|
9657
|
+
`;
|
|
9549
9658
|
var leadbay_set_active_lens = `Mark a lens as last-used. Subsequent \`/me\` reads return it as \`last_requested_lens\`, so all composite tools default to it.
|
|
9550
9659
|
|
|
9551
9660
|
WHEN TO USE: after the user explicitly switched contexts (e.g. created a new lens via leadbay_create_lens).
|
|
@@ -20749,6 +20858,83 @@ var reportFriction = {
|
|
|
20749
20858
|
}
|
|
20750
20859
|
};
|
|
20751
20860
|
|
|
20861
|
+
// ../core/dist/tools/send-feedback.js
|
|
20862
|
+
var MESSAGE_MAX = 4e3;
|
|
20863
|
+
var sendFeedback = {
|
|
20864
|
+
name: "leadbay_send_feedback",
|
|
20865
|
+
annotations: {
|
|
20866
|
+
title: "Send feedback to the Leadbay team",
|
|
20867
|
+
readOnlyHint: false,
|
|
20868
|
+
destructiveHint: false,
|
|
20869
|
+
idempotentHint: false,
|
|
20870
|
+
openWorldHint: true
|
|
20871
|
+
},
|
|
20872
|
+
description: leadbay_send_feedback,
|
|
20873
|
+
// Write-gated: it sends data outward to the Leadbay team. Registered in
|
|
20874
|
+
// compositeWriteTools (default-on since 0.3.0).
|
|
20875
|
+
write: true,
|
|
20876
|
+
inputSchema: {
|
|
20877
|
+
type: "object",
|
|
20878
|
+
properties: {
|
|
20879
|
+
message: {
|
|
20880
|
+
type: "string",
|
|
20881
|
+
description: "The user's feedback, in their own words. Confirm the wording with the user BEFORE calling \u2014 this is sent to the Leadbay team. Cap 4000 chars."
|
|
20882
|
+
},
|
|
20883
|
+
associated_error_id: {
|
|
20884
|
+
type: "string",
|
|
20885
|
+
description: "Optional: a Sentry event id to attach this feedback to (e.g. the id from an error the user just hit), so the team sees the feedback on that exact issue."
|
|
20886
|
+
}
|
|
20887
|
+
},
|
|
20888
|
+
required: ["message"],
|
|
20889
|
+
additionalProperties: false
|
|
20890
|
+
},
|
|
20891
|
+
outputSchema: {
|
|
20892
|
+
type: "object",
|
|
20893
|
+
description: "Whether the feedback reached the Leadbay team. `sent: true` means it landed in the team's inbox.",
|
|
20894
|
+
properties: {
|
|
20895
|
+
sent: { type: "boolean" },
|
|
20896
|
+
message: { type: "string" },
|
|
20897
|
+
_meta: {
|
|
20898
|
+
type: "object",
|
|
20899
|
+
properties: { region: { type: "string" } }
|
|
20900
|
+
}
|
|
20901
|
+
}
|
|
20902
|
+
},
|
|
20903
|
+
execute: async (client, params, ctx) => {
|
|
20904
|
+
const text = typeof params.message === "string" ? params.message.trim() : "";
|
|
20905
|
+
if (!text) {
|
|
20906
|
+
return {
|
|
20907
|
+
error: true,
|
|
20908
|
+
code: "BAD_INPUT",
|
|
20909
|
+
message: "message is required \u2014 pass the user's feedback text.",
|
|
20910
|
+
hint: "Ask the user what they'd like to tell the Leadbay team, then call again with their words in `message`."
|
|
20911
|
+
};
|
|
20912
|
+
}
|
|
20913
|
+
const message = text.length > MESSAGE_MAX ? `${text.slice(0, MESSAGE_MAX - 1)}\u2026` : text;
|
|
20914
|
+
if (!ctx?.sendFeedback) {
|
|
20915
|
+
return {
|
|
20916
|
+
sent: false,
|
|
20917
|
+
message: "Feedback could not be sent from this client (feedback delivery isn't available here). Let the user know it wasn't delivered.",
|
|
20918
|
+
_meta: { region: client.region }
|
|
20919
|
+
};
|
|
20920
|
+
}
|
|
20921
|
+
const errorId = typeof params.associated_error_id === "string" && /^[A-Za-z0-9_-]{1,64}$/.test(params.associated_error_id) ? params.associated_error_id : void 0;
|
|
20922
|
+
const sent = await ctx.sendFeedback(message, {
|
|
20923
|
+
...errorId ? { associatedEventId: errorId } : {}
|
|
20924
|
+
});
|
|
20925
|
+
return {
|
|
20926
|
+
sent,
|
|
20927
|
+
message: sent ? "Sent to the Leadbay team \u2014 thanks for the feedback." : (
|
|
20928
|
+
// `sent:false` means the bounded flush didn't confirm within the
|
|
20929
|
+
// window — the envelope may still drain on shutdown. Don't assert it
|
|
20930
|
+
// failed (that trains users to re-send and spam the inbox).
|
|
20931
|
+
"Delivery not confirmed \u2014 it may still reach the Leadbay team. Avoid re-sending unless the user wants to."
|
|
20932
|
+
),
|
|
20933
|
+
_meta: { region: client.region }
|
|
20934
|
+
};
|
|
20935
|
+
}
|
|
20936
|
+
};
|
|
20937
|
+
|
|
20752
20938
|
// ../core/dist/index.js
|
|
20753
20939
|
var agentMemoryTools = [
|
|
20754
20940
|
agentMemoryRecall,
|
|
@@ -20880,6 +21066,13 @@ var compositeWriteTools = [
|
|
|
20880
21066
|
refinePrompt,
|
|
20881
21067
|
answerClarification,
|
|
20882
21068
|
reportOutreach,
|
|
21069
|
+
// sendFeedback is granular-shaped (a single call to the telemetry seam →
|
|
21070
|
+
// Sentry.captureFeedback, same inbox as the web app's feedback form), so it
|
|
21071
|
+
// lives in tools/, NOT composite/. Registered here (not advanced-gated) so
|
|
21072
|
+
// users can send feedback in-conversation without LEADBAY_MCP_ADVANCED.
|
|
21073
|
+
// Write-gated since it sends data outward. Not in COMPOSITE_FILE_TOOL_NAMES,
|
|
21074
|
+
// so _triggered_by is optional (no orchestration / mandatory-intent-trace).
|
|
21075
|
+
sendFeedback,
|
|
20883
21076
|
importLeads,
|
|
20884
21077
|
importAndQualify,
|
|
20885
21078
|
// Contact management (product#3703) — each is a single-call relay, so
|
|
@@ -21052,6 +21245,7 @@ var NOOP_TELEMETRY = {
|
|
|
21052
21245
|
},
|
|
21053
21246
|
captureException: () => {
|
|
21054
21247
|
},
|
|
21248
|
+
captureFeedback: async () => false,
|
|
21055
21249
|
captureUpdateCheck: () => {
|
|
21056
21250
|
},
|
|
21057
21251
|
captureUpdatePrompted: () => {
|
|
@@ -21907,7 +22101,11 @@ function buildServer(client, opts = {}) {
|
|
|
21907
22101
|
notificationsInbox: opts.notificationsInbox,
|
|
21908
22102
|
signal: extra.signal,
|
|
21909
22103
|
progress,
|
|
21910
|
-
elicit
|
|
22104
|
+
elicit,
|
|
22105
|
+
// Route leadbay_send_feedback to Sentry's feedback inbox (same place
|
|
22106
|
+
// the web app's form lands). NOOP_TELEMETRY returns false, so the
|
|
22107
|
+
// tool reports honestly when telemetry is off.
|
|
22108
|
+
sendFeedback: (message, fbOpts) => telemetry.captureFeedback(message, fbOpts)
|
|
21911
22109
|
});
|
|
21912
22110
|
maybeAttachUpdate(name, result);
|
|
21913
22111
|
maybeAttachNotifications(result);
|
|
@@ -22205,7 +22403,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
22205
22403
|
}
|
|
22206
22404
|
|
|
22207
22405
|
// src/http-server.ts
|
|
22208
|
-
var VERSION = true ? "0.19.
|
|
22406
|
+
var VERSION = true ? "0.19.3" : "0.0.0-dev";
|
|
22209
22407
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
22210
22408
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
22211
22409
|
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.19.
|
|
1469
|
+
VERSION = "0.19.3";
|
|
1470
1470
|
PORT = Number(process.env.LEADBAY_INSTALLER_PORT ?? 0);
|
|
1471
1471
|
sessions = /* @__PURE__ */ new Map();
|
|
1472
1472
|
OAUTH_BASE_URLS = {
|
package/dist/installer-gui.js
CHANGED
|
@@ -873,7 +873,7 @@ async function oauthLogin(opts) {
|
|
|
873
873
|
}
|
|
874
874
|
|
|
875
875
|
// installer/installer-gui.ts
|
|
876
|
-
var VERSION = "0.19.
|
|
876
|
+
var VERSION = "0.19.3";
|
|
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.19.
|
|
3
|
+
"version": "0.19.3",
|
|
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",
|