Skip to Content

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

MCP proxy request sequence An MCP Client posts to /mcp/id on the AgentTrust ID Gateway. The Gateway sends an action check to the Auth Service, which returns allowed or denied. On allow the Gateway forwards the JSON-RPC request to the MCP Server, receives the upstream response, and returns the response to the MCP Client. MCP Client AgentTrust ID Gateway Auth Service MCP Server POST /mcp/id action check GW: allowed/denied --> allowed/denied forward JSON-RPC GW: upstream response --> upstream response C: response --> response

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:

FieldTypeRequiredDescription
namestringYesTool name (matches JSON-RPC method names)
effect_overridestringNoOverride pattern-based effect classification. Valid values: read, mutating, destructive, admin
require_approvalbooleanNoForce 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:

ModeDescription
read_only(default) Sessions start in read-only mode. Non-read actions require elevation via human approval. Every action routes through the Guardian pipeline.
scopedSessions 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-resource

The /.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

  1. Authorization request — Client sends GET /mcp/oauth/authorize?client_id=https://example.com/client&redirect_uri=...
  2. Metadata fetch — AgentTrust ID fetches the JSON metadata document from the client_id URL over HTTPS (30-second timeout). Fetched documents are cached in Redis with a cimd: key prefix and a 5-minute TTL.
  3. Validation — AgentTrust ID validates the metadata document:
    • client_id in the document must exactly match the URL
    • redirect_uris must be non-empty and all use HTTPS
    • client_name must be non-empty
    • The requested redirect_uri must exactly match one of the document’s registered redirect URIs (no wildcards, per OAuth 2.1)
  4. 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).
  5. Scope extraction — The scope is read from the document’s scope field. If empty, it defaults to mcp:tool_call.
  6. Token issuance — On POST /mcp/oauth/token, if the client_id is an HTTPS URL, AgentTrust ID resolves it to the internal UUID via Redis before issuing a bearer token with format mcp_{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 record
  • mcp_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_only when 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

  1. 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)" } }
  1. Admin approves:
POST /mcp/approvals/apr-xyz/approve Content-Type: application/json { "decided_by": "admin@company.com" }
  1. Session is elevated for write_file for 5 minutes. Elevation is per-action — only the specific action that triggered the approval (write_file in 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.

  2. Agent retries tool call — succeeds.

Approve Elevation

POST /mcp/approvals/{approvalID}/approve

Deny Elevation

POST /mcp/approvals/{approvalID}/deny

Get 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:

  1. Parse JSON-RPC request to extract method name
  2. Look up per-tool annotations from ToolDefinition (effect override, require_approval)
  3. Build AgentTrustRequest with agent_id, org_id, session_id (from X-Session-ID header), action_name, and effect hints
  4. Effect is classified using override if set, otherwise auto-classified by pattern matching (e.g., delete_file — destructive)
  5. UnifiedChecker routes every action through the Guardian pipeline based on effect classification
  6. Tools with require_approval: non-read actions require human approval even if guardian approves
  7. Allowed — forward to upstream MCP server
  8. 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.

Last updated on