MCP Gateway
Quick example
- curl
- Python
- Node.js
# 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"}
}'
import os, requests
resp = requests.post(
f"https://mcp.trinitite.ai/v1/sessions/{os.environ['SESSION_ID']}/tools/call",
headers={"Authorization": f"Bearer {os.environ['TRINITITE_API_KEY']}"},
json={
"tool": "stripe.create_refund",
"args": {"charge": "ch_123", "amount": 1099, "reason": "requested_by_customer"},
},
)
print(resp.json())
const resp = await fetch(`https://mcp.trinitite.ai/v1/sessions/${process.env.SESSION_ID}/tools/call`, {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.TRINITITE_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
tool: 'stripe.create_refund',
args: { charge: 'ch_123', amount: 1099, reason: 'requested_by_customer' },
}),
});
console.log(await resp.json());
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
| Method | Path | Purpose |
|---|---|---|
POST | /v1/mcp | JSON-RPC 2.0 message |
GET | /v1/mcp | Open Server-Sent Events stream for server-initiated notifications |
DELETE | /v1/mcp | Terminate the session |
GET | /.well-known/oauth-protected-resource | RFC 9728 Protected Resource Metadata (public) |
Upstream server registry
| Method | Path | Permission |
|---|---|---|
GET | /v1/mcp/servers | mcp:read |
POST | /v1/mcp/servers | mcp:create |
GET | /v1/mcp/servers/catalog | mcp: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}/refresh | mcp:update |
Per-tool Guardian bindings
| Method | Path | Permission |
|---|---|---|
GET | /v1/mcp/tool-guardian-bindings | mcp:read |
POST | /v1/mcp/tool-guardian-bindings | mcp: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
| Method | Path | Auth | Notes |
|---|---|---|---|
POST | /v1/mcp/oauth/register | none (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/consent | API 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/authorize | API key (mcp:create) | PKCE authorization code |
POST | /v1/mcp/oauth/token | none (creds in body) | Exchange code or refresh token |
POST | /v1/mcp/oauth/revoke | none | RFC 7009 token revocation |
Analytics & alerts
| Method | Path | Permission |
|---|---|---|
GET | /v1/mcp/analytics/summary | mcp:read |
GET | /v1/mcp/analytics/tools | mcp:read |
POST | /v1/mcp/analytics/rollup | mcp:update |
GET | /v1/mcp/analytics/sessions | mcp: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-audit | mcp: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.
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.
| Mode | Behaviour |
|---|---|
passthrough | All tool calls pass without evaluation |
rules | Deterministic policy engine — block/allow lists, argument constraints, sequence analysis, credential redaction |
slm | AI-powered governance — resolves the NHI's assigned Guardian and runs its LoRA adapter to evaluate each call |
chained | Two-stage: rules first (fast-path block / correct), then SLM on the sanitised payload |
| Phase | Trigger | Default behaviour |
|---|---|---|
pre_tool_call | Before tools/call is forwarded upstream | Block/allow list, argument constraints, sequence rules |
post_tool_result | After upstream returns a tool result | Credential redaction; outputSchema validation |
resource_read | After resources/read | URI block-list; credential redaction in content |
prompt_fetch | After prompts/get | Prompt-injection detection; credential redaction |
server_to_client | When upstream initiates a message back to the client | Method 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.
| Event | Trigger | Governed |
|---|---|---|
notifications/tools/list_changed | Any upstream's tool catalogue refreshed | Yes |
elicitation/create, sampling/createMessage, roots/list | Relayed from an upstream | Yes (default policy blocks server_method:sampling/createMessage unless allowlisted) |
notifications/progress, notifications/cancelled, notifications/message, notifications/resources/updated | Upstream-initiated | Yes — 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
}
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Unique namespace (lowercase alphanumeric + hyphens). Becomes the tool-name prefix |
label | string | No | Display label |
url | string | Required for streamable_http | HTTP endpoint of the upstream server |
command | string | Required for stdio | Shell command for stdio transport |
args | string[] | No | Arguments for the stdio command |
transport | enum | Yes | streamable_http or stdio |
auth_type | enum | Yes | none, bearer, or api_key |
credential_id | string | No | Vaulted Provider Credential for upstream auth |
enabled | boolean | No | Default 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):
- Tool-specific enabled binding (
original_tool_nameexact match) - Server-wide enabled binding (
original_tool_nameisnull) - 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
}
| Field | Type | Description |
|---|---|---|
upstream_server_id | string | The registered upstream (mcp_*) |
original_tool_name | string | null | Omit / null for a server-wide binding |
guardian_id | string | Guardian that must evaluate this tool |
frozen_policy_hash | string | Compared against the active policy on every call. A mismatch returns -32004 TOOL_BLOCKED |
determinism_mode | enum | strict, relaxed, or off. Default relaxed |
receipt_required | boolean | When true, reject the call if the inference service did not return a deterministic receipt. Use for regulated / high-risk tools |
required_scopes | string | Space-separated OAuth scopes the caller must present, enforced alongside NHI privilege tier |
enabled | boolean | Soft-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
| Code | Symbol | When |
|---|---|---|
-32001 | MCP_SESSION_INVALID | Missing or unknown Mcp-Session-Id |
-32002 | UPSTREAM_UNAVAILABLE | Registered upstream not reachable |
-32003 | OAUTH_CONSENT_REQUIRED | No active consent grant; client is untrusted |
-32004 | TOOL_BLOCKED | Pre- or post-tool governance verdict was block (or schema validation failed) |
-32005 | RECEIPT_REQUIRED | Binding 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..."
}
}
| HTTP | error.code | Cause |
|---|---|---|
400 | validation_error | Body or query failed schema validation |
401 | unauthenticated | Missing or invalid credential |
403 | forbidden | Caller lacks the required mcp:* permission |
403 | consent_required | Client untrusted, no consent grant (authorize endpoint) |
404 | not_found | Server, binding, client, or consent not found |
409 | conflict | Server name collision, duplicate binding |
422 | unprocessable_entity | Upstream rejected the registration |
429 | rate_limited | Per-organization rate limit exceeded |
Next steps
- Stand up an upstream server credential → Provider Credentials
- Train a tool-specialist Guardian → Guardians API
- Bind an autonomous identity to a Guardian → Identities
- Govern an LLM call via Hosted MCP forwarding → Proxy
- Understand NHI auto-routing and tier escalation → NHI Governance
- View MCP governance verdicts in the audit trail → Attestation & Compliance