eli.ai doesn't just consume MCP servers (Tools, MCP & HITL) — it is one. Any MCP client — Claude, an IDE agent, another platform — connects to your workspace over streamable HTTP and gets the knowledge graph and the governed data layer as native tools, under the same RLS scoping and provenance as everything else.
A mounted client reaches the same pipeline the workspace and the REST API run: kb_search for grounded retrieval, eli_query for the whole job, every call under the key's scopes and the workspace's row-level security.
The transport resolves exactly one workspace before constructing the server; every tool call executes under that workspace's row-level security. User keys with more than one possible workspace send X-Workspace-Id. There is no workspace field in a model-supplied tool input. See Authentication for creating keys and scopes.
Stateless streamable HTTP
The server is stateless: each JSON-RPC request is self-contained, initialized per call, and served by whichever web process receives it — no session affinity, no SSE subscription required, safe behind any load balancer. Clients that prefer long-lived SSE streams still work, but nothing depends on them.
Tool catalog
kb_search
tool
Hybrid lexical + semantic search over the workspace KB; returns scored chunks with doc metadata.
kb_entity_lookup
tool
Resolve entities by name (exact / alias / fuzzy); returns entity cards with type, aliases, mention counts, top relations.
kb_entity_get
tool
Fetch one entity by id with its full card and governance metadata.
kb_document_lineage
tool
Return a document's origin, revisions, governance, derived graph state, audit trail, and downstream dependencies.
kb_graph_query
tool
Expand the knowledge graph around seed entities (structured params: seeds, depth 1–3, type filters); returns nodes, typed edges, truncated flag.
kb_data_discover
tool
List the live-data surface: enabled entity bindings and domain components in scope, with labels, descriptions, and param specs.
kb_data_lookup
tool
Execute a domain component or binding with validated params; returns columns, rows (maxRows-capped, truncated flag), duration, and the dataCallId provenance anchor.
eli_query
tool
The full pipeline in one call — retrieval + entity resolution + governed lookups → structured answer with [Sn]/[Dn] citations (same shape as POST /api/v1/query).
kb_records
tool
Read structured records (matrix rows, deal configurations): list with keys and versions, or fetch one validated payload; cite as recordType:key@vN.
kb_assets
tool
Read the versioned asset & skill registry (brand, templates, examples, skills): list active series or fetch the latest version's content; cite as asset:kind/name@vN.
These ten kb_* / eli_query tools are read-only. The fine-grained tools suit agents that orchestrate their own reasoning; eli_query suits clients that want eli.ai to do the whole job and hand back a cited result. Every kb_data_lookup and eli_query data access writes the same append-only data_calls provenance row as any internal call, attributed to the API key.
Module tool suite
Beyond the KB reads, the full platform is mounted as module tools — distinct query and create/update tools per capability module, named <module>_<verb>:
The knowledge graph: find/get/expand plus create/update entities, create/delete relations, and merge — the same audited, constraint-checked curator operations as the app.
warrant_*
7 tools
Governance: list/propose/decide change requests, read and update curation state (authority, owner, lifecycle, certify, review), verify documents.
lineage_*
5 tools
Documents: list/read (ACL-scoped), save (create or update — revisioned, audited, re-indexed), revision history, and the full provenance trail.
conduit_*
6 tools
Governed live data: list connectors/queries/bindings, run a named query ([Dn] provenance), author and update query definitions. The read-only SQL gate always applies.
lens_*
2 tools
lens_search (hybrid retrieval, optional rerank) and lens_answer (the policy-gated cited StructuredAnswer).
crucible_*
8 tools
Evals: golden items CRUD, append-only qrels, run metrics, paired run comparison, acceptance gates.
ports_*
6 tools
Agents: list agents/runs/approvals, start a durable run (enqueue + runId), inspect a run's steps, resolve HITL approval gates.
Write tools execute the same audited services the app and REST API use — revisions, audit rows, ontology constraints, and governance state machines all apply. Each tool mirrors the REST scope map (for example governance:write for warrant_decide_change), and a delegated user key additionally re-checks the owner's live role capabilities per call.
Per-user module access — default full read-write
Each user can dial every module to read & write (the default), read only, or offper workspace, under Settings → MCP servers → "eli as an MCP server". The setting bounds that user's own eli_uk_ keys: excluded tools disappear from tools/listand are denied at call time. It only ever narrows below what key scopes and the user's role already allow; workspace eli_sk_ service keys are scope-bound at mint time and unaffected.
Worked examples, per module
Each block shows the JSON-RPC bodies POSTed to /api/mcp — headers identical to the kb_search call above. IDs in angle brackets come from the preceding read.
intake — register a source, trigger a syncbash
# read: sources with sync state (masked credentials){"jsonrpc":"2.0","id":10,"method":"tools/call","params":{"name":"intake_list_sources","arguments":{}}}# write: register an upload source, then enqueue an incremental sync{"jsonrpc":"2.0","id":11,"method":"tools/call","params":{"name":"intake_create_source","arguments":{"type":"markdown_upload","config":{}}}}{"jsonrpc":"2.0","id":12,"method":"tools/call","params":{"name":"intake_trigger_sync","arguments":{"id":"<source-id>","kind":"incremental"}}}
Sync runs land on the source's history — poll intake_get_source. Credentials are write-only: stored encrypted, never returned.
atlas — resolve, then curate the graphbash
# read: name → canonical entities (exact, alias, fuzzy){"jsonrpc":"2.0","id":20,"method":"tools/call","params":{"name":"atlas_find_entities","arguments":{"query":"Order Platform","limit":5}}}# write: a typed, evidence-backed relation between two entities{"jsonrpc":"2.0","id":21,"method":"tools/call","params":{"name":"atlas_create_relation","arguments":{"srcEntityId":"<id-a>","dstEntityId":"<id-b>","type":"depends_on","evidence":"The checkout flow calls the order platform."}}}
Creates are conflict-aware: atlas_create_entity returns { conflict: true, existingId } instead of duplicating an active name.
warrant — propose, then decide a governed changebash
# write: open a change request (nothing applies yet){"jsonrpc":"2.0","id":30,"method":"tools/call","params":{"name":"warrant_propose_change","arguments":{"kind":"update","entityId":"<entity-id>","payload":{"description":"The order orchestration platform."},"note":"tighten wording"}}}# write: an accountable reviewer applies it atomically{"jsonrpc":"2.0","id":31,"method":"tools/call","params":{"name":"warrant_decide_change","arguments":{"id":"<change-request-id>","decision":"approve","note":"reads well"}}}
Owner/steward reviewer gates are enforced server-side; approval applies the payload and promotes authority in one transaction.
lineage — save a document, trace its trailbash
# write: create (path) or update (docId); revisioned, audited, re-indexed{"jsonrpc":"2.0","id":40,"method":"tools/call","params":{"name":"lineage_save_document","arguments":{"path":"notes/order-platform.md","content":"# Order Platform\n\nOwns order orchestration."}}}# read: origin, governance, derived knowledge, downstream impact{"jsonrpc":"2.0","id":41,"method":"tools/call","params":{"name":"lineage_document_trail","arguments":{"docId":"<doc-id>"}}}
Reads are ACL-scoped: a document you cannot see is indistinguishable from one that does not exist.
conduit — run governed data, author a querybash
# read-execute: a named query under your entitlements ([Dn] provenance){"jsonrpc":"2.0","id":50,"method":"tools/call","params":{"name":"conduit_run_query","arguments":{"query":"orders-for-customer","params":{"customerId":"C-1042"}}}}# write: author a new named query (read-only SQL gate validates the template){"jsonrpc":"2.0","id":51,"method":"tools/call","params":{"name":"conduit_create_query","arguments":{"connectorId":"<connector-id>","slug":"open-orders-by-region","title":"Open orders by region","sqlTemplate":"select region, count(*) from orders where status = :status group by region","params":[{"name":"status","type":"string","required":true}]}}}
Every execution returns a dataCallId — the persisted provenance anchor to cite as [Dn]. Non-SELECT templates are rejected at authoring time.
lens — search, or ask for a cited answerbash
# read: hybrid retrieval (optionally rerank: true){"jsonrpc":"2.0","id":60,"method":"tools/call","params":{"name":"lens_search","arguments":{"query":"escalation policy for stuck orders","limit":8}}}# read: the full policy-gated StructuredAnswer with [Sn]/[Dn] citations{"jsonrpc":"2.0","id":61,"method":"tools/call","params":{"name":"lens_answer","arguments":{"question":"What is our escalation policy for stuck orders?","includeData":true}}}
lens_answer refuses explicitly when grounding is missing — the answer policy applies exactly as it does in the app.
crucible — grow the golden set, read run metricsbash
# write: a golden question with graded assertions{"jsonrpc":"2.0","id":70,"method":"tools/call","params":{"name":"crucible_create_golden_item","arguments":{"question":"How often are helium purity inspections required?","assertions":[{"text":"states the quarterly cadence","kind":"must"}],"tags":["ops"],"status":"active"}}}# read: one eval run aggregated, or two runs compared with significance{"jsonrpc":"2.0","id":71,"method":"tools/call","params":{"name":"crucible_compare_runs","arguments":{"runIdA":"<baseline-run>","runIdB":"<candidate-run>","metric":"ndcgAt10"}}}
Qrels are append-only and idempotent per (chunk, source, judge) lane; golden edits bump revisions so historical runs stay comparable.
ports — start a durable run, resolve its approval gatebash
# write: enqueue durably; returns { runId, status: "queued" } immediately{"jsonrpc":"2.0","id":80,"method":"tools/call","params":{"name":"ports_start_run","arguments":{"agentSlug":"docs-desk","input":"Summarize this week's document changes"}}}# read, then write: inspect the suspended gate and decide it{"jsonrpc":"2.0","id":81,"method":"tools/call","params":{"name":"ports_get_run","arguments":{"runId":"<run-id>"}}}{"jsonrpc":"2.0","id":82,"method":"tools/call","params":{"name":"ports_resolve_approval","arguments":{"runId":"<run-id>","approvalId":"<approval-id>","decision":"allow","note":"looks right"}}}
Admission (concurrency + spend caps) runs before the enqueue; the loop executes on the worker, so poll ports_get_run for progress.
Denials are stable, actionable strings — the settings layer, the scope layer, and the live-capability layer each name exactly what was missing:
what a denied call looks likebash
# a module set to read-only rejects its write tools — stable, greppable errors{"error":"module_access_denied: the key owner's MCP settings set the atlas module to \"read-only\", which excludes atlas_create_entity"}# a key without the scope never reaches the service{"error":"insufficient_scope: warrant_decide_change requires one of the API-key scopes: governance:write"}
The access matrix is also scriptable — the same API the settings UI uses:
GET / PUT /api/account/mcp-accessbash
# read your effective per-module access for one workspace (session-authed)curl -s "https://eli.ai/api/account/mcp-access?workspaceId=<id>"# narrow your own keys: conduit to read-only, ports off (null restores read-write)curl -s -X PUT https://eli.ai/api/account/mcp-access \ -H "Content-Type: application/json" \ -d '{"workspaceId":"<id>","modules":{"conduit":"read-only","ports":"off"}}'
Resource catalog
eli://workspace/summary
resource
ACL-filtered workspace counts and recent visible documents.
eli://workspace/manifest
resource
The governed knowledge manifest for the selected workspace.
Connect from Claude
Claude Code:
claude mcp addbash
claude mcp add --transport http eli https://eli.ai/api/mcp \ --header "Authorization: Bearer eli_sk_..."
Or in an MCP client config file (Claude Desktop, IDEs, and most other clients accept this shape):
Once connected, ask naturally: "Using the eli tools, what do we know about Acme Corp — and what is their current churn risk?" The client will chain kb_entity_lookup → kb_data_discover → kb_data_lookup, or just call eli_query.
Agent-scoped mounts
Besides the workspace mount, every agent is its own MCP server at /api/mcp/agents/:slug. The mount is the MCP projection of the agent's capability lock: only the tools the locked toolset maps to are registered — a locked-out tool is not listed, not merely denied — and eli_queryis forced away from live data when the lock excludes it. This is the self-service harness boundary: hand a team a chatbot that mounts one locked agent and it can use exactly that combination of platform capabilities, with the caller's key scopes, role capabilities, and content ACL still checked on every call. The mount resolves the latest published version per request, so tightening a lock takes effect immediately everywhere the agent is mounted.
Workspace isolation— the key picks the workspace; FORCE-RLS makes other tenants' rows nonexistent, not forbidden.
Source ACL enforcement — connector principals are mapped to the caller and enforced before document chunks or document-derived graph context reach a tool result.
Governed writes only — the kb_*surface stays read-only; module write tools go through the same audited, revisioned services as the app, gated by scopes, live role capabilities, and the key owner's per-module access settings. External data-layer access remains single-statement SELECT/WITH through governed components.
Provenance & attribution — external data access is recorded per call with the exact SQL, params, timing, and status; keyed usage is attributed to its API key exactly like /api/v1 traffic, and the same rate-limit posture applies (Errors & rate limits).
Secrets stay home — connector credentials never appear in tool results; clients see component slugs and descriptions, never connection configs.
Plain HTTP instead?
If your integration isn't an MCP client, the same pipeline is available as ordinary JSON over POST /api/v1/query.