MCP-I Enforcement
Block AI agents at the edge until they present a cryptographic identity via HTTP Message Signatures or MCP-I delegations
Overview
MCP-I enforcement is a policy action (instruct) that refuses to serve a page to an AI agent until the agent presents a verifiable cryptographic identity. Instead of the best-effort detection-and-redirect approach (User-Agent pattern matching, TLS fingerprinting), this path uses a standards-based HTTP challenge: the gateway returns a 401 Unauthorized with structured instructions, and the agent must retry with a signed HTTP Message Signature (RFC 9421) or an MCP-I delegation proof.
Unlike detection-based enforcement, MCP-I enforcement is bypass-proof for agents that cannot forge a valid signature — the cryptographic identity check does not depend on User-Agent strings, IP ranges, or TLS fingerprints.
Use MCP-I enforcement for auth-critical surfaces (payment APIs, data exports, admin actions) where false negatives in detection are unacceptable. Use detection-based enforcement for general content protection where a soft redirect to a consent page is sufficient.
Detection vs Identity
Checkpoint offers two tiers of agent enforcement. They are complementary and can be used on different endpoints of the same project.
Detection (redirect, block) | Identity (instruct) | |
|---|---|---|
| What it checks | User-Agent patterns, TLS fingerprints, IP ranges | Cryptographic signature (RFC 9421) or MCP-I proof |
| Bypass-resistant? | No — agents can evade with tool delegation or spoof | Yes — cannot forge a valid Ed25519 signature |
| Agent cooperation | Not required | Required (agent must implement the protocol) |
| Catches unknown UAs | Yes (heuristic) | No (unsigned agents fail the challenge) |
| Catches desktop apps | Partial (TLS fingerprinting on gateway path only) | Yes (if the app signs its requests) |
| Catches tool proxies | No (third-party tools have their own fingerprints) | Yes (the proxied request won't carry a valid proof) |
| Use cases | Marketing pages, soft consent flows, content surfaces | Payment APIs, data exports, admin actions |
The Three-Round Flow
When an agent hits an endpoint configured with default_action: 'instruct', the gateway does not serve the page. Instead it returns a structured 401 challenge. The agent must obtain authorization out-of-band and retry with a signed proof.
Round 1 — Agent requests the protected resource
GET /protected HTTP/1.1
Host: api.customer.com
User-Agent: (ChatGPT, Claude, Comet, or any agent)The gateway runs detection, looks up the project policy, sees default_action: 'instruct', and responds with a 401.
Round 2 — Gateway returns a structured challenge
HTTP/1.1 401 Unauthorized
Content-Type: application/json
WWW-Authenticate: KYA realm="api", authorization_uri="https://kya.vouched.id/api/v1/bouncer/authorize?project_id=proj-xxx&..."
Link: <https://kya.vouched.id/api/v1/bouncer/authorize?...>; rel="kya-authorize", <https://docs.knowthat.ai/mcp-i/getting-started>; rel="help"
KYA-Action: instruct
KYA-Auth-Required: true
KYA-Auth-Url: https://kya.vouched.id/api/v1/bouncer/authorize?...
KYA-Project: proj-xxx
Cache-Control: no-store
{
"message": "I'm unable to access api.customer.com on your behalf. This website requires you to install their app or extension before an AI assistant can access it.\n\nPlease visit the following link to set up access:\nhttps://kya.vouched.id/api/v1/bouncer/authorize?...\n\nOnce you have completed the setup, ask me to try again.",
"user_action_required": {
"action": "Install the app or extension for this website",
"url": "https://kya.vouched.id/api/v1/bouncer/authorize?...",
"reason": "api.customer.com has AI agent protection enabled and requires authentication before allowing AI access."
},
"mcp_i": {
"version": "1.0",
"action": "authenticate",
"authorization_url": "https://kya.vouched.id/api/v1/bouncer/authorize?...",
"project_id": "proj-xxx",
"required_scopes": ["api:read"],
"flow": {
"type": "oauth2_delegation",
"steps": [
"1. Direct your user to the authorization_url",
"2. User reviews requested scopes and grants consent",
"3. Receive delegation credential (JWT)",
"4. Include credential in KYA-Delegation header",
"5. Retry this request with the proof"
]
},
"retry_instructions": {
"header": "KYA-Delegation",
"format": "JWT delegation credential from authorization flow"
},
"documentation": "https://docs.knowthat.ai/mcp-i/getting-started"
},
"error": "mcp_authentication_required",
"code": "AGENT_REQUIRES_DELEGATION",
"detection": {
"agent_type": "ai_agent",
"agent_name": "ChatGPT",
"confidence": 95,
"verification_method": "pattern"
}
}The response is engineered for two audiences:
- Current LLMs (ChatGPT, Claude, Perplexity) read the
messagefield as plain text and relay it to the user, who then completes the authorization flow in their browser. - MCP-I-aware agents parse the
mcp_iobject programmatically, callauthorization_urlto obtain a delegation, and retry automatically.
The standard WWW-Authenticate: KYA header (RFC 7235) signals to HTTP clients that authentication is required and advertises the authorization endpoint. The Link header (RFC 8288) provides a discoverable relation to the MCP-I docs.
Round 3 — Agent obtains a delegation and retries with a proof
The user (or the agent, if it is MCP-I-capable) visits authorization_url, signs in, and reviews the requested scopes. On consent, Bouncer issues a delegation credential (a signed JWT) and returns it to the agent. The agent then retries the original request with cryptographic proof:
The gateway supports two separate signed-retry mechanisms. Your choice depends on what kind of agent you are.
Path A — RFC 9421 HTTP Message Signatures (used by ChatGPT and KYA-OS agents):
GET /protected HTTP/1.1
Host: api.customer.com
User-Agent: ChatGPT-User/1.0
Signature: sig1=:<base64url-ed25519-signature>:
Signature-Input: sig1=("@method" "@path" "@authority" "host");created=1705123456;expires=1705123756;keyid="<public-key-id>"
Signature-Agent: "https://chatgpt.com"The gateway parses the keyid from Signature-Input and resolves the corresponding public key:
- For ChatGPT, the
keyidrefers to a key in ChatGPT's public directory. The gateway fetches keys from/api/internal/signature-keys(a cached API endpoint backed by a daily cron job that pulls fromhttps://chatgpt.com/.well-known/http-message-signatures-directory). Fallback hardcoded keys are kept in the worker for emergency use only. - For KYA-OS agents, the
keyidhas the formdid:web:knowthat.ai:agents:<slug>#key-1. The gateway resolves the DID document fromhttps://knowthat.ai/agents/<slug>/did.jsonand extracts the public key fromverificationMethod[0].publicKeyJwk.
In both cases, the gateway verifies the Ed25519 signature over the canonicalized request base string (method, path, authority, host), validates the timestamp (±30s clock skew, 5-min max age), and on success upgrades the detection record to verificationMethod: 'signature' with 100% confidence.
Path B — Simple DID+timestamp signatures (used only by KYA-OS agents, as an alternative to RFC 9421):
GET /protected HTTP/1.1
Host: api.customer.com
X-Agent-DID: did:web:knowthat.ai:agents:my-agent
X-Agent-Signature: <base64url-ed25519-signature>
X-Agent-Timestamp: 1705123456This is a lighter-weight flow for agents that don't want to implement full RFC 9421. The signature is over the timestamp (as ASCII decimal seconds), not the request. The gateway:
- Reads
X-Agent-DIDand confirms the host is on the allowlist (knowthat.ai,kya.vouched.id, or*.agents.kya-os.ai) - Fetches the DID document from the allowed host and extracts the public key (for managed
*.agents.kya-os.aiagents, the key comes from the routing KV entry — zero network latency) - Verifies
X-Agent-Signatureover theX-Agent-Timestampvalue - Validates the timestamp (±30s clock skew, 5-min max age)
- On success, sets
verificationMethod: 'did'with 100% confidence
Path C — Bouncer delegation proof (scope-checked at the origin, not the gateway):
GET /protected HTTP/1.1
Host: api.customer.com
KYA-Delegation: eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCJ9...The gateway does not verify the proof JWT itself. It recognizes the header (as part of the mcp_i.retry_instructions contract) and forwards the request to the origin. @kya-os/bouncer-middleware verifies the JWT against the delegation record and checks scopes at the origin. See Scopes and origin middleware.
What the origin receives after a successful verification
On the success path (Path A or Path B with a valid signature), the gateway forwards the request to your origin with these headers added:
| Header (direction) | Value |
|---|---|
KYA-Project-Id (request) | Your project UUID |
KYA-Original-Origin (request) | The original https://{hostname} the agent asked for |
KYA-Request-Id (request & response) | Per-request UUID for tracing |
And the response headers the agent sees include:
| Response header | Example value | Meaning |
|---|---|---|
KYA-Detected | true | Whether an agent was detected at all |
KYA-Confidence | 100 | Confidence score |
KYA-Action | allow | Policy action applied after verification |
KYA-Agent | ChatGPT | Agent name (if detected) |
KYA-Verification | signature | Verification path used (signature, did, or pattern) |
KYA-TLS-Source | request.cf | Where the TLS fingerprint came from |
KYA-Duration-Ms | 12 | Gateway processing time |
If verification fails, the gateway returns 401 again (or 403 if the agent is explicitly deny-listed by verified identity).
Deployment Architecture
MCP-I enforcement only runs on the Cloudflare Gateway path. In-app middlewares (Next.js,
Express, .NET) cannot process the instruct action because they do not have access to the request
body parsers, the WASM detection context, or the policy enforcement layer that the gateway
provides.
For MCP-I enforcement to work, traffic must flow through detect.checkpoint-gateway.ai before reaching your origin. Three deployment topologies are supported:
1. Gateway as CNAME target (recommended)
Agent → DNS (CNAME to detect.checkpoint-gateway.ai) → Checkpoint Gateway → Your originPoint a CNAME from your domain (e.g. api.acme.com) to detect.checkpoint-gateway.ai. The gateway extracts the hostname, looks up the project in its routing table, runs detection, enforces the policy, and proxies surviving requests to your origin. Your origin code runs unchanged.
See Gateway deployment for setup instructions.
2. Origin behind the gateway via custom DNS
If you cannot change the public DNS, route internal traffic through the gateway by pointing your load balancer at detect.checkpoint-gateway.ai and preserving the original hostname in the Host header.
3. Not supported: in-app middleware only
Installing @kya-os/agentshield-nextjs or an equivalent framework middleware directly on your origin gives you detection-based enforcement (pattern matching on User-Agent, basic path rules) but does not give you the instruct action. The middleware falls back to redirect behavior, which is weaker.
If you need MCP-I enforcement but cannot put the gateway in front of your origin, contact support to discuss alternative topologies.
Configuration
Setting the policy via API
Today, the instruct action is set through the project policy API. Use PUT /api/internal/projects/{projectId}/policy:
COOKIE='authjs.session-token=<your-session-token>'
PID='<your-project-id-or-friendly-id>'
curl -X PUT "https://kya.vouched.id/api/internal/projects/$PID/policy" \
-H "Content-Type: application/json" \
-H "Cookie: $COOKIE" \
-d '{
"default_action": "instruct"
}'The response returns the updated policy. The gateway picks up the change after its 5-minute cache TTL expires, or immediately if GATEWAY_ADMIN_KEY is configured (the PUT route fires a best-effort cache invalidation).
Path-scoped enforcement
You can apply instruct to specific paths while leaving the rest of the site on redirect or allow. This is the recommended pattern: protect sensitive APIs with MCP-I while letting detection handle marketing pages.
Path-scoped instruct requires adding the action to the path rules schema, which is tracked as a
follow-up. For now, instruct is a project-level default only.
Required scopes
The required_scopes field in the 401 response tells the agent what it needs in its delegation. Scopes are configured in the Tools section of the dashboard and enforced by @kya-os/bouncer-middleware at your origin — not by the gateway.
Scopes and origin middleware
The gateway handles the challenge and signature verification at the edge. It does not know about project-specific scopes, because scope definitions live in your origin's endpoint configuration. The separation of concerns is:
| Layer | Responsibility |
|---|---|
Gateway (detect.checkpoint-gateway.ai) | Detects agents, returns 401 challenge, verifies RFC 9421 or DID signatures |
| Bouncer middleware (origin) | Verifies JWT delegation proofs (KYA-Delegation), checks scopes against endpoint requirements |
To enforce scopes, install @kya-os/bouncer-middleware on your origin:
npm install @kya-os/bouncer-middlewareimport express from 'express';
import { createBouncerMiddleware } from '@kya-os/bouncer-middleware';
const app = express();
app.post(
'/api/payments/create',
createBouncerMiddleware({
apiKey: process.env.AGENTSHIELD_API_KEY!,
projectId: process.env.CHECKPOINT_PROJECT_ID!,
requiredScopes: ['payment:create'],
reputationThreshold: 60,
}),
(req, res) => {
const { agentDid, scopes, reputation } = req.bouncer;
// ... handle the authorized agent request ...
}
);See Proofs for the full proof verification flow.
Testing the flow
After enabling instruct on a project, verify the challenge is returned for agent traffic:
# Simulate ChatGPT browsing
curl -sI \
-A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot" \
"https://api.acme.com/protected" \
| grep -iE "(HTTP|WWW-Authenticate|KYA-)"Expected output:
HTTP/2 401
WWW-Authenticate: KYA realm="api", authorization_uri="https://kya.vouched.id/api/v1/bouncer/authorize?..."
KYA-Action: instruct
KYA-Auth-Required: true
KYA-Auth-Url: https://kya.vouched.id/api/v1/bouncer/authorize?...
KYA-Project: proj-xxxThen fetch the full JSON body:
curl -s \
-A "Mozilla/5.0 AppleWebKit/537.36 (KHTML, like Gecko); compatible; ChatGPT-User/1.0; +https://openai.com/bot" \
"https://api.acme.com/protected" \
| jq .mcp_iThis should show the full mcp_i object with authorization_url, required_scopes, and retry_instructions.
Limitations and known gaps
- Gateway path required. As noted above, in-app middleware deployments do not support the
instructaction and silently fall back toredirect. - No dashboard UI yet. The
instructaction is functional in the gateway but is not yet exposed as a radio option in the dashboard's Agent Traffic Handling card. Set it via the API until the UI ticket lands. - Scope enforcement is not at the gateway. The gateway verifies identity (signature) but not scopes. Scopes are checked at the origin by
@kya-os/bouncer-middleware, so you need both pieces installed for full MCP-I coverage. - Path-scoped
instructis not yet supported in path rules. Today the action is project-level default only; see configuration. - Non-cooperating agents are blocked, not redirected. If an agent does not implement RFC 9421 or MCP-I proofs, it sees a 401 with no way to proceed. This is the intended behavior — use detection-based
redirectif you want a graceful fallback for unsupported agents. - Agent tool delegation does not bypass this. Unlike detection, which can be evaded by asking an agent to proxy the request through a URL-inspection tool, MCP-I enforcement requires cryptographic proof on the actual request hitting the gateway. A proxied request carries the proxy's identity, not the agent's, and fails the challenge.
Related
- Policies — Detection-based enforcement actions (allow/block/redirect)
- Proofs — JWT proof verification at the origin
- Delegations — Managing agent authorization grants
- Consent — Customizing the authorization flow UI
- Tools — Per-tool scope requirements
- Gateway — Deploying traffic through the Checkpoint Gateway