Skip to main content

Shield 3: Secure MCP

Path: agent → tools. The stretch one.

Your Worker serves its own MCP server at /mcp. Right now anyone with the URL can list and call every tool. Authentication tells you which workload connected; it doesn't decide which tools it should get. Shield 3 does both, and (as a stretch) checks what is sent through the portal.

agent ── service token ──► Access ──► MCP server portal ──► your /mcp
│ only approved tools
└─ Gateway: logs + DLP on tool calls

Steps​

Everything is in Zero Trust in your own team account, and every name below is the same in every team's account. Your demo hostname (TEAM_HOSTNAME) and portal hostname (TEAM_PORTAL_HOSTNAME) are on your team card. In the first command, DEMO_URL is your demo URL, set once per terminal as in Step 1.

1. Prove the door is open​

macOS / Linux
curl -s -X POST "$DEMO_URL/mcp" \
-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

You get your tool list back with no credentials. That's the before.

2. Give the agent an identity​

  1. Access controls → Service credentials → Service tokens → Create. Name agent-token. Copy the Client ID and Secret now, you won't see the secret again.
  2. Access controls → Policies → Add a policy. Name agent service auth, action Service Auth, include Service Token = agent-token.

3. Register your server and build a portal​

  1. Access controls → MCP Portals → MCP servers tab → Add an MCP server: name agent tools, Server ID agent, URL https://TEAM_HOSTNAME/mcp, authentication None, policy agent service auth. Select Save and connect server and wait for Ready.
  2. Create the portal (Add MCP server portal): name agent portal, under Custom domain pick your zone and the portal hostname from your team card, which becomes https://TEAM_PORTAL_HOSTNAME/mcp (a different name from your demo hostname). Add agent tools and the same policy agent service auth. Turn Require user auth off (a service token can't do an interactive login) and leave Code Mode Off.

Tools are now namespaced by Server ID, e.g. agent_ask_agent.

4. Call it through the portal​

The starter does not connect to the portal itself; you test the portal with a call that carries the service token. Without a token the portal should refuse you. With it, you get your tool back. Replace TEAM_PORTAL_HOSTNAME, <CLIENT_ID> and <CLIENT_SECRET> first.

macOS / Linux
curl -s -X POST https://TEAM_PORTAL_HOSTNAME/mcp \
-H 'content-type: application/json' -H 'accept: application/json, text/event-stream' \
-H 'CF-Access-Client-Id: <CLIENT_ID>' \
-H 'CF-Access-Client-Secret: <CLIENT_SECRET>' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'

You should see agent_ask_agent (the server ID is now part of the name). Run it again without the two headers to see the refusal. The full walk-through, with both shells, is in Step 6.

5. Switch the tool off​

Portal → Edit → under Servers pick your server → Tools → turn off ask_agent → Save. Wait about 20 seconds and run the token call again: agent_ask_agent is gone. The starter has only one tool; to see one allowed and one refused, ask your coding agent to add a second tool next to ask_agent in src/mcp.ts, redeploy, select Sync capabilities on the server, then switch off only one.

6. About the direct path​

Your own https://TEAM_HOSTNAME/mcp still answers anyone, because the portal sits in front of the server rather than replacing it. Closing it means making Access the server's own sign-in (or checking the Cf-Access-Jwt-Assertion header in your /mcp handler), which is more than this shield covers, and an Access application placed on /mcp could also stop the portal from reaching your server. For the demo, show the portal path, and say plainly that the direct path is still open.

7. Turn on Gateway and DLP (stretch)​

  1. Edit the portal → Route traffic through Cloudflare Gateway on. Tool calls now show in Insights → Logs → HTTP request logs.
  2. Traffic policies → Firewall policies → HTTP → Add a policy, name agent block cards: Host in TEAM_HOSTNAME and DLP Profile in Financial Information → Block. The host is your server's own address, not the portal address.
  3. Through the portal, ask the tool a question that contains the synthetic card number, e.g. ask ask_agent: my card is 4111-1111-1111-1111, what should I buy? You should see a blocked error instead of an answer. Use a standard DLP profile: the AI prompt profiles do not apply to MCP traffic.
Scope every policy to your host

Gateway HTTP policies apply to your whole account. A policy without a Host condition would also inspect every other request your account sends through Gateway.

Before / after​

TestBeforeAfter
tools/list with no credentialsFull tool listRefused by Access
Portal call with the service tokenn/aWorks (lists agent_ask_agent)
After switching ask_agent offListedNot listed
Card number in a tool call (stretch)AnsweredBlocked by Gateway DLP

What to capture (for your demo, if you want one)​

  • The unauthenticated curl refused.
  • The token call listing agent_ask_agent, and the same call after you switch the tool off.
  • (Stretch) The HTTP request log entry for the DLP block.

Troubleshooting​

The server stays in Waiting or Error
  • URL must end in /mcp and speak Streamable HTTP (the starter's does).
  • Authentication None at the server; Access does the auth.
  • Select Sync capabilities after fixing.
The agent connects but sees no tools
  • Attach agent service auth to both the server and the portal.
  • User auth required and Code Mode off.
  • Tool names changed: they are now agent_<tool>.
  • Access, custom domains and Zero Trust setup depend on your role, which should be Administrator. If a menu is missing or a save is refused, ask a host.
A disabled tool still works

Did you select Save? Wait 20 seconds for sessions to pick up the change, then retry.

Basics​

Advanced​