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 — 119 tools on this deploy today. 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. 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 for every GET, false for every write. This is what lets a caller allow reads and ask on writes without maintaining a list.
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

Nine 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. 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 is the one route on the whole surface whose body carries a secret value. Vault reads never return values, so the rest of the vault family is published; the write is not.
The other four are not security exclusions at all — they are shape exclusions. A never-ending stream, a byte body with no JSON schema, the catalog itself, and a billed model call that also streams cannot be expressed as a tool call that returns.
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 to allow reads while asking on writes.