MCP Gateway API
Veto's MCP Gateway API proxies MCP tool calls through policy enforcement before upstream execution.
Veto's MCP Gateway API proxies Model Context Protocol tool calls through policy enforcement before upstream execution. A JSON-RPC tools/call request reaches Veto first, Veto evaluates the tool name and arguments, and only allowed calls are forwarded to the configured MCP upstream.
Managed MCP gateway endpoints are served from veto-server under /v1/mcp.
Authentication
Data plane endpoints (/:slug) require API key auth:
X-Veto-API-Key: veto_xxx- or bearer API key in
Authorization: Bearer veto_xxx
Control plane endpoints (/upstreams*) use combined auth middleware (dashboard JWT sessions and API keys).
Data plane
POST /v1/mcp/charter
Run Veto's native Charter MCP server. This endpoint exposes Charter as MCP tools for external agents; it does not proxy to a configured upstream.
curl -X POST https://api.veto.so/v1/mcp/charter \
-H 'X-Veto-API-Key: veto_xxx' \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":"tools","method":"tools/list"}'Native tools:
| Tool | Description |
|---|---|
veto_charter_turn | Run a Charter turn with mode: auto, chat, or compile |
veto_charter_chat | Ask grounded questions about registered tools and active policies |
veto_charter_compile | Draft policy changes and return a Veto verification link |
veto_list_tools | List registered tools Charter can reason about |
veto_register_tools | Register an agent's callable tools so Charter compiles against the real runtime surface |
Policy-changing results return MCP structuredContent.verification.required = true plus linkUrl; the policy is not live until that link is approved.
Register tools before asking Charter to compile policies for a new agent:
curl -X POST https://api.veto.so/v1/mcp/charter \
-H 'X-Veto-API-Key: veto_xxx' \
-H 'content-type: application/json' \
-d '{
"jsonrpc": "2.0",
"id": "register-tools",
"method": "tools/call",
"params": {
"name": "veto_register_tools",
"arguments": {
"tools": [
{
"name": "send_customer_email",
"description": "Send a customer email",
"parameters": [
{ "name": "to", "type": "string", "required": true },
{ "name": "body", "type": "string", "required": true }
]
}
]
}
}
}'veto_register_tools accepts either parameters (SDK-style) or arguments (MCP-style) for each tool.
POST /v1/mcp/:upstreamSlug
Proxy an MCP JSON-RPC request through Veto policy enforcement.
curl -X POST https://api.veto.so/v1/mcp/default \
-H 'X-Veto-API-Key: veto_xxx' \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"approve_invoice","arguments":{"amount":120}}}'Decision mapping:
allow: forwarded upstream response.deny: JSON-RPC error-32001.require_approval: JSON-RPC error-32002.
Denied tool-call response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32001,
"message": "Tool call denied by Veto policy",
"data": {
"tool": "approve_invoice",
"reason": "Amount exceeds policy limit",
"decisionId": "dec_01hxyz"
}
}
}GET /v1/mcp/:upstreamSlug
Open an SSE event stream for session notifications and decision events.
Typical events:
- JSON-RPC session notification (
notifications/session). - Veto decision envelope (
type: "decision").
Decision receipts
Every guarded tools/call that records a decision also produces a canonical veto.receipt/1 receipt, surfaced as a compact summary:
{
"receipt": {
"receipt_id": "rcp_000000000000000000000001",
"receipt_hash": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
"previous_receipt_hash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"merkle_root": "sha256:2222222222222222222222222222222222222222222222222222222222222222"
}
}Where the summary appears depends on the decision:
deny/require_approval\u2014 the receipt is written before the response, and the summary rides the SSE decision envelope (type: "decision").allow\u2014 the receipt is finalized after the upstream tool executes (so it can bind aresult_hashover the output) and is attached to the JSON-RPC response underresult._meta.veto_receipt.
MCP receipts also bind client_id, connection_id, and upstream_id, so an exported chain shows which gateway client and upstream each call flowed through.
Fetch the full payload later with GET /v1/decisions/:id/receipt or stream a batch with GET /v1/receipts/export.
Control plane
GET /v1/mcp/upstreams
List MCP upstreams in org/project scope.
Query params:
projectId(optional)
POST /v1/mcp/upstreams
Create an upstream.
{
"name": "default",
"slug": "default",
"transport": "mcp-sse",
"url": "https://example.com/mcp",
"timeoutMs": 30000,
"enabled": true,
"headers": {
"authorization": "Bearer token"
}
}Managed v1 behavior:
- accepts
mcp-sse - rejects
mcp-stdiowith validation error
PATCH /v1/mcp/upstreams/:id
Update upstream fields (name, slug, transport, url, command, args, headers, timeoutMs, enabled, projectId).
DELETE /v1/mcp/upstreams/:id
Delete an upstream config.
POST /v1/mcp/upstreams/:id/test
Run a connectivity test for an upstream.
Response shape:
{
"ok": true,
"status": 200,
"latencyMs": 42,
"message": "Upstream reachable"
}Error semantics
Common error codes:
invalid_api_key(401)validation_failed(400)not_found(404)rate_limit_exceeded(429)plan_limit_exceeded(429)
JSON-RPC errors from data plane:
-32600: invalid JSON-RPC request-32001: policy deny-32002: require approval-32603: upstream/internal gateway failure

