Skip to main content
POST /v1/mcp publishes this API itself as a Model Context Protocol tool catalog. Point an agent at it and the agent can operate a Vetta account — create agents, start sessions, read events, exec on a computer, check credits — the same way it operates any other connected system. It is a JSON-RPC 2.0 endpoint. It is not a REST resource: there is no object, no list, no GET. Everything it can do, it does by dispatching one of the routes documented elsewhere in this reference.
There is no client.mcp in the TypeScript SDK and no vetta mcp command, on purpose. Every operation behind this endpoint already has a typed method and a command; a second, weaker way to call the same routes would be a surface to keep in sync for no gain. This endpoint exists for MCP clients — an agent’s runtime, or your own.

Authentication

The same organization-scoped API key as every other endpoint, in the same header. See Authentication.
Two properties follow from how a tool call is executed, and both are the point of the design:
  • A tool call is an ordinary API call. It is re-dispatched through the full middleware chain — authentication, scope check, plan gate, rate limit, strict request validation — as a real request carrying the caller’s own bearer. There is no second authorization path, because there is no second path.
  • A tool can never reach further than the key that invoked it. A key scoped agents:read calling agents_create gets 403 forbidden back as a tool error, in the control plane’s own words. Scopes are not re-declared here and cannot be widened here.
The endpoint itself declares no scope of its own — any authenticated principal may open the catalog — because every tool inside it is authorized by the route it actually is. The plan gate applies as usual.

Handshake

The server speaks MCP revision 2025-06-18 and exactly the methods a tool catalog needs: initialize, notifications/initialized, ping, tools/list, tools/call. Any other method answers JSON-RPC error -32601.

The tool catalog

tools/list returns one tool per served route — 192 tools on this deploy today, from 228 routes less the open ones and the 20 withheld. The catalog is generated from the server’s own route table, the same table GET /v1/openapi.json is generated from — so it describes exactly what the deploy you are talking to serves. A tool cannot exist without a route, and a route cannot acquire a tool by being described twice.
string
resource_verb, derived from the route’s resource and its handler — agents_create, agents_list, sessions_list_events, computers_exec_command, credits_get_balance. A sub-resource keeps its word: the browser on a computer is computers_browser_run_action, computers_browser_login, computers_browser_save_context. Namespaced by the server name in an agent’s tool config, a tool is vetta.agents_create.
string
What the operation does, the METHOD /v1/… it maps to, one sentence about the resource, and the scope it requires.
object
JSON Schema. Path parameters, declared query filters and body fields arrive as one flat object — a model fills a flat schema reliably and a nested one it does not. The body fields are the same zod schema the server validates with, so the arguments offered are the arguments enforced. additionalProperties is false.
boolean
true when the call changes nothing, false when it does. That is the method in almost every case — GET reads, the rest write — with named exceptions in both directions: a POST whose body is only the question (pricing a payment or a card, minting a file link) is true; the one GET that returns a live card credential is false. This is what lets a caller allow reads without maintaining a list.
boolean
true when the call is one a person should see before it runs: every DELETE, and any call that spends money or acts outside your account (a payment or transfer, a card order or its credential reveal, a credits charge, paid media generation, a company formation, a social post, an outbound email or SMS, an account created in a third party’s system), changes the organization’s security posture (vaults, webhooks, the organization and its members, domains, app secrets and login links, saved browser logins), rewrites data (a schema migration, raw SQL through apps_query_db, a path removal), decides what an agent may do (creating, updating or rolling back an agent — its version is the model, toolset, permissions and budget every later session runs under), or runs code (a command on a computer, a browser action that acts on a page, a browser login or sign-up). Creating a session, an identity, an inbox, a skill, a file or a deployment is false. A read-only tool is never destructive. This is what lets a caller allow benign writes and ask only on the dangerous ones.
Listing routes additionally accept limit (1–100) and after; a read addressed to a single id accepts neither. See Pagination.

Calling a tool

The response body of the underlying call is returned verbatim as the tool’s text content.

Failures

A failed call is a tool error, not a transport error. The JSON-RPC envelope stays 200 and the result carries isError: true with the API’s own error envelope as its text — a non-200 here would make an MCP client conclude the transport is broken and drop the whole catalog for the turn.
Naming a tool that is not in the catalog answers no such tool: <name> with isError: true, rather than guessing a route.

Results are capped at 8,000 characters

A tool result is read by a model, on a budget, so a result longer than 8,000 characters is cut and the cut says so — terminally. The notice states that the reply was cut, that paging will not fix it (the rows themselves are large, so the next page is cut too), and that the model should report what it can see rather than call again. This wording is deliberate and was measured. An earlier version suggested narrowing the call, and models then walked the cursor to their iteration limit — 24 model calls to count sixteen rows, every page truncated. Ask for a small limit, or filter, before you call.

Withheld routes

Some served routes are deliberately absent from the catalog. They answer normally over HTTP with the same key; they are simply never handed to a model. Each exclusion is a property you can rely on, not an oversight — the reason is recorded next to the route in the server and enforced by a test that fails if a withheld route disappears. Two rules generate that table, and they are worth stating plainly because they are the security properties the endpoint offers:

No route that returns a secret

The four /v1/api_keys routes are withheld as a family because two of them return live credentials into the model’s transcript and the log that records it; the browser live-view URL is a bearer credential for the same reason. A key is issued by a person, in the dashboard or the CLI — never by an agent.

No route that accepts a secret

POST /v1/vaults/{id}/credentials and POST /v1/browser/credentials are the routes whose body carries a secret value. Vault reads never return values, so the rest of the vault family is published; the writes are not.
The rest are not security exclusions at all — they are shape exclusions. A never-ending stream, a byte body with no JSON schema, the catalog itself, a billed model call that also streams, and a REST passthrough whose arguments are an arbitrary URL cannot be expressed as a flat tool call that returns. The database one is not lost to agents: the built-in apps tool’s db_rest action carries the whole request.
Withholding is not an access control. It keeps these operations out of a model’s reach; it does not stop the key from performing them. If an agent must not be able to touch a resource at all, scope its key — the tool call is checked against that key like any other request.

Permissions

Because the catalog arrives as an ordinary external MCP server, an agent’s own tool policy governs it. vetta.agents_create is filtered by the same allow / ask / deny mechanism as bash, and the default permission for MCP tools is ask — a newly exposed tool never auto-runs. See Tools for the tools.configs shape, and pair it with annotations.readOnlyHint and annotations.destructiveHint to allow reads and benign writes while asking on the dangerous ones.