LeadVal MCP server
An MCP server that lets any MCP-capable AI — Claude Desktop, Cursor, or your own agent — use LeadVal directly: notify LeadVal that a session is ready, poll its status, or do both and wait.
LeadVal's integration model is notify-then-poll: your AI Agents write a
finished conversation to LeadVal's raw lead storage, then this server notifies
LeadVal to score it, and you poll for the outcome. The conversation itself must
already exist under the sessionId you pass — this server triggers scoring, it
does not upload transcripts.
There are two LeadVal MCP servers. This is the writing one.
They do opposite jobs and authenticate differently, so pick by direction:
This package (leadval-mcp) |
The hosted endpoint | |
|---|---|---|
| Direction | Submits sessions for scoring | Reads scored leads, and registers your tools |
| Runs | Locally, next to your agent | On your LeadVal host, at POST /mcp |
| Auth | A val_key__… ingestion key you hold |
OAuth 2.1 — the customer approves your app |
| Scope | Your own workspace | Whichever workspace the person authorised |
| Install | npx -y leadval-mcp |
Point any MCP client at https://<your-host>/mcp |
Use this package for your own workspace's pipeline. Use the hosted endpoint when
you are building something other LeadVal customers install, so you never hold
their API key — see Connect an application in <your-host>/docs.
Nothing stops you running both: submit with this one, read with that one.
What the hosted endpoint offers (for comparison)
This package's tools are listed below. The hosted POST /mcp endpoint is a
separate, OAuth-authorized surface with its own tool set.
Reading — get_workspace, search_leads, get_lead, get_lead_brief,
get_lead_crm_status, list_lead_tags, list_crm_agents and
get_customer_profile.
Recognising a returning customer, offered only to a client the customer
approved for the identity:lookup scope, and only where LeadVal has enabled the
Identity Resolution API for that workspace — identity_lookup. You send the
identifying signals you have for the person in front of you; you receive
pointers to matching conversations and never their details.
Whether you may use a name is one field, on both surfaces. Every match row
carries nameUse — PERMITTED or WITHHOLD — with nameUseReason when it is
withheld, and get_customer_profile on the hosted endpoint returns the same
field with the same two values on each conversation. Read it on the row you are
acting on rather than deriving permission from verdict, band or
corroboratedBy, and note that the response's top-level constraintsFired
unions every candidate scored, including near misses — it can name a clamp
that a different person's row fired. Each row carries its own.
Three other answers mislead if read quickly:
retrieval.candidatesConsidered: 0 means nobody was retrieved to compare
against rather than that the person is new, retrieval.truncated: true means an
absent match is not evidence of absence, and corroboratedBy: [] means the
match rests on device signals alone — what a shared computer looks like.
corroboratedBy is evidence and tone, never permission: ["contact-phone"]
alongside SHARED_CONTACT_NO_NAME is one handset and one browser shared by a
household, because a phone identifies an address where an email identifies a
person. decision.activeGuards lists the protections in force.
Registering your own tools, offered only to a client the customer approved
for the tools:write scope:
| Tool | What it does |
|---|---|
submit_tool |
Describe one of your tools — its parameters, and which LeadVal fact each maps to — so LeadVal reads its calls as graded evidence on that workspace's leads. Returns a submission id and status: "PENDING". Idempotent on the contract, so re-sending an unchanged definition is free. |
verify_tool |
Does the tool you are shipping still match what was approved? Returns which field names and types differ — never a value. Run it at start-up and after any schema change. Read compared_against: on a tool still awaiting review it compares against what you submitted and returns "SUBMITTED", so matches: true alone does not mean live. |
list_tools |
Your workspace's registrations and where each one stands, including the reviewer's note on anything declined. |
A submission is a request, not a change. Nothing you submit affects a single lead until a LeadVal administrator reviews and approves it — approving one decides how every future lead in that workspace is read into evidence, which is why it is not yours to do. Approval also applies only to leads processed afterwards; a lead already scored is never re-read.
Which tools a client sees depends on the scopes the customer approved, so
tools/list is the authority rather than any list in a README. Full reference:
Developers → MCP → Hosted server in <your-host>/docs.
Tools
| Tool | What it does |
|---|---|
leadval_notify_session |
Queue a finished session for scoring (sessionId, processor). Idempotent. |
leadval_get_session_status |
Poll one session's conformance + processing state. Never returns PII/score/price. |
leadval_process_and_wait |
Notify, then poll until the session reaches a terminal state (or a timeout). |
leadval_identity_lookup |
Mid-conversation: does this workspace already know the person you are talking to? Returns pointers (leadId, customerProfileId), never customer data. Read nameUse on the row you act on (PERMITTED/WITHHOLD, with nameUseReason) — corroboratedBy is evidence and tone, never permission. |
Configuration
Two environment variables:
LEADVAL_BASE_URL— your LeadVal host, e.g.https://app.your-workspace.comLEADVAL_API_KEY— aval_key__…ingestion key. Mint one in Settings → API Playground. Keep it secret.
Claude Desktop
Add to claude_desktop_config.json:
{
"mcpServers": {
"leadval": {
"command": "npx",
"args": ["-y", "leadval-mcp"],
"env": {
"LEADVAL_BASE_URL": "https://app.your-workspace.com",
"LEADVAL_API_KEY": "val_key__your-key"
}
}
}
}
Cursor
Add to .cursor/mcp.json (or Cursor Settings → MCP):
{
"mcpServers": {
"leadval": {
"command": "npx",
"args": ["-y", "leadval-mcp"],
"env": {
"LEADVAL_BASE_URL": "https://app.your-workspace.com",
"LEADVAL_API_KEY": "val_key__your-key"
}
}
}
}
From source
npm install
npm run build
LEADVAL_BASE_URL=https://app.your-workspace.com LEADVAL_API_KEY=val_key__… node dist/index.js
Notes
Published to npm since 1.3.0.
npx -y leadval-mcpresolves, so the client configs above use it directly and nothing has to be built or path-wired. The "From source" section below is still correct and is what you want when you are changing the server itself.Production only. This server authenticates with an ingestion key, so it submits real sessions. The sandbox needs a logged-in session token as well — for sandbox testing, use the API Playground UI in the app.
This server notifies; it does not carry your data. The transcript and its metadata go to the document store; these tools tell LeadVal a session is finished and report on it. What you put in that metadata is what your customer can later use.
Extra metadata keys are useful, not ignored — and you are their only source. Your agent is the only thing in the conversation, so a fact that is not in the transcript and not in the metadata does not exist downstream. Anything you send beyond the required contract can be surfaced by the workspace itself, two ways over the same key:
- Custom Signals — the value shown on the lead as a field or badge, or totalled in the workspace's Analytics Hub. Evidence their team can read.
- Lead Tags — a rule on the same field, which is what alerts, CRM sync, AI automations and lead visibility all key off. This is the one that makes a lead do something.
Shape them to be reachable: shallow paths (≤ 5 segments), stringified once, scalar leaves, and stable values a rule can match with
equals—"PC-4471"works,"campaign PC-4471 (spring)"does not."metadata": { "sessionId": "…", "started": "…", "embedding": "…", // the standard contract "partnerCampaignId": "PC-4471" // yours }Four rules decide whether it is usable on the other side:
Send leaves, not trees {"tier":"vip"}stringified once is reachable aspreferences.tier; a deep tree is not (5 segments max).Keep values matchable Rules match with equals. Stable identifiers, not prose.Keep names and types stable Mapping is by path — renaming silently stops a configured signal from finding anything. Omit optional keys, don't blank them A rule can test presence alone, and an empty string still counts as present — so a key you always send but sometimes leave blank matches every lead. Omit it or send null.None of it is scored or priced: extraction runs after scoring, so sending more cannot make a lead worth more. Full detail in §6.11 of the Ingestion Specification, and on the Custom metadata page under Developers in
<your-host>/docs.Your tool calls are evidence — and they outrank the transcript. If your agent calls tools during the conversation, LeadVal turns those calls into graded facts and rebuilds the customer's Website Journey timeline from them. A conversation says what someone claimed; a tool call records what they did, in a structured field, so it is believed more.
What you control and what you don't is deliberate:
You decide LeadVal decides Which tools exist and what they take Whether a tool is recognised, and what its parameters mean When to call them, and what values to send How strongly the source is believed Emitting the result beside the call Whether a value is private and withheld before purchase Declaring the toolset the agent has Which values feed scoring, and which are recorded only Sending a field called
tierorpiichanges nothing — those come from the registry, never from your payload. Which is exactly why a buyer trusts them.Four habits materially change what a session is worth:
Emit both sides, back to back Send the call and its result as two consecutive turns naming the same tool, with nothing between them — that adjacency is what joins them. The result carries your system's own truth (price, availability, a qualification outcome), the more credible half. Do not try to join them with a correlation id: no such field survives the trip, and tool_call_idrenders as a stamp inside the tool body.Declare your full toolset metadata.function.tools— every tool the agent has, not only the ones it used. A tool turn asserts your agent did something and the transcript alone cannot prove it, so this is what lets LeadVal refuse turns your agent never made. Declare nothing and that check is off entirely. Any common shape is read: OpenAI chat or Responses, Anthropic, Bedrock, Gemini, or a bare name.Send raw values 450000, not"$450k"."+13055550142", not"(305) 555-0142 (mobile)". Formatting is lossy.Distinct tools for distinct actions Separate tools read as a real journey. One catch-all log_event(name, payload)reads as nothing and cannot be mapped.Keep tool names exactly stable Matching is case-sensitive. A rename or a capitalisation change silently ends the evidence — no error, calls still retained, facts simply stop. Unregistered tools are never dropped: the calls are kept verbatim and surfaced for registration, so ship first and have them mapped after.
You can register them yourself. If your application is connected over the hosted endpoint with the
tools:writescope, callsubmit_toolwith the parameter schema and your parameter→fact mappings;list_toolstells you where each one stands and carries the reviewer's note on anything declined. Otherwise send the platform operator your tool's JSON Schema plus a real sample of one call and one response — samples make the mapping far more accurate than a schema alone. Either way a registration takes effect only after LeadVal approves it, and only for leads processed after that.And unlike custom metadata, this DOES move the number. Where a captured parameter is wired to scoring, it feeds the lead's quality index and therefore its price. Measured against the live calibration on one mid-strength lead: with no tool evidence it scored below the pricing floor and was unsellable; a single tool-captured budget lifted it over the floor to $51.83, and adding a target property and an intent signal took it to $59.76. A tool-captured phone number matters even more — remove every contact vector and reachability is zero, which zeroes the lead outright.
Only some fact keys are wired that way (a contact vector, a budget corroboration, a target property, an intent signal); the rest are recorded faithfully and change nothing. Which is which is registry configuration, so ask your operator what your parameters are wired to.
None of this is real-estate specific: the event labels and captured fields are configuration, so the same machinery carries a clinic booking appointments or a SaaS company starting trials. Full detail on the Tool calls as evidence page under Developers in
<your-host>/docs, and the admin-side Tool Calls & Journey page under Guides.Status responses are PII/score/price-free by design — safe to hand back to an AI.
Errors come back as the LeadVal RFC 9457
problem+jsonbody with a stablecode.Full API reference:
<your-host>/docsand the machine-readable spec at<your-host>/openapi.yaml.Advanced identity resolution and the evidence schema: contact EVAS at support@evas.live.