Skip to main content

MCP Gateway

Quick example

# Make a governed tool call
curl https://mcp.trinitite.ai/v1/sessions/$SESSION_ID/tools/call \
-H "Authorization: Bearer $TRINITITE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": "stripe.create_refund",
"args": {"charge": "ch_123", "amount": 1099, "reason": "requested_by_customer"}
}'

See MCP Gateway architecture and Cookbook → Govern an MCP tool call for the full picture.

Tool-call lifecycle


Overview

The MCP Gateway is Trinitite's aggregation layer for Model Context Protocol traffic. AI clients (Claude Desktop, Cursor, OpenAI Agents SDK, etc.) connect to Trinitite as their single MCP server. The gateway fans out to all of your registered upstream MCP servers, merges their tool / resource / prompt catalogues, and applies governance at every boundary where untrusted content can cross — from upstream to client, from client to upstream, and between server-initiated server-to-client messages.

This is distinct from provider-hosted MCP (OpenAI Responses API tools: [{ "type": "mcp" }], Anthropic mcp_servers), which is forwarded through the LLM Proxy and logged alongside the completion. Trinitite supports both — hosted MCP for fast integration with public connectors, the Gateway for an organisation's own internal or high-assurance tool surfaces.


Endpoints

Transport

MethodPathPurpose
POST/v1/mcpJSON-RPC 2.0 message
GET/v1/mcpOpen Server-Sent Events stream for server-initiated notifications
DELETE/v1/mcpTerminate the session
GET/.well-known/oauth-protected-resourceRFC 9728 Protected Resource Metadata (public)

Upstream server registry

MethodPathPermission
GET/v1/mcp/serversmcp:read
POST/v1/mcp/serversmcp:create
GET/v1/mcp/servers/catalogmcp:read
GET/v1/mcp/servers/{id}mcp:read
PATCH/v1/mcp/servers/{id}mcp:update
DELETE/v1/mcp/servers/{id}mcp:delete
POST/v1/mcp/servers/{id}/refreshmcp:update

Per-tool Guardian bindings

MethodPathPermission
GET/v1/mcp/tool-guardian-bindingsmcp:read
POST/v1/mcp/tool-guardian-bindingsmcp:create
GET/v1/mcp/tool-guardian-bindings/{id}mcp:read
PATCH/v1/mcp/tool-guardian-bindings/{id}mcp:update
DELETE/v1/mcp/tool-guardian-bindings/{id}mcp:delete

OAuth 2.1

MethodPathAuthNotes
POST/v1/mcp/oauth/registernone (RFC 7591)Dynamic client registration. Pass X-Organization-Id to scope
GET/v1/mcp/oauth/clients/{clientId}API key (mcp:read)
DELETE/v1/mcp/oauth/clients/{clientId}API key (mcp:delete)
POST/v1/mcp/oauth/consentAPI key (mcp:create)Record a consent grant
GET/v1/mcp/oauth/consent/{clientId}API key (mcp:read)
DELETE/v1/mcp/oauth/consent/{consentId}API key (mcp:delete)
POST/v1/mcp/oauth/authorizeAPI key (mcp:create)PKCE authorization code
POST/v1/mcp/oauth/tokennone (creds in body)Exchange code or refresh token
POST/v1/mcp/oauth/revokenoneRFC 7009 token revocation

Analytics & alerts

MethodPathPermission
GET/v1/mcp/analytics/summarymcp:read
GET/v1/mcp/analytics/toolsmcp:read
POST/v1/mcp/analytics/rollupmcp:update
GET/v1/mcp/analytics/sessionsmcp:read
GET/v1/mcp/analytics/sessions/{sessionId}mcp:read
GET / POST / PATCH / DELETE/v1/mcp/analytics/alerts/rules[/...]mcp:*
GET / POST / PATCH/v1/mcp/analytics/alerts/events[/...]mcp:*
GET / POST/v1/mcp/analytics/reports[/...]mcp:read / mcp:create
GET/v1/mcp/analytics/config-auditmcp:read

For the higher-level analytics aggregates that combine MCP with Guardian and NHI dimensions, see the Analytics API.


Governance modes & phases

The gateway evaluates traffic at every boundary where untrusted content can cross. All phases share the same pass / correct / block verdict vocabulary.

NHI auto-routing

When a request arrives with an NHI credential, the gateway automatically resolves the Guardian from the NHI's active assignment — no per-call guardian parameter required. The NHI's privilege tier (T0–T3) is enforced alongside any required_scopes on the tool binding, preventing scope creep at the identity layer. See NHI Governance for the full model.

ModeBehaviour
passthroughAll tool calls pass without evaluation
rulesDeterministic policy engine — block/allow lists, argument constraints, sequence analysis, credential redaction
slmAI-powered governance — resolves the NHI's assigned Guardian and runs its LoRA adapter to evaluate each call
chainedTwo-stage: rules first (fast-path block / correct), then SLM on the sanitised payload
PhaseTriggerDefault behaviour
pre_tool_callBefore tools/call is forwarded upstreamBlock/allow list, argument constraints, sequence rules
post_tool_resultAfter upstream returns a tool resultCredential redaction; outputSchema validation
resource_readAfter resources/readURI block-list; credential redaction in content
prompt_fetchAfter prompts/getPrompt-injection detection; credential redaction
server_to_clientWhen upstream initiates a message back to the clientMethod block-list; credential redaction in params

JSON-RPC transport

Three methods on a single path, differentiated by HTTP verb. The Mcp-Session-Id response header from initialize identifies the active session; subsequent JSON-RPC calls must echo it back.

Initialize

{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {},
"clientInfo": { "name": "my-app", "version": "1.0.0" }
}
}

The response advertises the negotiated protocol version and the standard MCP server capabilities. For tenants that opt into governance receipts, the gateway additionally returns the active policy_hash, the supported determinism mode, and the receipt endpoint URL — see MCP Gateway architecture for the full handshake contract.

{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"serverInfo": { "name": "Trinitite MCP Gateway", "version": "1.0.0" },
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": false, "subscribe": false },
"prompts": { "listChanged": false }
}
}
}

Supported protocol versions: 2025-11-25, 2025-06-18, 2025-03-26.

Server-Sent Events stream

GET /v1/mcp opens an SSE stream for server-initiated notifications.

EventTriggerGoverned
notifications/tools/list_changedAny upstream's tool catalogue refreshedYes
elicitation/create, sampling/createMessage, roots/listRelayed from an upstreamYes (default policy blocks server_method:sampling/createMessage unless allowlisted)
notifications/progress, notifications/cancelled, notifications/message, notifications/resources/updatedUpstream-initiatedYes — credential patterns in params are redacted

Messages whose verdict is block are never delivered; the gateway emits notifications/message at level warning with the block reason and persists the event to the governance log.

Terminate the session

DELETE /v1/mcp with Mcp-Session-Id returns 204 No Content.


Upstream server registry

Upstream servers are the MCP servers the gateway connects to on behalf of AI clients. Tools from each upstream are namespaced by upstream name + __ (e.g. github__create_issue).

POST /v1/mcp/servers

{
"name": "github",
"label": "GitHub MCP",
"url": "https://mcp.example-internal.com/github",
"transport": "streamable_http",
"auth_type": "bearer",
"credential_id": "cred_01JF8RCRD1A2B3C4D5E6F7G8H9I",
"enabled": true
}
FieldTypeRequiredDescription
namestringYesUnique namespace (lowercase alphanumeric + hyphens). Becomes the tool-name prefix
labelstringNoDisplay label
urlstringRequired for streamable_httpHTTP endpoint of the upstream server
commandstringRequired for stdioShell command for stdio transport
argsstring[]NoArguments for the stdio command
transportenumYesstreamable_http or stdio
auth_typeenumYesnone, bearer, or api_key
credential_idstringNoVaulted Provider Credential for upstream auth
enabledbooleanNoDefault true

SSRF defence: when auth_type=bearer, the OAuth token URL must be https:// and must not resolve to loopback, private, or link-local hosts. RFC 8707 audience binding is enforced — a token scoped for one upstream cannot be replayed against another.

GET /v1/mcp/servers/catalog

Aggregated tool catalogue across all connected upstreams.

{
"total": 14,
"tools": [
{
"tool_name": "github__create_issue",
"original_name": "create_issue",
"server_id": "mcp_01JF8RMCP1A2B3C4D5E6F7G8H9I",
"server_name": "github",
"description": "Create an issue in a GitHub repository.",
"title": "Create Issue",
"input_schema": { "type": "object", "properties": { /* ... */ } },
"output_schema": { "type": "object", "properties": { /* ... */ } },
"annotations": {
"readOnlyHint": false,
"destructiveHint": false,
"idempotentHint": false,
"openWorldHint": true
},
"refreshed_at": "2026-05-01T09:00:00Z"
}
]
}

The gateway uses annotations.readOnlyHint to decide whether a tool bypasses post-result side-effect scanning, and validates successful tool responses against output_schema — a validation failure is treated as a post-governance block (-32004 TOOL_BLOCKED), preventing schema-drift exploits.


Per-tool Guardian bindings

A binding pins one upstream tool (or an entire upstream server) to a specific Guardian, frozen policy hash, determinism mode, and required OAuth scope set — overriding the NHI-level Guardian assignment that would otherwise resolve at call time. This is how operators "certify" a governance configuration for a high-risk tool.

Resolution order when governing a call to (upstream_server_id, original_tool_name):

  1. Tool-specific enabled binding (original_tool_name exact match)
  2. Server-wide enabled binding (original_tool_name is null)
  3. NHI-Guardian assignment fallback

POST /v1/mcp/tool-guardian-bindings

{
"upstream_server_id": "mcp_01JF8RMCP1A2B3C4D5E6F7G8H9I",
"original_tool_name": "create_issue",
"guardian_id": "gov_01JF8R3M3X4N5Q6T7V8W9Y0Z1A",
"frozen_policy_hash": "sha256:8f3c...",
"determinism_mode": "strict",
"receipt_required": true,
"required_scopes": "issues:write",
"enabled": true
}
FieldTypeDescription
upstream_server_idstringThe registered upstream (mcp_*)
original_tool_namestring | nullOmit / null for a server-wide binding
guardian_idstringGuardian that must evaluate this tool
frozen_policy_hashstringCompared against the active policy on every call. A mismatch returns -32004 TOOL_BLOCKED
determinism_modeenumstrict, relaxed, or off. Default relaxed
receipt_requiredbooleanWhen true, reject the call if the inference service did not return a deterministic receipt. Use for regulated / high-risk tools
required_scopesstringSpace-separated OAuth scopes the caller must present, enforced alongside NHI privilege tier
enabledbooleanSoft-disable without deleting

OAuth 2.1

Standards-compliant OAuth 2.1 authorization for MCP clients that need delegated access to upstream servers. Implements RFC 7591 (Dynamic Client Registration), RFC 9728 (Protected Resource Metadata), RFC 7009 (Token Revocation), and PKCE.

Public metadata

GET /.well-known/oauth-protected-resource — RFC 9728 Protected Resource Metadata describing the OAuth configuration.

POST /v1/mcp/oauth/register

Dynamic client registration. Public — no API key. Pass X-Organization-Id to scope the client.

{
"client_name": "Cursor — Acme Corp",
"redirect_uris": ["https://cursor.example/oauth/callback"],
"grant_types": ["authorization_code", "refresh_token"],
"response_types": ["code"],
"scope": "tools:read tools:execute",
"client_type": "public"
}

Response — 201 Created returns client metadata. For confidential clients a client_secret is also returned, once only.

Authorization-code flow

1. POST /v1/mcp/oauth/consent             ← record user consent
2. POST /v1/mcp/oauth/authorize ← issue PKCE code (returns directly, no redirect)
3. POST /v1/mcp/oauth/token ← exchange code for access_token + refresh_token
4. JSON-RPC calls with Bearer access_token

POST /v1/mcp/oauth/authorize accepts an optional upstream_server_id. When present, the gateway issues the code only if the client is trusted: true or an active consent grant exists for (client_id, user_id, upstream_server_id) — a deliberate confused-deputy mitigation.

Token revocation

POST /v1/mcp/oauth/revoke — RFC 7009. No API key. Always returns 200 OK per spec, even if the token is unknown.


Analytics

GET /v1/mcp/analytics/summary

{
"from": "2026-04-30T00:00:00Z",
"to": "2026-05-01T00:00:00Z",
"totals": {
"tool_calls": 4123,
"passed": 3812,
"corrected": 264,
"blocked": 47
},
"top_tools": [
{ "tool": "github__create_issue", "calls": 412, "block_rate": 0.041 },
{ "tool": "postgres__query", "calls": 1840, "block_rate": 0.005 }
]
}

POST /v1/mcp/analytics/rollup

Trigger an on-demand rollup over a time window. Returns 202 Accepted with a rollup_id.

GET /v1/mcp/analytics/config-audit

Append-only audit log of every change to the gateway configuration — server registrations, binding edits, alert-rule changes — with actor, before, after, and request_id.


JSON-RPC error codes

CodeSymbolWhen
-32001MCP_SESSION_INVALIDMissing or unknown Mcp-Session-Id
-32002UPSTREAM_UNAVAILABLERegistered upstream not reachable
-32003OAUTH_CONSENT_REQUIREDNo active consent grant; client is untrusted
-32004TOOL_BLOCKEDPre- or post-tool governance verdict was block (or schema validation failed)
-32005RECEIPT_REQUIREDBinding requires a deterministic receipt; inference service did not return one

REST error responses

{
"error": {
"code": "consent_required",
"message": "Client is untrusted and no matching consent grant exists.",
"details": { "client_id": "mcp_01J...", "upstream_server_id": "mcp_01K..." },
"request_id": "req_01J..."
}
}
HTTPerror.codeCause
400validation_errorBody or query failed schema validation
401unauthenticatedMissing or invalid credential
403forbiddenCaller lacks the required mcp:* permission
403consent_requiredClient untrusted, no consent grant (authorize endpoint)
404not_foundServer, binding, client, or consent not found
409conflictServer name collision, duplicate binding
422unprocessable_entityUpstream rejected the registration
429rate_limitedPer-organization rate limit exceeded

Next steps