Skip to content

feat(core): retrieve org qualification methods + per-lead custom fields#113

Open
ArtyETH06 wants to merge 16 commits into
mainfrom
ArtyETH06/mcp-export-qualification-questions-custom-fields
Open

feat(core): retrieve org qualification methods + per-lead custom fields#113
ArtyETH06 wants to merge 16 commits into
mainfrom
ArtyETH06/mcp-export-qualification-questions-custom-fields

Conversation

@ArtyETH06

@ArtyETH06 ArtyETH06 commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Read + modify for qualification methods and CRM custom fields over MCP.

Tools

Capability Retrieve Modify
Qualification questions leadbay_get_qualification_methods leadbay_set_qualification_methods (add/remove/replace)
Custom-field definitions leadbay_list_mappable_fields leadbay_create_custom_field / update / delete
Per-lead custom-field values leadbay_get_lead_custom_fields via import (leadbay_import_leads)

All on the default surface. Modify tools are write-gated and admin-scoped server-side (every user is admin of their own org, so they work for everyone). Removals/deletes require confirm:true; set_qualification_methods enforces the backend's 5-question cap. Custom-field config is sanitized per type (and tolerates a stringified config) so the backend's strict deserializer doesn't reject it.

Notes

  • No direct per-lead custom-field value write endpoint exists server-side — values flow through the import pipeline (already exposed).
  • New unit tests + output-schema conformance cases; eval contracts in WORKFLOWS.md.
  • Depends on backend leadbay/backend#1906 (merged) so OAuth tokens are accepted on the org + custom-field routes.

Verification

  • pnpm -r build && pnpm -r test && pnpm -r typecheck green.
  • Eval workflows (qual methods + custom fields, read + write) pass on the eval's u.-token path.
  • Full OAuth path (the installed .dxt) verified manually on staging end-to-end: read + add/remove qualification questions, and create → rename/retype (PRICE/EUR) → delete custom fields, all working.

Closes https://github.com/leadbay/product/issues/3768

Two new default-surface read composites resolving product#3768:

- leadbay_get_qualification_methods — returns the org's AI-agent
  qualification questions (the "qualification methods") with created_at +
  lang, plus the caller's is_admin flag and a web-app edit hint. Read-only
  for everyone; the API exposes no write endpoint for these questions.
- leadbay_get_lead_custom_fields — returns the CRM custom-field VALUES on a
  single lead as {id, name, type, value}. The lead payload embeds each
  field's definition (verified live), so no /crm/custom_fields join is
  needed; the catalog is fetched only as a defensive fallback. Fires
  LEAD_SEEN on read, in parity with the research tools.

Both were already partially reachable only via the ADVANCED-gated
get_taste_profile / list_mappable_fields (definitions only); the per-lead
custom_fields array was silently dropped (untyped on LeadPayload).

Adds LeadCustomFieldEntry + LeadPayload.custom_fields, two routing
templates, WORKFLOWS.md rows, and registry/audit entries
(COMPOSITE_FILE_TOOL_NAMES, TOOLS_WITH_ROUTING, output-schema
conformance CASES). Verified live end-to-end on the test org.

Co-Authored-By: Claude <noreply@anthropic.com>
@ArtyETH06 ArtyETH06 self-assigned this Jun 19, 2026
ArtyETH06 and others added 15 commits June 19, 2026 11:51
…m-fields

Adds the machine-readable yaml expected + yaml scenario blocks for
workflows #30 (Org qualification methods) and #31 (Per-lead custom-field
values) so /eval can run them. Live run: both 5/5/5/5, invariants green.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds the modify surface for custom fields, completing the read+write set:

- leadbay_update_custom_field — rename and/or change type+config in place
  (POST /crm/custom_fields/{id} → 204, verified live). Partial-merge over the
  current definition so rename-only keeps the type and retype-only keeps the
  name. Same EXTERNAL_ID/PRICE config validation as create.
- leadbay_delete_custom_field — DELETE /crm/custom_fields/{id} → 204.
  Destructive (drops the field's values from every lead), so it requires an
  explicit confirm:true; without it the tool previews the field and does
  nothing. destructiveHint:true.

Both granular-shaped, registered in compositeWriteTools (default surface,
write-gated like create_custom_field). Conformance CASES + new unit test
files added; live update+delete round-trip verified.

Qualification methods intentionally get NO modify tool — the API exposes no
write endpoint for ai_agent_questions (every verb 404s); they stay read-only
with the web-app edit hint.

Co-Authored-By: Claude <noreply@anthropic.com>
The qualification-questions write endpoint DOES exist — it's the org root
POST /organizations/{orgId} with {ai_agent_lead_questions:[string,...]} → 204
(full-replace; confirmed live with the web app's exact payload). My earlier
404 was probing the wrong path (/ai_agent_questions).

Adds leadbay_set_qualification_methods:
- set / add / remove modes; reads the current list and posts the full
  resulting array (matches the full-replace endpoint).
- shrinking the list requires confirm:true (removing a question changes how
  every lead is scored); add does not.
- enforces the backend cap (max 5 questions) with an actionable hint instead
  of a raw 400.
- invalidates the taste-profile cache after a write.

Live-verified: add hits the 5-cap correctly; remove-without-confirm previews;
remove+restore round-trips cleanly (account left as found).

This supersedes the earlier "no write endpoint" note — qualification methods
are now fully modifiable, alongside custom fields.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds workflow #32 (Modify qualification methods) exercising
leadbay_set_qualification_methods, and drops the now-stale "read-only"
note from #30. Scenario adds a question against an org already at the
5-question cap — exercises the write tool + cap handling while mutating
nothing. Live run: 5/5/5/5, invariants 2/2, org questions byte-for-byte
unchanged after.

Co-Authored-By: Claude <noreply@anthropic.com>
Adds workflow #33 (Modify custom fields) exercising the full
create → update → delete lifecycle, including the delete confirm-gate.
Self-contained: the scenario creates a throwaway field, renames it, and
deletes it with confirm, so the run cleans up after itself. Live run:
5/5/5/5, invariants 3/3, catalog byte-for-byte unchanged after (only the
pre-existing field remains).

Co-Authored-By: Claude <noreply@anthropic.com>
The read tool's description + admin hint still said editing happens in the
Leadbay web app with "no MCP edit tool yet" — stale since
leadbay_set_qualification_methods landed. Repoint the hint, is_admin doc,
description, and rendering footnote at leadbay_set_qualification_methods.

Confirmed against the backend repo: both modify surfaces (qualification
questions + custom fields) are authenticate("admin"), i.e. org-admin only —
which every user is for their own org, so the modify tools work for everyone
in practice. No per-lead custom-field VALUE write route exists (values flow
through the import pipeline only).

Co-Authored-By: Claude <noreply@anthropic.com>
The previous wf32 scenario ("add a question" against a capped list) let the
agent self-confirm a destructive removal to make room, mutating live data.
Rewrite it as a remove-then-readd round-trip on a single named question: it
nets back to the original set, exercises remove(confirm)+add, and never leaves
the org mutated. Live re-run: 5/5/5/5, question set byte-for-byte unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
…qualification-questions-custom-fields

# Conflicts:
#	WORKFLOWS.md
#	packages/core/src/composite/_composite-file-names.ts
…n Wayland/Snap

OAuth-on-install (.dxt) spawned xdg-open "successfully" but no tab opened on
Wayland + Snap-browser setups (the Ubuntu default). Claude Desktop strips
XDG_RUNTIME_DIR — which broke the existing WAYLAND_DISPLAY reconstruction (it
reads that dir) — and strips DBUS_SESSION_BUS_ADDRESS, so a Snap/Flatpak
browser couldn't be reached and the launch silently no-op'd.

browserLaunchEnv now rebuilds XDG_RUNTIME_DIR from /run/user/<uid> when stripped,
then derives WAYLAND_DISPLAY (wayland-N socket) and DBUS_SESSION_BUS_ADDRESS
(<runtimeDir>/bus) from it. Existing values are never overridden. Confirmed
live: with the fix a browser tab opens from a fully-stripped env; without it,
nothing. New test file oauth-browser-env-wayland.test.ts.

Co-Authored-By: Claude <noreply@anthropic.com>
Follow-up to the XDG_RUNTIME_DIR/WAYLAND/DBUS reconstruction: the browser
still failed to open from Claude Desktop's stripped child env because an
X11/XWayland browser (Chrome/Brave/Electron) needs the X authority cookie to
connect to the display. Without XAUTHORITY the X server rejects the client
("Authorization required... Missing X server") and the browser segfaults —
xdg-open returns 0, but no tab opens.

browserLaunchEnv now injects XAUTHORITY from the Mutter Xwayland cookie at
<runtimeDir>/.mutter-Xwaylandauth.*, falling back to ~/.Xauthority. Existing
value never overridden. Confirmed live: with XAUTHORITY the browser launches
instead of segfaulting. Test coverage extended.

Co-Authored-By: Claude <noreply@anthropic.com>
Claude Desktop won't replace an installed extension with the same version
number, so the XAUTHORITY browser-open fix didn't take until the version
changed. Bumps package.json + server.json.

Co-Authored-By: Claude <noreply@anthropic.com>
leadbay_update_custom_field and leadbay_create_custom_field sent the config
object verbatim. The backend's per-type config models are strict
(PriceFieldConfig = {currency}, Date/DateTime = {format}, ExternalId =
{urlTemplate}, TEXT/NUMBER = none), so any extra key — a stale `format` left
from the previous type on a type CHANGE, both url_template + urlTemplate, or a
currency on a non-PRICE field — triggers a 500 "JSON deserialization error".
Reproduced live on staging: TEXT→PRICE with an over-broad config 500s.

Add sanitizeConfigForType (new _custom-field-config.ts) that narrows config to
exactly the key(s) the target type accepts, and apply it in both tools before
building the request body. Live-verified on staging: TEXT→PRICE/EUR now sends
{currency:"EUR"} and succeeds; full create→update→delete lifecycle clean.

Co-Authored-By: Claude <noreply@anthropic.com>
…ropped)

The agent passes config as a JSON STRING (e.g. config:'{"currency":"EUR"}'),
not an object. sanitizeConfigForType only read object properties, so a string
config yielded no currency → backend 400 "PRICE requires a currency config" on
a TEXT→PRICE update even though the rename landed. Observed live on staging.

sanitizeConfigForType now JSON-parses a string config before narrowing, and
both tools sanitize BEFORE the EXTERNAL_ID url_template check (so that check
sees the parsed value too). Live-verified: the exact stringified payload the
agent sent now narrows to {currency:"EUR"}.

Co-Authored-By: Claude <noreply@anthropic.com>
P2 — set_qualification_methods: gate confirm on the ACTUAL removed questions,
not on count. A remove+add or `set` that keeps the count the same still drops
a scoring question; the old `next.length < previousCount` check let that
through without confirm. Now computes the removed set and requires confirm
whenever any existing question would be dropped.

P2 — get_qualification_methods: fetch ai_agent_questions DIRECTLY instead of
via resolveTasteProfile (Promise.allSettled substitutes [] on a rejected
fetch). A transient backend/auth failure now surfaces as an error rather than
a false "no questions configured" that could lead a caller to overwrite real
questions.

P3 — create/update_custom_field: the tools parse a stringified config, so the
input schema now advertises it (type: ["object","string","null"]) and the
param types accept string — making the recovery path valid for schema-
validating clients.

New tests: same-count swap confirm gate, and fetch-failure-surfaces.

Co-Authored-By: Claude <noreply@anthropic.com>
The package ships CHANGELOG.md but it started at 0.23.0, so consumers
installing 0.23.1 had no release notes for the new qualification-method +
custom-field tools. Adds the 0.23.1 entry.

Co-Authored-By: Claude <noreply@anthropic.com>
@ArtyETH06 ArtyETH06 marked this pull request as ready for review June 22, 2026 22:23
@ArtyETH06 ArtyETH06 requested a review from milstan June 22, 2026 22:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant