Skip to main content
Zelto exposes a Model Context Protocol server at https://api.zelto.ai/mcp. Any MCP-compatible client can connect and get read access to your agents, conversations, transcripts, reviews, and findings, plus a small set of write tools for the bucket-based review workflow. Most read and write tools mirror a public REST API endpoint, so anything you can do over HTTP you can do from inside your editor. Some tools go beyond the REST surface, though — review queues, changes, bucket population, voice-provider connect/import, and a raw read-only SQL tool (query_database) are MCP-only. The Mirrors column in each table below tells you which is which (— (MCP-only) means there’s no REST equivalent).
New here? Onboard with an agent drives the whole setup — connect a provider, get calls in, create a monitor, and route findings — over the onboarding tools below.

Get an API key

Mint one at https://dashboard.zelto.ai/org/<your-slug>/settings/integrationsAPI Call Upload. The key is shown once — copy it immediately. The same key works for the REST API and every MCP client below. Minting and revoking keys is an owner/admin action; if you hold the member role, ask an owner or admin for one (and see Your role decides what you can do). Export it for the snippets below:

Sign in with OAuth

Clients that support remote MCP OAuth — Claude, Cursor, VS Code, and others — can connect without minting a key. Point the client at the server URL and sign in through your browser:
On first connect the client opens a browser window to sign in to Zelto and authorize access. If you belong to more than one organization you choose which one the connection is scoped to on the consent screen — the client only ever sees that organization’s data, exactly like an org-scoped API key. Access tokens are short-lived (one hour), refresh only while your Zelto session is valid, and stop working as soon as you lose access to that organization. Under the hood the server is a standard OAuth 2.1 Authorization Server as the MCP spec defines it — it publishes Protected Resource Metadata, supports Dynamic Client Registration, and requires PKCE — so a compliant client discovers everything it needs from the URL alone. API keys keep working in parallel; use whichever your client supports.

Clients

Claude Code

Add the server to ~/.claude.json (create the file if it doesn’t exist):
Restart Claude Code (or run /mcp to refresh). The zelto: tools appear in the picker; zelto:health returns your org id as a smoke test.
The integrations page also shows this snippet pre-filled with the current host — go to Settings → Integrations → AI agent access and open the Claude card.

OpenAI Codex CLI

Codex CLI supports MCP servers via ~/.codex/config.toml. Add a server entry under [mcp_servers.zelto]:
Then run codex and ask it something that needs your data, e.g. “list my last 20 conversations and group failures by agent.” Codex discovers the zelto: tools automatically.

Cursor

~/.cursor/mcp.json accepts the same shape as Claude Code:
Then open Cursor’s MCP panel and toggle the zelto server on. The tools show up in the chat sidebar.

Continue, Zed, and other Streamable-HTTP clients

The server speaks Streamable HTTP, so any client that accepts a URL + Authorization header works. Continue uses config.yaml:
Zed uses ~/.config/zed/settings.json under context_servers.zelto with the same url + headers shape.

Quick smoke test (curl)

If a client is misbehaving, verify your key and connectivity against the REST API — the same key authenticates both:
A 200 with a { "data": [...] } page means the server and your key are good — the problem is in the client config. From inside a connected client, the health tool returns { "status": "ok", "orgId": "…" } as the same check. A 401 means the credential is the problem: the key was deleted, or the person it belongs to lost access to the organization (removed, or their account banned). Minting a fresh key only helps if your own account still has access to that org.

Authentication

Every request carries an Authorization: Bearer <token> header, where the token is either an API key or an OAuth access token obtained by signing in. Requests with no token or an invalid/expired token return 401 and never reach a tool. Either way the token resolves to a single organization server-side — an API key from its baked-in org, an OAuth token from the org you chose at consent — and a tool only ever sees that organization’s data.

Your role decides what you can do

Authenticating proves which organization you may act on, not what you may do inside it. Tools that administer the organization itself require the owner or admin role — the same gate the dashboard applies: update_org_settings · invite_member · remove_member · save_usage_report_config · set_voice_tool_access · delete_agent · set_findings_delivery · merge_findings · delete_finding · approve_finding_candidate · reject_finding_candidate · update_finding (only when you pass title — the triage fields stay open to any member) update_member_role is stricter still: owner only, and it refuses to change your own role. remove_member likewise refuses to remove your own membership, whatever your role — ask another owner or admin. A token belonging to a member reaches every read tool and every non-administrative write, but the tools above return Only an organization owner or admin can perform this action. instead of acting (update_member_role returns Only an organization owner can perform this action.). This is true for both credential types: a key or token acts as the person behind it, and this gate reads that person’s role at call time rather than at mint time. To take a credential out of service entirely, revoke it — see API keys. Removing that person from the organization (or banning their account) does the same thing implicitly: every request re-checks the owner, so their keys and OAuth tokens stop working on the next call.

Tools

Read tools

read_docs is MCP-only — call it with no arguments to list every documentation page (slug + title + description), then pass a path (e.g. findings or integrations/slack) to read that page’s full Markdown. status enums: reviews accept pending / reviewed / flagged; findings accept open / acknowledged / resolved / ignored. Finding priority accepts none / low / medium / high / urgent.

Raw SQL (query_database)

When the dedicated list_* / get_* tools can’t express what you need — aggregations, group-bys, ad-hoc joins — query_database runs a single read-only SQL statement against either store and returns { rows, rowCount, columns, truncated }. It’s MCP-only (no REST equivalent) and annotated readOnlyHint: true, destructiveHint: false.
Read-only by construction. Only SELECT / WITH / DESCRIBE / EXPLAIN / SHOW statements run, and exactly one statement per call — no DDL, no DML, no semicolon-chained queries. ClickHouse runs with readonly=2; Postgres wraps the query in SET TRANSACTION READ ONLY. Every query is automatically scoped to your organization at the engine level (you cannot reach another org’s data even if you omit a WHERE), so do not add organization_id filters yourself.
Inputs: What each store exposes:
  • clickhouse — call & analytics data: conversations, transcripts, conversation_analyses, analysis_findings, agents, webhooks. Use it for aggregations over call data. The ReplacingMergeTree tables (conversations, transcripts, conversation_analyses) should use FINAL for accurate reads.
  • postgres — app & config data: any table with an organization_id column — receivers, integrations, reviews, buckets, findings, agents, jobs, webhooks, and more. Tables without an organization_id column (e.g. raw conversations) aren’t reachable here — query ClickHouse for those, or join through a scoped table.
How scoping and limits are enforced:
  • Org scoping is injected for you — ClickHouse via additional_table_filters, Postgres by rewriting the SQL AST to add organization_id = <your-org> to every table reference. You can’t bypass it by omitting a WHERE.
  • Row cap of ~1,000 rows. Any LIMIT higher than that is clamped down, and a missing LIMIT gets one injected. The response sets truncated: true when the cap is hit.
  • Timeout of 30s on ClickHouse queries.
To discover columns before writing a query:

Write tools

annotate_finding_conversation (add a system-authored, optionally audio-ranged annotation to a call within a finding) requires a platform-admin API key and the X-Organization-Id header — it is not callable with a standard org key. Regular org keys can write the same annotation over REST at POST /v1/findings/[id]/conversations/[conversationId]/comments (attributed to the calling user), or read them via list_finding_conversation_comments and edit/remove with update_finding_conversation_comment / delete_finding_conversation_comment.
Idempotent: add_conversation_to_bucket and add_conversation_to_finding return the existing row with alreadyExisted: true on re-add. The conversation must belong to the same agent as the bucket. create_bucket accepts only type: "static" today. AI agent creation runs the system prompt through the audit pipeline before insert. create_finding submits every finding as a candidate pending platform review: it does not appear in the organization’s findings list and is not auto-linked to calls until a platform admin approves it (or rejects it) from the Candidates queue on the findings page. update_agent changes only the fields you pass — send description: null to clear it, omit a field to leave it untouched. systemPrompt is accepted for AI agents only (human agents have none) and is written as-is: unlike creation, it is not re-run through the audit pipeline. An agent’s type and provider are immutable and cannot be changed.

Review queues

A queue is a saved set of filters defining a dynamic collection of conversations to review. These tools are MCP-only. The filters object accepts: agentIds, agentType (ai / human), durationMinSeconds, durationMaxSeconds, endedReasons, dateFrom (YYYY-MM-DD), dateTo (YYYY-MM-DD), alreadyReviewed, hasAudio, isInteresting, findingTypes. An empty/omitted filters matches every conversation in the org. On update_queue, passing filters replaces the whole object — it is not merged with the existing one.

Changes

A change is one atomic, agent-scoped suggestion that addresses a set of findings. Each new change starts as a draft (no workflow status, hidden from the main list) until a human publishes it. These tools are MCP-only.
For API stability the MCP tool names keep the original solution term (create_solution, list_solutions, …) even though the product now calls these changes. The tools and the UI operate on the same records.
create_solution inserts exactly one draft per call — never bundle multiple ideas into one body; call it again for each distinct suggestion. A published change’s status is one of backlog / todo / in_progress / done / cancelled. You can’t set status on a draft: move it out of draft first with publish_solution (→ backlog) or discard_solution (→ cancelled). Deleting a change removes its finding links by cascade but never deletes the underlying findings.

Reports

A report is a free-form analyst write-up authored in the same rich-text editor as findings and changes. prompt holds the question or brief; description holds the body as a TipTap/ProseMirror JSON document. status tracks the generation lifecycle (pending / generating / completed / failed) — a report you create sits in pending unless you pass a finished body and set completed. These tools are MCP-only. On update_report, passing description replaces the whole body — it is not merged with the existing document. Pass prompt: null to clear the ask.

Bucket population

These tools snapshot conversations into buckets from queues or raw filters. They are MCP-only. Because queues can span multiple agents but buckets are agent-scoped, create_bucket_from_queue creates one bucket per matched agent and returns an array. The populate_* tools are idempotent — re-running adds zero new rows — and matches belonging to a different agent than the bucket are skipped and counted in skippedOtherAgentCount.

Bucket & finding mutations

The remaining MCP-only mutations, plus the one finding-conversation unlink that does mirror a REST endpoint. type accepts only "static". Every delete cascades its own link rows (bucket tasks, finding-conversation links, comments) but never deletes the underlying conversations.

Provider import

Connect a voice provider and pull its agents and calls directly into Zelto — the same connect → import → backfill flow as Settings → Integrations, driven from your editor. These tools are MCP-only and cover every pull-capable provider: Vapi, Retell, Speechify, Telnyx, and ElevenLabs. Each takes a provider argument (vapi / retell / speechify / telnyx / elevenlabs). connect_provider uses a browser handoff so the API key never passes through the agent: it returns a one-time url (valid 15 minutes). You hand that URL to the user; they open it while signed in to Zelto and paste their key there, so the key goes straight to the server — never into your context or logs. Don’t ask the user for their API key yourself. After they finish, poll check_provider_connection with the returned token until status is completed (it returns agentsFound) — or expired (start over). label defaults to Default and must be unique per provider; the user can change it on the connect page.
Never accept a provider API key as a tool argument. It would be captured in the agent’s conversation and client logs. connect_provider is a handoff for exactly this reason — the key only ever travels browser → server.
list_provider_agents returns every agent/assistant in the connected account(s), each annotated with alreadyImported (and importedAgentId once imported). agentId is the provider’s external id; for Vapi and Telnyx that is the assistant id. get_retell_agent reads one Retell agent directly without importing it. It returns the current general_prompt for Retell LLM agents and global_prompt for conversation-flow agents. import_provider_agent imports one agent and fails if it is already imported — call list_provider_agents first. import_provider_conversations imports that agent’s calls from since (an ISO 8601 timestamp, e.g. 2026-03-01T00:00:00Z) until now, in the background — the agent must already be imported, and re-running with an overlapping window is safe (already-imported calls are skipped).

Onboarding

Stand a new organization up end to end from your editor: connect a call provider, create a monitor, and route findings. These tools are MCP-only. The Onboard with an agent guide walks an agent through the whole flow using them. A headless run in order:
  1. get_onboarding_status — read nextIncomplete to resume from the right step.
  2. connect_integrationprovider is one of vapi, retell, speechify, telnyx, elevenlabs; apiKey is that provider’s key. For Vapi / Retell / Speechify the response’s webhook.webhookUrl must be pasted into the provider’s dashboard (a human step) before calls flow; ElevenLabs and Telnyx sync automatically. Confirm with list_conversations.
  3. create_monitor — pass a starterKey from list_starter_monitors, or a name + a natural-language boolean instruction. Omitting agentIds applies it to every agent. Confirm with list_monitors.
  4. set_findings_deliverychannels is any of email / slack (Slack also needs the Slack app connected via OAuth in the web UI).
These are write tools (except the list_* / get_* reads) and return a JSON object that may carry an error key — inspect it, and because writes settle asynchronously, confirm each step with the paired read tool before advancing.

Monitors & scorecards

Manage the AI judges that surface findings (create_monitor / list_monitors / list_starter_monitors are under Onboarding).

Findings workflow

Beyond create_finding / update_finding, these drive the candidate → open lifecycle and dedup. Everything in this table except set_finding_version_scope requires the owner or admin role, as does delete_finding and renaming through update_finding — see Your role decides what you can do.

Call review

flag_call_for_review only queues a call; these record the actual review.

Agents (lifecycle)

create_ai_agent / create_human_agent / update_agent plus: merge_agents / unmerge_agents exist too but require an admin (cross-org) key, matching the staff-only merge tools in the web UI.

Receivers (phone numbers)

Integrations & connectors

Provider connect/import is under Provider import. These configure an already-connected integration. Slack (post-connect; OAuth install stays in the web UI): add_slack_channel, remove_slack_channel, set_default_slack_channel, set_slack_channel_chat_enabled, send_slack_channel_test, set_agent_slack_channel — each takes a Slack channel id (C…), and set_agent_slack_channel also an agentId. Linear (post-connect; OAuth install stays in the web UI): list_linear_teams, set_default_linear_team (teamId, nullable), create_linear_issue_for_finding (findingId), create_linear_issue_for_solution (solutionId), unlink_linear_issue (findingId? / solutionId?).

Organization & members

Every tool in this table except upload_conversation requires the owner or admin role, and update_member_role requires owner — see Your role decides what you can do. The inputs above are the primary fields; the MCP server advertises the full, authoritative schema for each tool via tools/list. Email inputs use regular-language JSON Schema patterns so the advertised tool schemas remain compatible with strict structured-output providers, including OpenAI.

Output shapes & pagination

Tool responses match the REST API exactly. See the API reference for the JSON shapes of Agent, Conversation, Transcript, Review, Bucket, and Finding, plus the { data, nextCursor } pagination envelope. limit clamps to 200 (default 50). cursor is opaque — pass back the nextCursor value from a previous response to fetch the next page.

Troubleshooting

  • 401 from every tool — the bearer token is wrong or revoked. Test the key directly: curl -H "Authorization: Bearer $ZELTO_API_KEY" https://api.zelto.ai/v1/agents. If that also returns 401, mint a new key.
  • Only an organization owner or admin can perform this action. — the tool worked, your role didn’t. Your credential resolves to the member role and the tool administers the organization. Ask an owner or admin to run it or to raise your role; see Your role decides what you can do.
  • Tools list is empty in the client — most clients cache the tool list. Restart the client (Claude Code: /mcp; Cursor: toggle the server off/on; Codex: restart the CLI).
  • 429 rate-limited — back off and retry. Use the cursor pagination instead of fetching large pages.
  • Cross-org access denied — keys are scoped to the org they were minted in. Switch organizations in the web app and mint a new key from that org’s Settings → Integrations.