MCP (Model Context Protocol)
AgentTrust ID acts as a security proxy for Anthropic’s Model Context Protocol, enforcing Guardian checks before tool invocations reach MCP servers.
Overview
MCP standardizes how AI agents access tools, resources, and prompts. AgentTrust ID sits between MCP clients and servers, adding authentication (OAuth 2.1 with PKCE) and authorization (Guardian action checks) to every tool call.
Architecture
Key security properties:
- Every tool call is checked against the agent’s capabilities and Guardian rules
- Denied requests return a JSON-RPC error without reaching the upstream server
- Server registry is scoped per organization via Redis
API Reference
POST /mcp/servers — Register MCP Server
Register a new MCP server in your organization’s registry.
curl -X POST http://localhost:8080/mcp/servers \
-H "Authorization: Bearer sk_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "code-tools",
"url": "http://localhost:9000/mcp",
"description": "Code analysis and generation tools",
"tools": [
{"name": "read_file", "effect_override": "read"},
{"name": "write_file"},
{"name": "run_tests", "effect_override": "mutating"},
{"name": "deploy_prod", "effect_override": "destructive", "require_approval": true}
],
"default_mode": "read_only"
}'Response (201):
{
"id": "uuid",
"org_id": "org-uuid",
"name": "code-tools",
"url": "http://localhost:9000/mcp",
"tools": [
{"name": "read_file", "effect_override": "read"},
{"name": "write_file"},
{"name": "run_tests", "effect_override": "mutating"},
{"name": "deploy_prod", "effect_override": "destructive", "require_approval": true}
],
"default_mode": "read_only",
"status": "active"
}ToolDefinition Schema
Each tool in the tools array is an object with:
| Field | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Tool name (matches JSON-RPC method names) |
effect_override | string | No | Override pattern-based effect classification. Valid values: read, mutating, destructive, admin |
require_approval | boolean | No | Force human-in-the-loop approval for any non-read invocation of this tool |
Default Mode
The default_mode field controls the initial session mode for agents connecting to this server:
| Mode | Description |
|---|---|
read_only | (default) Sessions start in read-only mode. Non-read actions require elevation via human approval. Every action routes through the Guardian pipeline. |
scoped | Sessions allow actions within the scope ceiling without requiring elevation. Every action still routes through the Guardian pipeline. |
If omitted, defaults to read_only. Individual tools can be annotated with require_approval: true to force human-in-the-loop for specific tools regardless of session mode.
GET /mcp/servers — List Servers
curl http://localhost:8080/mcp/servers \
-H "Authorization: Bearer sk_live_..."DELETE /mcp/servers/{serverID} — Remove Server
curl -X DELETE http://localhost:8080/mcp/servers/server-uuid \
-H "Authorization: Bearer sk_live_..."POST /mcp/{serverID} — Proxy to MCP Server
Forward a JSON-RPC request through Guardian security checks.
curl -X POST http://localhost:8080/mcp/server-uuid \
-H "Authorization: Bearer sk_live_..." \
-H "X-Agent-ID: agent-uuid" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "read_file",
"params": {"path": "/src/main.go"}
}'If denied by Guardian:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32600,
"message": "action denied by guardian: agent lacks read_file capability"
}
}OAuth 2.1 Authentication
MCP servers authenticate via AgentTrust ID’s OAuth 2.1 implementation with mandatory PKCE. See OAUTH2.md for the full flow.
Discovery endpoints:
# Authorization server metadata
curl http://localhost:8080/.well-known/oauth-authorization-server
# Protected resource metadata (RFC 9728)
curl http://localhost:8080/.well-known/oauth-protected-resourceThe /.well-known/oauth-protected-resource endpoint returns metadata about AgentTrust ID as an OAuth 2.0 protected resource, including supported scopes and bearer token methods:
{
"resource": "http://localhost:8080",
"authorization_servers": ["http://localhost:8080"],
"scopes_supported": [
"mcp:tool_call",
"mcp:resource_read",
"mcp:prompt_read",
"mcp:admin"
],
"bearer_methods_supported": ["header"]
}CIMD (Client ID Metadata Document)
MCP requires HTTPS URL-based client IDs per the OAuth 2.1 specification. When a client presents an https:// URL as its client_id, AgentTrust ID triggers the CIMD flow to fetch and validate the client’s metadata document from that URL.
CIMD Flow
- Authorization request — Client sends
GET /mcp/oauth/authorize?client_id=https://example.com/client&redirect_uri=... - Metadata fetch — AgentTrust ID fetches the JSON metadata document from the
client_idURL over HTTPS (30-second timeout). Fetched documents are cached in Redis with acimd:key prefix and a 5-minute TTL. - Validation — AgentTrust ID validates the metadata document:
client_idin the document must exactly match the URLredirect_urismust be non-empty and all use HTTPSclient_namemust be non-empty- The requested
redirect_urimust exactly match one of the document’s registered redirect URIs (no wildcards, per OAuth 2.1)
- Internal ID mapping — AgentTrust ID generates a UUID as the internal client ID and stores the URL-to-UUID mapping in Redis (
cimd_client:prefix, 1-hour TTL). - Scope extraction — The scope is read from the document’s
scopefield. If empty, it defaults tomcp:tool_call. - Token issuance — On
POST /mcp/oauth/token, if theclient_idis an HTTPS URL, AgentTrust ID resolves it to the internal UUID via Redis before issuing a bearer token with formatmcp_{uuid-prefix}_{random-hex}.
CIMD Document Schema
{
"client_id": "https://example.com/client",
"client_name": "My MCP Client",
"redirect_uris": ["https://example.com/callback"],
"grant_types": ["authorization_code"],
"scope": "mcp:tool_call mcp:resource_read",
"response_types": ["code"],
"token_endpoint_auth_method": "none",
"contacts": ["admin@example.com"]
}Non-HTTPS client_id values are rejected with an invalid_client error.
Guardian Integration
The proxy calls POST /api/v1/actions/check before every tool invocation:
{
"agent_id": "agent-uuid",
"action": "tool_call",
"tool_name": "read_file"
}The check evaluates:
- Agent’s registered capabilities
- Organization policy rules
- Agent revocation status (Redis cache)
- Guardian tier rules (fast/spot/deep)
SDK Examples
Python
from agenttrustid import AgentTrustClient
client = AgentTrustClient(api_key="sk_live_...")
# Register an MCP server with per-tool effect annotations
server = client.mcp.register_server(
name="code-tools",
url="http://localhost:9000/mcp",
tools=[
{"name": "read_file", "effect_override": "read"},
{"name": "write_file"},
],
default_mode="read_only",
)
# Invoke a tool through the proxy
result = client.mcp.call_tool(
server_id=server["id"],
agent_id="agent-uuid",
method="read_file",
params={"path": "/src/main.go"},
)TypeScript
import { AgentTrustClient } from "@agenttrustid/sdk";
const client = new AgentTrustClient({ apiKey: "sk_live_..." });
const server = await client.mcp.registerServer({
name: "code-tools",
url: "http://localhost:9000/mcp",
tools: [
{ name: "read_file", effectOverride: "read" },
{ name: "write_file" },
],
defaultMode: "read_only",
});
const result = await client.mcp.callTool({
serverId: server.id,
agentId: "agent-uuid",
method: "read_file",
params: { path: "/src/main.go" },
});Server Registry
MCP servers are stored in Redis with a 24-hour TTL per entry. Each organization has an independent server index, ensuring complete tenant isolation.
Key structure:
mcp_server:{orgID}:{serverID}— individual server recordmcp_servers:{orgID}— org-level index of server IDs
Session Management
AgentTrust ID 1.0 introduces session-based authorization for MCP tool calls. Sessions track privilege mode, scope boundaries, and call metrics. Sessions have a 1-hour sliding-window TTL — the TTL is refreshed on every authorization check, so active sessions stay alive indefinitely while idle sessions expire automatically.
Initialize Session
POST /mcp/sessions/init
Content-Type: application/json
X-Org-ID: {orgID}
{
"agent_id": "agent-123",
"server_id": "my-mcp-server"
}Response (201):
{
"session_id": "sess-abc123",
"mode": "read_only",
"scope_ceiling": ["web_search", "read_file", "write_file"],
"source": "mcp",
"created_at": "2026-03-04T10:00:00Z"
}The scope ceiling is set from the server’s registered tool names. The initial session mode depends on the server’s default_mode: scoped allows actions within the ceiling, while read_only (default) requires elevation for non-read actions.
Retrieve Session
GET /mcp/sessions/{sessionID}Returns the full session state including mode, counters, and elevation status.
Session Modes
- read_only (default): Only
read-classified actions are allowed through the Guardian pipeline. Mutating/destructive actions trigger an elevation request. - elevated: Time-boxed (5-minute) elevation for specific actions. Auto-reverts to
read_onlywhen TTL expires.
Approval Workflow
When a read_only session encounters a mutating or destructive action, the proxy returns an elevation-required error with an approval ID. Approval requests are stored in Redis with a 5-minute TTL — if not approved or denied within 5 minutes, the approval expires and the agent must retry the tool call to generate a new one.
Elevation Flow
- Agent calls tool — proxy returns JSON-RPC error code
-32001:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32001,
"message": "elevation required for 'write_file' (approval_id: apr-xyz)"
}
}- Admin approves:
POST /mcp/approvals/apr-xyz/approve
Content-Type: application/json
{
"decided_by": "admin@company.com"
}-
Session is elevated for
write_filefor 5 minutes. Elevation is per-action — only the specific action that triggered the approval (write_filein this case) is elevated, not all mutating actions. The elevation scope is set to[]string{req.ActionName}, so other mutating tools (e.g.,delete_file) still require separate approval. -
Agent retries tool call — succeeds.
Approve Elevation
POST /mcp/approvals/{approvalID}/approveDeny Elevation
POST /mcp/approvals/{approvalID}/denyGet Approval Status
GET /mcp/approvals/{approvalID}Response:
{
"id": "apr-xyz",
"session_id": "sess-abc",
"agent_id": "agent-123",
"action_name": "write_file",
"action_effect": "mutating",
"action_source": "mcp",
"input_summary": "path=/src/main.go, content=package main...",
"status": "approved",
"decided_by": "admin@company.com",
"created_at": "2026-03-04T10:00:00Z",
"expires_at": "2026-03-04T10:05:00Z"
}AgentTrust ID Integration
The MCP proxy integrates with the UnifiedChecker for every tool call:
- Parse JSON-RPC request to extract method name
- Look up per-tool annotations from
ToolDefinition(effect override, require_approval) - Build
AgentTrustRequestwith agent_id, org_id, session_id (fromX-Session-IDheader), action_name, and effect hints - Effect is classified using override if set, otherwise auto-classified by pattern matching (e.g.,
delete_file— destructive) - UnifiedChecker routes every action through the Guardian pipeline based on effect classification
- Tools with
require_approval: non-read actions require human approval even if guardian approves - Allowed — forward to upstream MCP server
- Denied — return JSON-RPC error (-32600 or -32001 with approval_id)
X-Session-ID Header: SDKs pass the session ID via X-Session-ID header on tool calls. This binds the tool call to the session’s scope and privilege mode.
ScopeEnforcer
The ScopeEnforcer centralizes MCP token scope storage and enforcement. It is used by the auth handler during token issuance and by the proxy during request authorization.
Token Scope Storage
When the token endpoint issues an access token, ScopeEnforcer.StoreTokenScope() writes the token-to-scope mapping into Redis under the key mcp_token_scope:{accessToken} with a 1-hour TTL (matching the token’s expires_in).
Scope Enforcement During Proxy Requests
On each proxied tool call, the enforcer provides two checks:
CheckScopeCeiling(scopeCeiling, actionName)— Verifies the requested action is within the session’s scope ceiling (derived from the MCP server’s registered tools). An empty ceiling permits all actions.CheckAllowedActions(allowedActions, actionName)— Verifies the action is in the session’s explicit allowed-actions list. An empty list permits all actions.
Scope Retrieval
ScopeEnforcer.GetTokenScope(ctx, accessToken) retrieves the scope string for a given OAuth token from Redis, enabling the proxy to verify that the token’s granted scope covers the requested operation.