All posts

How to security-review MCP tools before production

Reviewing MCP tool surfaces before agents can call them. Wildcard exposure, scope creep, missing approval policies, schema strictness — what good and bad look like.

The reason teams adopt the Model Context Protocol is the same reason MCP needs a release review: connecting a new tool surface to an agent takes about five minutes. The five minutes you save on integration are the five minutes you’d otherwise have spent on review.

Most teams skip that review entirely. This post walks through how to do it deliberately, with the MCP filesystem server as the worked example. The same five questions apply to any MCP server you’re about to connect.

What MCP gives you and what it doesn’t

MCP standardizes how tool inventories are exposed, how tool calls flow between agent and server, and how errors come back. That standardization is genuinely useful — it’s why the protocol has adoption.

MCP does not validate that the tools are safe for your agent to call. It doesn’t limit which operations are appropriate at runtime. It doesn’t tell you whether the schemas are too loose, whether the inventory is too broad for the agent’s declared purpose, or whether destructive tools need approval gates.

That gap is what a release review fills. The protocol is necessary infrastructure; the review is the static check you run before any agent gets access to a server’s tools.

The five questions to ask about any MCP server

For any MCP server you’re about to connect to a tool-using AI agent:

  1. Inventory. What tools does this server expose, by name? Do you need all of them?
  2. Schema. What does each tool’s input schema actually accept? Strict, bounded, named fields — or freeform JSON the model can pack with anything?
  3. Auth scopes. What scopes does each tool need? Are the scopes declared in your manifest narrower than the server’s actual permissions?
  4. Approval and confirmation. Which tools have side effects? Which require human approval before firing? Which require customer confirmation?
  5. Idempotency. Can a write be retried safely? If a transient network blip causes a retry, does the same operation fire twice?

These five map onto the seven dimensions of tool-use readiness. For MCP specifically, the first four show up in almost every release review; idempotency is the one teams most often miss until production.

Worked example: reviewing the MCP filesystem server

Imagine you’re connecting @modelcontextprotocol/server-filesystem to a customer-support agent. The agent needs to read support tickets uploaded under /tickets/ and summarize them for a human reviewer. It does not need to write, edit, move, or delete files.

Run through the five questions on this concrete surface.

1. Inventory

The MCP filesystem server (@modelcontextprotocol/server-filesystem, late-2026 release) exposes 13 tools:

  • read_text_file, read_media_file, read_multiple_files
  • write_file, edit_file
  • create_directory, move_file
  • list_directory, list_directory_with_sizes, directory_tree
  • search_files, get_file_info
  • list_allowed_directories

(Earlier versions split read_text_file and read_media_file out of a single read_file tool. If your snapshot was taken from an older server, the inventory will look different — pin the version in your MCP export so future reviewers know which surface they are reading.)

The release-readiness question isn’t “is this server safe” — it’s “is this surface what the agent needs?” For a read-only ticket lookup, the agent needs read_text_file, list_directory, and search_files. Everything else is over-grant.

Inventory finding: ten tools the agent doesn’t need are part of its declared surface.

2. Schema

Each filesystem tool takes a path parameter. The schema accepts any string. The MCP server enforces a configured set of allowed paths at startup — but that boundary lives in the server config, not in the manifest the release reviewer reads.

For the review, what you want is:

  • path field present and required on every tool that takes one.
  • additionalProperties: false so the model can’t pack unexpected fields into the call.
  • The allowed-paths configuration is referenced in the manifest so a reviewer can see what slice of the filesystem the agent can touch.

Schema finding: schemas are strict by default in the MCP server, but the path scope lives outside the manifest. Add it explicitly.

3. Auth scopes

For MCP filesystem, “auth scope” doesn’t mean an OAuth scope — it means which paths the server is configured to expose, and which of those paths the agent’s manifest declares it needs.

Two scope failures show up in practice:

  • The server is configured with broader paths than the agent’s declared purpose covers. The agent says “tickets only,” the server is configured for the entire repo.
  • The manifest doesn’t pin which subdirectory the agent reads from. A future change to the server config silently expands the agent’s access.

Scope finding: pin paths in both the server config and the manifest; the release reviewer should be able to read both and confirm they match.

4. Approval and confirmation

For the read-only customer-support use case, the agent doesn’t need any of the write/edit/move tools. If you keep them in the surface — even with the intent that “the model just won’t use them” — each one needs an explicit approval policy.

The cleaner path is to configure the MCP server itself to expose only the tools the agent needs (most MCP servers, including filesystem, support a tool allowlist or read-only flag at startup). The exported snapshot then contains only the safe tools. Declare the broader intent in the agent’s prohibited_actions so a future reviewer can confirm the boundary without re-deriving it from the server config:

agent:
  name: customer-support-agent
  prohibited_actions:
    - write or modify any files on disk
    - create, move, or list directories outside /tickets/

Approval finding: write tools that survive into the MCP snapshot without an approval policy are critical findings under SHIP-POLICY-APPROVAL-MISSING.

5. Idempotency

write_file overwrites by default. A retry overwrites a file you may not have wanted to overwrite again. If you keep the tool on the surface, either mark it as not safe to retry or require an idempotency key as part of the schema.

For the read-only support agent, this is moot — we’re removing the write tools entirely. But it’s the kind of finding that survives unnoticed when a team keeps “just in case” tools on the surface.

What good looks like

The shipgate.yaml manifest after a clean review:

version: "0.1"
project:
  name: customer-support-agent
agent:
  name: customer-support-agent
  declared_purpose:
    - "Read customer support tickets from /tickets/ only"
    - "Summarize ticket history for a given customer"
  prohibited_actions:
    - write or modify any files on disk
    - create, move, or list directories outside /tickets/
environment:
  target: production_like
tool_sources:
  - id: filesystem
    type: mcp
    path: mcp-exports/filesystem-server.json
risk_overrides:
  tools:
    filesystem.read_text_file:
      tags: [read_only, pii_access]
      owner: customer-support-platform
      reason: reads support tickets which may contain customer PII; path scope /tickets/ enforced by MCP server config
    filesystem.list_directory:
      tags: [read_only]
      owner: customer-support-platform
      reason: lists /tickets/ contents only; path scope enforced by MCP server config
    filesystem.search_files:
      tags: [read_only]
      owner: customer-support-platform
      reason: searches within /tickets/ only; path scope enforced by MCP server config
ci:
  mode: advisory
  pr_comment: true
output:
  directory: agents-shipgate-reports
  formats:
    - markdown
    - json
    - sarif

This manifest answers all five questions: the inventory is narrowed (the MCP export only contains read tools because the server was started in read-only mode), the path scope is documented in declared_purpose and repeated in each tool’s reason, the PII tag on read_text_file makes the access classification explicit, prohibitions are written down in prohibited_actions, and there are no writes left to need approval or idempotency policies.

What bad looks like

The same agent without the manifest narrowing — the surface as the MCP server exports it, with no policies declared:

Status: release_blockers_detected
Critical: 4 · High: 8 · Medium: 2

[critical] filesystem.write_file lacks a declared approval policy
[critical] filesystem.edit_file lacks a declared approval policy
[critical] filesystem.create_directory lacks a declared approval policy
[critical] filesystem.move_file lacks a declared approval policy
[high]     filesystem.write_file may be retried without idempotency evidence
[high]     filesystem.edit_file may be retried without idempotency evidence
[high]     filesystem.write_file lacks declared auth scopes
[high]     filesystem.edit_file lacks declared auth scopes
[high]     filesystem.create_directory lacks declared auth scopes
[high]     filesystem.move_file lacks declared auth scopes
[high]     filesystem.read_text_file is not tagged with pii_access risk
[high]     Tool surface inventory broader than declared_purpose
[medium]   Prompt lacks confirmation language for write/edit actions
[medium]   No CI release-gate workflow detected

Fourteen findings, every one of them legitimate. None of them would be caught by an eval suite — the eval set tests behavior on read-only ticket lookups, which is what the prompt asks for, and the model dutifully reads tickets. The unsafe surface lives in the artifact, not in the runtime behavior the evals exercise.

When to run this review

Trigger the review whenever any of the following appear in a PR:

  • A new MCP server is added to tool_sources.
  • An MCP server’s version is bumped (could add or change tools).
  • The server’s allowed paths, scopes, or configuration change.
  • The agent’s declared_purpose is expanded.
  • The prompt changes in ways that imply broader tool use.

This is the same trigger surface as the broader agent deployment checklist — MCP changes are the most common source of net-new findings on most agent codebases in 2026.

Automating the review

The five questions above are all deterministic. You can run them against your repo:

pipx install agents-shipgate
agents-shipgate init --workspace . --write
agents-shipgate scan -c shipgate.yaml

The scanner reads your MCP export (the JSON snapshot of the server’s declared tools, schemas, and descriptions) plus your manifest, and emits the finding list above. Static analysis only — it does not connect to MCP servers, does not invoke the agent, does not call any model.

For CI, add the GitHub Action in advisory mode first so the team sees MCP-related findings on every PR without blocking merges:

- uses: ThreeMoonsLab/agents-shipgate@v0.8.0
  with:
    config: shipgate.yaml
    ci_mode: advisory
    pr_comment: "true"

MCP gateways live downstream of this. Once the surface is reviewed and bounded, runtime enforcement can decide whether any specific call should proceed. The review and the gateway are complementary — see Agents Shipgate vs MCP gateways for the fuller comparison.

The shape of a good MCP review

What you want to leave a review with: a manifest you can hand to a security reviewer, the reviewer can read in three minutes, and they can point at every declared tool, scope, policy, and owner. No wildcards, no “trust the server,” no “the prompt will steer it.” The surface is what the surface is, and it’s the surface the agent gets.

That’s release-readiness for MCP tool surfaces. Five questions, one manifest, one CI check.

Install agents-shipgate GitHub