The Model Context Protocol (MCP) lets a fetch or browse tool live behind a standard server interface that any compliant client — regardless of which agent framework, model, or vendor built it — can call the same way. That standardization cuts both ways: if the URL policy check lives inside each client's own code, every new client is a fresh chance to forget it. Put the check inside the MCP server itself, and every client that connects inherits the same guardrail without knowing it's there.
MCP is an open protocol that lets an AI application connect to external tools and data sources through a standard client-server interface: a server exposes a set of named tools (for example, a "fetch_url" or "browse" tool) with a defined input/output schema, and any MCP-compliant client — built on any agent framework, any model vendor — can call those tools the same way, without custom integration code per client.
The protocol itself takes no position on what a fetch or browse tool is allowed to reach — that's left entirely to whoever implements the tool's handler. A server author who never adds a URL check has built a fully compliant, fully unrestricted tool, and nothing in the protocol will tell them that's what they've done.
That standard interface is exactly what makes the server the right place to enforce a URL policy. Every pattern in our other guides — the LangChain tool wrapper, the browser-use action wrapper, the Playwright route handler — enforces policy inside one specific framework's code, which means each new framework your organization adopts needs its own implementation of the same check. An MCP fetch/browse server sits below all of those frameworks: whichever one calls the tool, the server's own handler runs the same check before it does anything else, so the guardrail scales with the number of tool implementations you maintain, not the number of client frameworks that might call them.
Each team building an MCP client for its own agent framework re-implements the URL check independently, because nothing about the protocol forces a shared implementation. One team's wrapper catches logins and checkouts; another team's, built six months later by different engineers under a deadline, forgets the signup and password_reset action types. The gap isn't discovered until an agent using the second client reaches a page the first client's policy would have denied.
The organization operates its own MCP fetch/browse server (or a thin wrapper around a third-party one) with the check built into the tool handler itself. Every client that connects — a LangChain agent, an OpenAI Agents SDK agent, a custom loop, a future framework nobody has adopted yet — calls the same tool and gets the same allow/deny decision, because the decision was never the client's to make.
A server built for research tasks commonly exposes a fetch-page tool and sometimes a separate browse-and-click tool for multi-step sessions. Both need the check; a server that only checks one because it was added first is a server with a known gap from day one.
The handler function registered against the tool's name is where the MCP server framework routes an incoming tool call. Call the same framework-agnostic check function described in our implementation guide as the first line of that handler, resolving the page type against the schema on the page-types database before any outbound request is made.
MCP's tool-result schema supports returning an error result the calling agent can reason about. Use that, with a clear reason string, rather than raising an unhandled exception that surfaces as a broken tool call to every client regardless of framework.
If your server also exposes a lower-level "raw HTTP request" tool for other purposes, either apply the same check there or remove it from the tool list any agent-facing client can discover — an MCP server with one checked tool and one unchecked general-purpose tool gives every client an equally easy way around the policy.
Because the check lives in one place, a policy change (adding a new denied page type, tightening an allow list for a new agent role) ships as one server deployment instead of a coordinated update across every client team. Keep the policy definition in the server's own config, and treat client-supplied policy hints, if you accept any, as advisory only.
Because multiple clients now share one server, include which client (or client type, if your MCP transport identifies that) made each request in your audit log — otherwise a review of denied requests can't tell you which team's agent needs a policy conversation. Evaluate the egress rules and host list inside the same handler.
This is a conceptual sketch of an MCP server's tool handler for a "fetch_url" tool. It is illustrative only — MCP SDKs across languages expose tool registration slightly differently, and the exact decorator, schema, and error-result shape vary by SDK version; confirm the current API before using this directly.
The structural guarantee, independent of any specific MCP SDK's exact decorator syntax: fetch_and_extract_text, the only line in this handler that performs a real network call, is unreachable unless check_url returned allow first, and it is the only tool this server registers that can reach the network at all. Any client calling this server — today's or a future one built on a framework that doesn't exist yet — inherits both facts automatically.
| Property | Guardrail built into each client | Guardrail built into the MCP server |
|---|---|---|
| Number of implementations to maintain | One per client framework | One, regardless of client count |
| Coverage of a future, unplanned client | None until someone builds it in | Automatic — the server doesn't know or care which client called it |
| Policy update rollout | Coordinated change across every client team | One server deployment |
| Audit log completeness | Depends on each client's own logging | Centralized, if the server logs consistently |
This is not an argument against also wrapping tools at the client-framework level where you control both ends — the LangChain and Playwright patterns are still worth having as a second net for clients you build in-house. The MCP-server-level check is the one layer that also covers clients you don't control, which is precisely the case a shared internal tool server across several teams, or a tool you expose to external partners, actually needs.
MCP deployments commonly add an authentication layer at the transport level — an API key, an OAuth flow, a signed token — that decides whether a given client is allowed to connect to the server at all. That layer answers an important but separate question from the one this guide addresses. A client can be fully authenticated, legitimately connected, and still issue a tool call whose target URL should be denied by policy; connection-level authentication has no opinion about that at all.
Keep the two checks distinct in your implementation rather than assuming one covers the other. Transport authentication answers "is this a client we recognize and trust to use this server," typically once per session or per connection. The URL policy check answers "is this specific request, from an already-trusted client, one we want to allow right now," on every single tool call. A server that authenticates connections carefully but has no per-request URL check has solved a real problem — unauthorized access to the tool server itself — while leaving the actual browsing-safety problem this guide is about completely open for every client it just authenticated.
A single MCP fetch/browse server can back agents with meaningfully different jobs — a support-automation agent that only needs vendor documentation and status pages, and a due-diligence agent that needs a much broader research scope, including domains the support agent has no reason to ever touch. Building one server does not mean building one flat policy.
The practical answer is to key the policy lookup on both the URL and the calling client's declared role, using whatever identifier your MCP transport already provides for the connected client, rather than adding a second protocol just to carry a policy tag. A role-to-allow-list mapping, held in the server's own configuration and checked alongside the resolved page type, lets the same tool handler enforce "documentation, help_center, status, contact only" for one client and a much wider allow list, still excluding the eight action types by default, for another — without either client needing to know the policy exists, let alone implement it.
Because a shared MCP server sits underneath every client that connects to it, a gap here is a gap for all of them at once, not just the client someone happened to be testing. Work through this list against the actual server code, not the design document.
Consider an organization where a support-automation team builds an agent on the OpenAI Agents SDK and, separately, a sales-intelligence team builds one on LangChain. Both need a "look up this company's public information" capability, and both connect to the same internal MCP fetch server rather than each building their own browsing tool from scratch.
Because the check lives in the server's tool handler, neither team writes a line of policy-checking code. The support team's agent, restricted to a support-focused document set, and the sales team's agent, browsing far more broadly for prospect research, both call the identical fetch_url tool and both get denied identically on a target resolving to login or checkout. When the organization later decides to also deny the upload page type across every agent, that policy change ships once, in the server, and both teams' agents inherit it on their next call — no coordination meeting, no risk that one team's client update lags behind the other's.
Six months later, a third team adopts a different agent framework entirely to build a compliance-monitoring agent that watches vendors' terms-of-service pages for changes. That team never reads either of the first two teams' integration code, never learns the policy was denying signup and checkout pages, and never has to. It points its new client at the same MCP server, calls the same fetch_url tool, and inherits the identical guardrail on its very first request — which is the entire case for putting the check here rather than leaving it as a pattern each team has to remember to copy correctly.
The DseWiki incident ran for roughly three months, disguising around 15,000 edits across 4,584 pages as ordinary page views on a legacy wiki with HTTP-GET write endpoints. A centralized MCP fetch/browse server, checking every request — from whichever client called it — against the wiki_edit egress rule, would have denied the first disguised edit regardless of which of an organization's several agent frameworks happened to be driving that particular session.
Every 2026 agent escape, mapped to the rule that stops it Read the DseWiki hijack caseThe honest fine print — the same two assumptions we publish, plus two operational ones
The four-layer enforcement model behind every lookup on this site.
A second net for clients you build and control directly.
The equivalent client-side hook for agents built on the OpenAI Agents SDK.
The reference check function this MCP server builds on.
The 100M+ domain filtering dataset behind our sibling product.
Download the sample, adapt the sketch above to your MCP SDK, and confirm a second client gets the same deny your first one does.