Governing agent tools with the MCP Server Portal (the flagship demo)
Here’s the demo I show people: ask the shop bot “Where is my order ord_…?” — it answers with
real order data. Then I open the MCP Server Portal dashboard (Cloudflare One → AI Controls),
toggle lumina_check_order_status off, and ask again. The bot replies: “the order-status
lookup tool is currently unavailable.” Re-enable, ask again — capability restored. No code was
touched. No redeploy. One control plane, owned by the platform.
The story
The retail agent reaches its tools over the public MCP protocol, through Cloudflare’s MCP Server Portal — the same path any third-party agent (Claude, ChatGPT, Cursor) would take. The portal sits in the middle and adds what a raw MCP server can’t: identity, policy, namespacing, logging and analytics.
Browser chat → Retail Worker (MCP client) → MCP Server Portal → lumina-internal-mcp (tool server)
CF-Access-* headers Access authN/Z bearer credential
How it works
Identity: a service token, not OAuth. Machine-to-machine callers use Cloudflare Access service tokens — two headers on every portal request:
private baseHeaders(): Record<string, string> {
return {
'content-type': 'application/json',
accept: 'application/json, text/event-stream',
'cf-access-client-id': this.clientId,
'cf-access-client-secret': this.clientSecret,
};
}
The portal authorizes twice — once for the portal application, once per upstream MCP server. A missing Service Auth policy on either silently hides that server’s tools (found live, the hard way, when the Cloudflare docs server showed an empty roster until its policy was set).
Governance lives only in the portal. The tool Worker registers its three tools statically — there is deliberately no application-side allowlist:
for (const tool of TOOL_DEFS) {
server.registerTool(tool.name, { description, inputSchema }, (args) => tool.handler(env, args));
}
When a tool is toggled off in the portal dashboard, it stops being advertised in tools/list.
The agent builds its tool roster from tools/list — so it never legitimately sees a disabled
tool, and the model never gets a schema it shouldn’t call.
Namespacing makes multi-server aggregation safe. Portal tools are named
{server}_{tool} (lumina_search_internal_kb), so the agent can group the roster by server and
steer questions to the right one — Lumina’s three internal tools plus, in this deployment,
Cloudflare’s documentation server and others. Registering a new server in the portal gives the
bot new capabilities with zero code changes.
Everything is logged. Every portal call lands in Access request logs; the portal’s tool-call analytics show which tools the agent actually used, when, and how often. That’s the audit trail — for free, because the platform is the chokepoint.
Two sharp edges worth knowing before you demo this:
- The portal serves a synced view. Tool lists are a cached copy (~2h auto-sync). After toggling, click Sync capabilities in the dashboard before re-asking, or the old roster still shows.
- The agent is a client, not an admin.
portal_*administration tools are filtered out of the roster in the Worker — the bot consumes servers, it doesn’t manage the portal.
What the demo shows
- “What’s your return policy for sale items?” →
lumina_search_internal_kbfires, answer citesreturns-and-exchanges.md. - “Where is my order
ord_…?” →lumina_check_order_statusanswers from D1. - Toggle the tool off in the portal → ask again → transparent decline.
- Re-enable → capability returns.
- Portal analytics show the tool-call counts; Access logs show every request.
Evidence: what to capture
- MCP Server Portal dashboard → the registered servers list (lumina-internal-mcp + Cloudflare docs server) →
07-portal-servers.png - Tool toggles panel showing
lumina_*tools enabled →07-tool-toggles-on.png - The toggle-off live demo: bot decline screenshot side-by-side with the toggled-off dashboard →
07-tool-off-decline.png - Toggle back on + re-ask → capability restored →
07-tool-restored.png - Access request logs filtered to the portal →
07-access-logs.png - Tool-call analytics showing per-tool counts →
07-tool-analytics.png - (Optional)
curlthe portaltools/listwith and without the service-token headers → 401 vs tool list →07-fail-closed.png
Key takeaways
- Putting an agent’s tools behind a platform gateway turns governance into toggles, not code.
- Discovery-level enforcement (
tools/listreflects policy) plus request logs gives you a real audit trail. - Service tokens are the right identity for machine-to-machine MCP — no interactive OAuth dance.
- Test the failure direction too: with the tool off, the bot must say it can’t — not invent.