AI Agent Allowlist
Home Page-Types Database Agent Guardrails 2026 Incidents API Docs Pricing
Resources
Use Cases Industries & Buyers Learn: Core Concepts Implementation Guides Comparisons Schema & Data Reference FAQ Glossary
Why It Matters
2026 Agent Incidents Category Targeting Database Refreshes Contact Customer Login
Download Free Sample
one guardrail, every MCP client that connects

Put the Policy Check Inside the MCP Server

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.

1Server-side check, any number of clients
28Page types resolved per lookup
$99Entry-tier API, per month
40M+Domains, verified not guessed
Why centralize here

Every client that speaks MCP calls the same tool the same way

Model Context Protocol, briefly

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.

Where the risk actually sits

Per-client guardrails vs. one guardrail behind the tool

Policy duplicated in every client

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.

One policy, enforced where the tool actually runs

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.

Implementation walkthrough

Six steps to build a policy-checked MCP tool server

01

Identify every tool your MCP server exposes that touches the network

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.

02

Put the check inside the tool's handler function, before the real fetch

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.

03

Return a structured tool error on deny, not a protocol-level failure

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.

04

Do not expose an unchecked variant of the same tool

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.

05

Version the policy alongside the server, not per client

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.

06

Log every decision with the calling client's identity, then layer in egress rules and the host list

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.

Illustrative code

A policy-checked MCP tool handler, sketched

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.

mcp_fetch_server.py — illustrative, SDK-agnosticPython 3
# Illustrative sketch of an MCP tool handler — verify registration/schema
# APIs against your specific MCP server SDK and version.
from policy_check import check_url  # the framework-agnostic function from our implementation guide

@mcp_server.tool("fetch_url", description="Fetch the text content of a URL, subject to organization policy.")
async def fetch_url_tool(url: str, client_id: str) -> dict:
  result = check_url(url)
  log_decision(client_id=client_id, url=url, decision=result)  # step 6 — identity-tagged audit log

  if result["decision"] != "allow":
    # Structured tool error every MCP client can surface consistently — step 3
    return {
      "is_error": True,
      "content": f"DENIED: {url} resolves to page_type="
          f"'{result['page_type']}' ({result['reason']}).",
    }

  # Allowed — every client, regardless of framework, only reaches this line on allow
  page_text = await fetch_and_extract_text(url)
  return {"is_error": False, "content": page_text}

# No second, unchecked "raw_http" tool registered on this server — step 4

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.

Where enforcement actually lives

Client-side guardrail vs. MCP-server-side guardrail

PropertyGuardrail built into each clientGuardrail built into the MCP server
Number of implementations to maintainOne per client frameworkOne, regardless of client count
Coverage of a future, unplanned clientNone until someone builds it inAutomatic — the server doesn't know or care which client called it
Policy update rolloutCoordinated change across every client teamOne server deployment
Audit log completenessDepends on each client's own loggingCentralized, 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.

A distinction worth keeping explicit

Who may connect to the server is a different question from what a connected client may fetch

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.

Multiple tools, different scopes

Not every client needs the same allow list from the same server

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.

Before you flip the switch

Pre-launch checklist

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.

A worked example

Two teams, one server, one policy

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.

Every 2026 agent escape, mapped to the rule that stops it

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 case

The honest fine print — the same two assumptions we publish, plus two operational ones

  1. The policy engine must see every request — an agent with raw socket access or a second network path bypasses everything; enforcement belongs at the egress proxy/network layer, not only in an SDK hook.
  2. Default-deny must be on. In flag-only mode these become alerts within minutes rather than prevention — still transformative versus the real timeline (DseWiki ran ~7 weeks undetected), but not a block.
  3. For full URL+method matching on HTTPS you need to be the proxy or in-process hook — SNI alone shows only the host, which still catches the entire host-list layer.
  4. Policy can’t read intent inside a legitimately allowed action: an agent whose job is publishing packages keeps registry access. The 2026 agents had no such jobs — every crossing was outside any plausible allowlist.
Related guides

Framework-level patterns for clients you build in-house

FAQ

MCP access-control questions, answered

Do I need to build my own MCP server, or can I wrap a third-party one?
Either works, as long as the check runs inside whichever process actually performs the fetch. If you're using a third-party MCP fetch/browse server you don't control the source of, put a thin proxying MCP server in front of it that performs the check and only forwards allowed requests, rather than trusting the third-party server's own behavior.
Can different clients get different policies from the same server?
Yes, if the server can identify the calling client (through an API key, a client-supplied identifier, or the transport's own session metadata) and look up a role-specific policy before deciding. Keep the mapping from client identity to policy in the server's own configuration, not something the client can influence about itself.
Does this replace the guardrails inside LangChain or the OpenAI Agents SDK?
No, and it doesn't need to. Framework-level guardrails and an MCP-server-level check are complementary: the framework-level hook catches a call before it even reaches the MCP client, and the server-level check is the backstop that covers every client, including ones you don't control end to end.
What happens if the lookup service is briefly unavailable?
The tool handler should return a deny (or an explicit "policy service unavailable" tool error) rather than fall through to fetching the URL anyway. Every client calling the server inherits this fail-closed behavior automatically, which is a meaningful part of the value of centralizing the check in the first place.
Do I need the paid API for this, or can I test with the free sample?
The free sample CSV (100 domains) is enough to build and test the tool handler during development. Move to the live API (from $99/month) or an on-premise license (see pricing) for coverage beyond the sample.
Should the egress rules and host list also live in the MCP server, or in each client?
In the server, for the same reason the page-type check does: it is the one place every client's request actually passes through. Evaluate the egress rules and host list inside the same tool handler shown above.

Centralize the check behind your MCP tool server today

Download the sample, adapt the sketch above to your MCP SDK, and confirm a second client gets the same deny your first one does.

Download the Sample