Skip to content
/docs

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:

ToolDescription
veto_charter_turnRun a Charter turn with mode: auto, chat, or compile
veto_charter_chatAsk grounded questions about registered tools and active policies
veto_charter_compileDraft policy changes and return a Veto verification link
veto_list_toolsList registered tools Charter can reason about
veto_register_toolsRegister 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 a result_hash over the output) and is attached to the JSON-RPC response under result._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-stdio with 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