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.
MCP gateway
Section titled “MCP gateway”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 |
Protocol versions & dialects
Section titled “Protocol versions & dialects”The gateway is bilingual across the MCP spec’s two eras (the spec calls this “dual-era”):
- Handshake dialect (≤ 2025-11-25):
initialize→Mcp-Session-Id→notifications/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 theMCP-Protocol-Version/Mcp-Method/Mcp-NameHTTP headers; capability discovery via theserver/discovermethod. The gateway’s synthesized servers (VMCP, Agent Skills, Web Search) are dual-era — they answer bothinitializeandserver/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 adualprotocol badge.
Two behaviors are always on, regardless of the flag:
- Header/body consistency enforcement — a request whose
Mcp-MethodorMcp-Nameheader contradicts the JSON-RPC body is rejected with HTTP400+ 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/discoverfirst when the flag is on,initializefallback) and records each server’s negotiatedprotocol_versionanddialect(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.
Tool call example
Section titled “Tool call example”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": {...}}.
Virtual MCP (VMCP)
Section titled “Virtual MCP (VMCP)”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.
OAuth proxy
Section titled “OAuth proxy”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.
A2A v1.0 (spec-compliant)
Section titled “A2A v1.0 (spec-compliant)”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 |
Discovery (unauthenticated)
Section titled “Discovery (unauthenticated)”| 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.
curl http://localhost:8100/.well-known/agent-card/default/revenue-sentinelLegacy A2A v0.3 dialect
Section titled “Legacy A2A v0.3 dialect”| 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.
Agent Skills
Section titled “Agent Skills”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 + unmodifiedSKILL.md, then{"skill": {name, version, description}, "manifest": {resources, scripts, templates}}. Executes nothing; audited asskill_load.skills__run_script→ the script’s output only (sandbox execution), or a{"execution_id", "status": "pending_approval", ...}envelope when approval-gated — retry withapproval_token. Audited asskill_run.skills__read_resource/skills__render_template→ governed file content / rendered text; audited asskill_read/skill_render.- Failures return
isError: truein 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.
Access control
Section titled “Access control”- 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-Groupheader → primary group → first membership. - Guardrails apply on
mcp_input/mcp_output,skill_input/skill_output, anda2a_inbound/a2a_outboundsurfaces when enabled — see Guardrails.

