Checkpoint Docs
Govern

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 checksUser-Agent patterns, TLS fingerprints, IP rangesCryptographic signature (RFC 9421) or MCP-I proof
Bypass-resistant?No — agents can evade with tool delegation or spoofYes — cannot forge a valid Ed25519 signature
Agent cooperationNot requiredRequired (agent must implement the protocol)
Catches unknown UAsYes (heuristic)No (unsigned agents fail the challenge)
Catches desktop appsPartial (TLS fingerprinting on gateway path only)Yes (if the app signs its requests)
Catches tool proxiesNo (third-party tools have their own fingerprints)Yes (the proxied request won't carry a valid proof)
Use casesMarketing pages, soft consent flows, content surfacesPayment 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 message field 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_i object programmatically, call authorization_url to 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 keyid refers 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 from https://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 keyid has the form did:web:knowthat.ai:agents:<slug>#key-1. The gateway resolves the DID document from https://knowthat.ai/agents/<slug>/did.json and extracts the public key from verificationMethod[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: 1705123456

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

  1. Reads X-Agent-DID and confirms the host is on the allowlist (knowthat.ai, kya.vouched.id, or *.agents.kya-os.ai)
  2. Fetches the DID document from the allowed host and extracts the public key (for managed *.agents.kya-os.ai agents, the key comes from the routing KV entry — zero network latency)
  3. Verifies X-Agent-Signature over the X-Agent-Timestamp value
  4. Validates the timestamp (±30s clock skew, 5-min max age)
  5. 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 headerExample valueMeaning
KYA-DetectedtrueWhether an agent was detected at all
KYA-Confidence100Confidence score
KYA-ActionallowPolicy action applied after verification
KYA-AgentChatGPTAgent name (if detected)
KYA-VerificationsignatureVerification path used (signature, did, or pattern)
KYA-TLS-Sourcerequest.cfWhere the TLS fingerprint came from
KYA-Duration-Ms12Gateway 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:

Agent → DNS (CNAME to detect.checkpoint-gateway.ai) → Checkpoint Gateway → Your origin

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

LayerResponsibility
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-middleware
import 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-xxx

Then 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_i

This 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 instruct action and silently fall back to redirect.
  • No dashboard UI yet. The instruct action 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 instruct is 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 redirect if 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.
  • 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