Skip to content

MCP, A2A, OAuth & Skills API

All endpoints on this page are served by the Core Proxy (:8100) — the Rust runtime plane.

Base URL http://localhost:8100
Auth Gateway JWT (X-Gateway-Authorization: Bearer <jwt>) or API key (X-API-Key / Authorization: Bearer sk_brutor_api_...), plus X-Tenant-ID where the tenant isn’t in the credential
Public (no auth) A2A discovery well-knowns, /health
Run correlation x-brutor-run-id: <opaque id> on every call of one task groups the actions into one run in the ledger; credential-bound, so it cannot name another principal’s run. X-Brutor-Run-End: completed|errored|exhausted|cancelled on the last call closes the run with an explicit outcome. X-Brutor-Run-Outcome: resolved|escalated|handed_off|abandoned_by_user records the client-reported task outcome
Turn correlation X-Brutor-Turn-Id: <opaque id> on every call of one pass of the agent loop groups those actions into a turn. Optional — turns are otherwise inferred from model-call boundaries, and the ledger records which way it got the count so an inferred number is never reported as one you gave it. X-Brutor-Turn-Seq: <n> optionally orders them
Step correlation X-Brutor-Step-Id: <opaque id> marks the logical phase the call belongs to — a node in your state graph, a milestone on the agent’s plan. X-Brutor-Step-Name: research adds a label. Optional, and never inferred: nothing in the traffic tells us where a phase begins, so a run that asserts none reads unphased rather than being credited with one. Asserting them unlocks per-phase cost and the Trajectory Explorer
Errors {"error": "<message>"} with HTTP status

Guides: MCP Clients, A2A Agents, OAuth, Agent Skills.

Base: /v1/proxy/mcp/{server_id} — the governed front door to every registered MCP server. Speaks JSON-RPC 2.0 over POST plus an optional SSE listener (streamable HTTP transport).

Method Path Purpose
POST /v1/proxy/mcp/{server_id} JSON-RPC 2.0 request (initialize, tools/list, tools/call, resources/read, …)
GET /v1/proxy/mcp/{server_id} SSE listener — send Accept: text/event-stream (streamable HTTP)
ANY /v1/proxy/mcp/{server_id}/{path} Sub-path passthrough to the upstream server
GET /v1/proxy/mcp/{server_id}/.well-known/oauth-protected-resource OAuth discovery (gateway-rewritten)
GET /v1/proxy/mcp/{server_id}/.well-known/openid-configuration OIDC discovery (gateway-rewritten)
GET /v1/proxy/mcp/health MCP subsystem health

The gateway is bilingual across the MCP spec’s two eras (the spec calls this “dual-era”):

  • Handshake dialect (≤ 2025-11-25): initializeMcp-Session-Idnotifications/initialized. Fully supported, unchanged — every existing client and server keeps working.
  • Stateless dialect (2026-07-28): no handshake or sessions; every request carries params._meta (io.modelcontextprotocol/protocolVersion, .../clientCapabilities) and the MCP-Protocol-Version / Mcp-Method / Mcp-Name HTTP headers; capability discovery via the server/discover method. The gateway’s synthesized servers (VMCP, Agent Skills, Web Search) are dual-era — they answer both initialize and server/discover, shaping each response to the request’s dialect, so a legacy client and a 2026-07-28 client can use the same server at the same time. In the Admin UI these servers carry a dual protocol badge.

Two behaviors are always on, regardless of the flag:

  • Header/body consistency enforcement — a request whose Mcp-Method or Mcp-Name header contradicts the JSON-RPC body is rejected with HTTP 400 + JSON-RPC -32020 (Header Mismatch). Absent headers (legacy clients) pass; lying headers never do. This closes the spoofing gap where header-routing infrastructure sees a different action than the body performs.
  • Dialect discovery & recording — at capability discovery the gateway probes upstreams (server/discover first when the flag is on, initialize fallback) and records each server’s negotiated protocol_version and dialect (handshake | stateless) on the server record; the proxy speaks each upstream’s own dialect.

Error-code note: the 2026-07-28 spec retires -32002 (resource not found) in favor of -32602; the gateway accepts both from upstreams during the transition.

Terminal window
curl http://localhost:8100/v1/proxy/mcp/hubspot-server \
-H "Authorization: Bearer sk_brutor_api_..." \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_contacts",
"arguments": { "limit": 10 }
}
}'

Responses are standard JSON-RPC 2.0: {"jsonrpc": "2.0", "id": 1, "result": {...}} or {"jsonrpc": "2.0", "id": 1, "error": {...}}.

Base: /v1/proxy/vmcp

Method Path Purpose
POST /v1/proxy/vmcp/{virtual_server_id} JSON-RPC to a virtual server aggregating multiple MCP servers
GET /v1/proxy/vmcp/health Health

A virtual server presents one endpoint exposing the tools of many upstream servers. Tool names are namespaced by server slug; every tools/call is delegated to the originating upstream, so per-server OAuth works transparently — no extra client work.

Base: /v1/proxy/oauth/{mcp_server_id}/{idp_server_id} — brokered OAuth for MCP servers behind identity providers.

Method Path Purpose
GET /.../.well-known/oauth-protected-resource Discovery metadata with gateway-rewritten URLs
GET /.../.well-known/openid-configuration OIDC discovery, gateway-rewritten
GET /.../authorize Authorization endpoint. Registered clients pass straight through to the IdP; CIMD clients are bridged
GET /.../callback Bridge callback for CIMD flows (the IdP redirects here)
POST /.../token Authorization-code → token exchange
POST /.../revoke Revoke
GET /.../tokens List the caller’s stored tokens
POST /.../register RFC 7591 Dynamic Client Registration

Discovery advertises client_id_metadata_document_supported when the IdP supports DCR — see Client ID Metadata Documents for the bridged flow.

Base: /a2a/{tenant_id}/{card_name} — Agent-to-Agent protocol v1.0 endpoints for agents published through the gateway.

Method Path Purpose
POST /a2a/{tenant_id}/{card_name}/message:send Synchronous message send
POST /a2a/{tenant_id}/{card_name}/message:stream Streaming message (SSE)
GET /a2a/{tenant_id}/{card_name}/tasks List tasks
GET /a2a/{tenant_id}/{card_name}/tasks/{task_id} Get task
POST /a2a/{tenant_id}/{card_name}/tasks/{task_id}:cancel Cancel task
GET /a2a/{tenant_id}/{card_name}/tasks/{task_id}:subscribe Subscribe to task updates (SSE)
POST /a2a/{tenant_id}/{card_name}/tasks/{task_id}/pushNotificationConfigs Create push-notification config
GET /a2a/{tenant_id}/{card_name}/tasks/{task_id}/pushNotificationConfigs List push configs
GET /a2a/{tenant_id}/{card_name}/tasks/{task_id}/pushNotificationConfigs/{config_id} Get push config
DELETE /a2a/{tenant_id}/{card_name}/tasks/{task_id}/pushNotificationConfigs/{config_id} Delete push config
GET /a2a/{tenant_id}/{card_name}/extendedAgentCard Extended (authenticated) agent card
Method Path Purpose
GET /.well-known/agent-card/{tenant_id}/{card_name} Public agent card (served as application/json)
GET /.well-known/a2a-jwks/{tenant_id} Tenant Ed25519 JWK Set (RFC 7517) for verifying card signatures

Agent cards are signed with the tenant’s Ed25519 key; verifiers resolve the signature kid via the JWKS endpoint.

Terminal window
curl http://localhost:8100/.well-known/agent-card/default/revenue-sentinel
Method Path Purpose
POST /v1/proxy/a2a/outbound Governed outbound call to an external agent
POST /v1/proxy/a2a/inbound Governed inbound call from an external agent

Delegation context is carried on an HMAC-signed x-brutor-delegation-* header chain. Prefer the v1.0 surface for new integrations.

Skills are served by the per-tenant system MCP server — there is no separate skills protocol. Guide: Agent Skills.

Endpoint POST /v1/proxy/mcp/system-agent-skill-server-{tenant} (e.g. system-agent-skill-server-default)
Protocol MCP JSON-RPC 2.0 (initialize, tools/list, tools/call)

initialize returns the protocol preamble plus the caller’s access-scoped skill catalog in instructions. tools/list advertises exactly five generic verbs (skills themselves are not tools):

Tool Layer Arguments (required in bold)
skills__list L1 discover
skills__load L2 load skill_name
skills__read_resource L3 act skill_name, path
skills__run_script L3 act skill_name, script, args, approval_token
skills__render_template L3 act skill_name, template, vars
  • skills__list{"skills": [{"name", "description"}, ...]} (access-scoped).
  • skills__load → two content blocks: protocol preamble + unmodified SKILL.md, then {"skill": {name, version, description}, "manifest": {resources, scripts, templates}}. Executes nothing; audited as skill_load.
  • skills__run_script → the script’s output only (sandbox execution), or a {"execution_id", "status": "pending_approval", ...} envelope when approval-gated — retry with approval_token. Audited as skill_run.
  • skills__read_resource / skills__render_template → governed file content / rendered text; audited as skill_read / skill_render.
  • Failures return isError: true in the tool result (MCP transport is always HTTP 200); the governance verdict lands on the audit row.

A legacy skill__<name> tools/call is tolerated as an alias for skills__load; it is unadvertised and never executes.

  • MCP servers and skills are resource-group gated — bind them to the caller’s resource group in the Control Plane (Admin API). Skill access control is resource-group-only.
  • Group per request resolves via X-Resource-Group header → primary group → first membership.
  • Guardrails apply on mcp_input/mcp_output, skill_input/skill_output, and a2a_inbound/a2a_outbound surfaces when enabled — see Guardrails.