Skip to content
Status

Governance / Shadow MCP allow and block lists

Shadow MCP allow and block lists

Choose a posture for shadow MCP enforcement, build the policy's server list, layer per-server decisions on top, and roll out blocking safely.

A blocking shadow MCP policy decides what happens when an agent calls an MCP server the platform does not host. The policy has one of two postures:

  • Block all: block by default, allow named servers.
  • Allow all: allow by default, block named servers.

Both postures work from the same list of server URLs. The posture decides whether that list means “allowed” or “blocked”.

This guide covers:

  • A populated Shadow MCP inventory. Servers only become policy targets after they appear in the inventory, which fills from instrumented agent sessions.
  • The org:admin scope, required to create and edit risk policies.

The posture is stored on the policy as its disposition (shadow_mcp_disposition in the API).

The posture cannot be changed later

The URL list means the opposite thing under each posture, so updates that change the disposition are rejected. Switching means deleting the policy and creating a replacement, which leaves the project unprotected until the new policy exists. Decide the posture before the list grows long.

Block all denies every non-hosted MCP server; the URL list is an allow list. This is the default. It fits a sanctioned catalog: anything not approved fails closed, and members are pushed toward hosted servers or an access request. The cost is the review queue — every new server a member tries produces a blocked call.

Allow all permits every non-hosted MCP server; the URL list becomes a block list. It fits blocking a known set of servers, such as a vendor that failed review or a server that leaked credentials, without gating everything else. Traffic stays visible: the inventory and findings keep flowing.

Allow all fails open. A server that has never been seen before is permitted, and it only gets blocked after someone notices it in the inventory and adds it to the list. Choose it to manage a known-bad list, not an unknown-server problem.

  • Open Secure > Risk Policies in the dashboard and select New Policy, choosing the built-in detector type.

    The Risk Policies page with the New Policy button highlighted in the top right

  • On the Detect step, enable the Shadow MCP category.

    The Detect step of the policy editor with the Shadow MCP category highlighted and its toggle enabled

  • On the Action step, select Deny the request. Two shadow MCP controls appear: Default behavior, which sets the posture (Block all servers or Allow all servers), and the list of servers the policy allows or blocks.

    The Action step with Deny the request selected, the Default behavior posture picker highlighted, and three servers allowed by the policy

  • Select the servers to allow (under block all) or to block (under allow all), then review and create the policy.

When editing an existing policy, the Default behavior choice is read-only.

Only URLs already in the project’s shadow MCP inventory can be added to the list; unobserved URLs are rejected. This blocks mistyped hostnames and keeps every entry traceable to real traffic. To sanction a server that has not been used yet, let one call through under a flagging policy first so the server registers in the inventory.

The server selector in the policy editor lists the observed inventory, with each server’s status, last call, and usage:

The Select allowed servers dialog listing observed shadow MCP servers with status, last called, and usage columns, and three servers checked

URLs are canonicalized before they are stored, so paths and query strings on the same server collapse to one entry.

The policy editor manages the list in bulk. For a single server, record a decision instead: open the server’s access review and select Decide Access. Both write to the same grant set, so a server approved from the inventory also appears in the policy editor.

The Decide access sheet with approve and deny options, an audience selector, and a rationale field

A decision carries its own audience — everyone, or selected people and roles — so splitting a team does not take a second policy. Denying blocks the server for everyone. Decisions apply across every blocking shadow MCP policy, including policies created later.

Blocking on day one turns every unsanctioned server into a blocked call at once. A safer sequence:

  • Create a shadow MCP policy with the flag action. Nothing is interrupted, and the inventory fills with real usage.

  • Let it run over a normal working period, then read the inventory. Sort by usage to separate the servers a team depends on from one-off experiments.

    The Shadow MCP inventory listing discovered servers with their URLs, status, review state, last called, last seen, and call and user counts

  • Decide the posture. A short known-bad list points to allow all; a long tail of unvetted servers points to block all.

  • Create the blocking policy and seed its URL list from the inventory. To stage the rollout, scope the audience to one team first.

  • Watch the pending requests queue during the first week. Requests signal what the initial list missed.

A blocked call returns the block reason plus a request access link that expires after seven days. Opening the link signs the member in and files a request against the policy that blocked the call.

Requests appear on the Shadow MCP page as Pending and attach the requester and their justification to the server’s access review. Deciding resolves them: approving grants the chosen audience, and denying grants nothing.

The request path only exists under block all. Under allow all, calls to a listed server are denied with no route to request an exception, so the block list should hold servers that are firmly off-limits.

Policies are managed through the risk policy endpoints. Create a blocking policy with an explicit posture:

Terminal window
curl -X POST "https://app.getgram.ai/rpc/risk.createPolicy" \
-H "Content-Type: application/json" \
-H "Gram-Key: <your-api-key>" \
-H "Gram-Project: <your-project-slug>" \
-d '{
"sources": ["shadow_mcp"],
"action": "block",
"enabled": true,
"shadow_mcp_disposition": "block_all",
"shadow_mcp_allowed_urls": [
"https://mcp.example-vendor.com/mcp"
]
}'

The shadow_mcp_allowed_urls field is the complete list, not a delta: every update replaces the stored set. Omit the field to keep the current list, and send an empty array to clear it.

Both shadow_mcp_disposition and shadow_mcp_allowed_urls require a policy with the shadow_mcp source and the block action; sending either on any other policy is rejected. Omitting shadow_mcp_disposition on an update preserves the stored value, and only a changed value is an error.

Policies with only the shadow MCP source and the block action are named automatically, so a created policy appears as “Shadow MCP Server Policy”.