# Using third-party MCP servers safely with Claude Code — trust decisions, allowlists and managed-mcp.json

> Before you connect a third-party MCP server to Claude Code: local servers run outside the sandbox, and Anthropic does not audit MCP servers. Covers .mcp.json approval, writing allowlists and denylists correctly, managed-mcp.json, and a vetting checklist, per the official docs.

- Published: 2026-10-12
- Author: 友田 陽大
- Tags: Claude Code, AI駆動開発, セキュリティ, AI
- URL: https://tomodahinata.com/en/blog/claude-code-mcp-server-security-third-party-trust-guide
- Category: AI-driven development & productivity
- Pillar guide: https://tomodahinata.com/en/blog/spec-driven-development-claude-code-ai-agent-production-workflow

## Key points

- An MCP server is an outside program or service that hands Claude new tools. A server started locally runs outside the sandbox with the user's full access, and a server that pulls in outside content is an entry point for prompt injection
- The docs state that Anthropic reviews connectors against listing criteria before adding them to its directory, but does not security-audit MCP servers. The decision to adopt one belongs to the organization using it
- A project's .mcp.json asks for approval in interactive sessions, but claude -p, the Agent SDK and cloud sessions load it without asking
- allowedMcpServers and deniedMcpServers can be set in any settings file, so they only become the organization's allowlist once allowManagedMcpServersOnly is set in managed settings. serverName is a label users choose and is no control; write entries with serverUrl and serverCommand
- When vetting, check the source and maintainer, the scope of tokens it needs, where it connects, what data leaves, and whether the version is pinned, and start from the narrowest permissions

---

The short answer first. **Connecting an MCP server gives Claude Code new hands. Claude's calls to those tools still pass through your permission rules (allow, ask, deny), but a locally started server process runs outside the sandbox with your full access, and what it does inside that process is not something those rules see.** So the question for an organization is not "which useful servers can we find" but **which servers, with how much access, approved by whom**.

The official Security page states that Anthropic reviews connectors against its listing criteria before adding them to the directory, but **does not security-audit or manage any MCP server**. Deciding whether to adopt one is the user's job.

This article goes deeper into the MCP part of [security governance for rolling Claude Code out to an organization (permissions, managed settings, sandboxing)](/blog/claude-code-enterprise-security-governance-managed-settings-guide). Setting keys and behaviors were checked against the official documentation as retrieved on 2026-10-12. Some details change between versions, so confirm them against the version you deploy.

---

## 1. What an MCP server can do

**A local MCP server is a separate process Claude Code starts, and it runs with the user's full access.** The sandboxing docs list local MCP servers alongside command hooks and LSP servers as processes that "run with your full access." The sandbox wraps the shell commands Claude runs, not MCP server processes.

An organization should plan for four risks.

| Risk | What it means |
|---|---|
| Local execution | A stdio server is a program on the machine, so every file, environment variable and host its code can reach is in range |
| Where data goes | A remote server receives the arguments of each tool call: code, issue text, customer names |
| Prompt injection | Responses from a server that fetches outside content land in Claude's context. The docs warn that such servers can expose you to prompt injection risk |
| Additions you don't see | Scopes other than `.mcp.json`, claude.ai connectors and plugins can all add servers. In the docs' words, reviewing `.mcp.json` doesn't show every server a session can load |

## 2. Scopes: local, project, user and precedence

**MCP server configuration can live in three scopes. When the same server is defined in more than one, the entry from the highest-precedence source is used whole.**

| Scope | Loads in | Shared with team | Stored in |
|---|---|---|---|
| local (default) | Current project only | No | `~/.claude.json` |
| project | Current project only | Yes, via version control | `.mcp.json` in the project root |
| user | All your projects | No | `~/.claude.json` |

Precedence runs local → project → user → plugin-provided servers → claude.ai connectors, and fields are never merged across scopes. Duplicates across the three scopes are matched by name; plugins and connectors are matched by endpoint. Servers the organization provides with `managedMcpServers` in managed settings rank above all of these.

One confusing point: MCP "local scope" is stored in `~/.claude.json` in the home directory, and has nothing to do with the general `.claude/settings.local.json`.

## 3. Project .mcp.json approval, and the -p and SDK exception

**In interactive sessions, Claude Code asks for approval before using servers from a project's `.mcp.json`. `claude -p`, the Agent SDK and cloud sessions can't show that prompt, so they load those servers without asking.**

On the interactive side, there is a guard against a cloned repository approving itself: `enableAllProjectMcpServers` or `enabledMcpjsonServers` committed to the repository's `.claude/settings.json` is ignored in an untrusted folder, and the server stays pending approval.

The problem is non-interactive runs. The table in the permissions docs says that under `claude -p` or the SDK, in a folder never trusted, `.mcp.json` servers are "connected without asking, approved or not." If CI runs Claude against someone else's repository or an outside pull request, close this with one of:

- Listing the server by name in `disabledMcpjsonServers`, which blocks it in every permission mode.
- Excluding project settings with `--setting-sources`, or the SDK's `settingSources`.
- Starting with `--strict-mcp-config`, so only servers passed with `--mcp-config` are used.
- Starting with `--bare`, which stops auto-discovery, including the project's `.mcp.json`.

The approval prompt is also skipped in a session started in `bypassPermissions` mode with `skipDangerousModePermissionPrompt` set in user or managed settings.

## 4. Choosing an organizational control pattern

**The docs describe patterns by strength of control.** Decide which one you want first.

| Pattern | What happens | Configure |
|---|---|---|
| Disable MCP | No servers load, apart from a few exceptions | `managed-mcp.json` with an empty server map |
| Fixed deployment | Everyone gets the same servers and can't add others | `managed-mcp.json` |
| Provided servers | Everyone gets the remote servers you list and keeps their own | `managedMcpServers` in managed settings |
| Approved catalog | Users add what they want from an approved list; everything else is blocked | `allowedMcpServers` + `allowManagedMcpServersOnly: true` |
| Soft allowlist | An allowlist users can broaden in their own settings | `allowedMcpServers` without `allowManagedMcpServersOnly` |
| Denylist only | Block known-bad servers, allow everything else | `deniedMcpServers` |

For most organizations the realistic choice is the approved catalog. Claude Code has no built-in MCP registry users can browse, so share the approved servers and their `claude mcp add` commands on an internal wiki, or distribute them as plugins through a managed plugin marketplace.

## 5. Writing allowedMcpServers and deniedMcpServers correctly

**Each entry in either list is an object with exactly one key: `serverUrl`, `serverCommand` or `serverName`. Only `serverUrl` and `serverCommand` work as controls; `serverName` doesn't.**

- **`serverUrl`**: the URL of a remote (HTTP or SSE) server, with `*` wildcards. A pattern with the hostname written out in full matches only the scheme's default port (443 for https), while a hostname containing `*` matches every port.
- **`serverCommand`**: the command and arguments that start a stdio server, as an array, matched exactly and in order. `["npx", "-y", "server"]` matches neither `["npx", "server"]` nor the same command with `--flag` added. The `env` block isn't compared.
- **`serverName`**: the label a user assigns. The docs say it "is not a security control" in either list, because anyone can name any server `github`.

Evaluation runs merge the lists → check the denylist → check the allowlist. A server that matches the denylist is blocked, and nothing overrides that. Once the allowlist contains any `serverUrl` entry, every remote server must match a URL, so a name alone no longer gets one through (the same goes for stdio servers and `serverCommand`).

Know the difference between unset and an empty array.

| Setting | Unset (default) | Empty array `[]` | Populated |
|---|---|---|---|
| `allowedMcpServers` | All servers allowed | Every server users add is blocked | Only matching servers allowed |
| `deniedMcpServers` | Nothing blocked | Nothing blocked | Matching servers blocked |

And the most commonly misunderstood point: **`allowedMcpServers` and `deniedMcpServers` are not managed-only keys; any settings file can set them.** Without `allowManagedMcpServersOnly`, allowlists from every scope, including a user's own `~/.claude/settings.json`, are merged, and users can widen what you permit. Setting `allowManagedPermissionRulesOnly`, the lock for permission rules, doesn't enforce the MCP allowlist either. They are separate locks.

## 6. Pinning servers with managed-mcp.json

**Deploy `managed-mcp.json` and the only MCP servers usable on that machine are the ones the organization chose.** Users can't add, modify or use any other server, including plugin-provided servers and servers passed with `--mcp-config`, and `claude mcp add` fails with an enterprise-policy error.

It lives in the same system directory as managed settings.

| OS | Path |
|---|---|
| macOS | `/Library/Application Support/ClaudeCode/managed-mcp.json` |
| Linux and WSL | `/etc/claude-code/managed-mcp.json` |
| Windows | `C:\Program Files\ClaudeCode\managed-mcp.json` |

The format matches a project `.mcp.json`. Three cautions:

- **It can't be delivered through server-managed settings.** It is a standalone file, so place it with anything that can write system paths with administrator rights: MDM, Group Policy, configuration management. If you don't need exclusive control and want to deliver from claude.ai, use `managedMcpServers` instead.
- **An empty server map effectively disables MCP.** Servers you provide through `managedMcpServers`, and anything else you allow alongside the managed set, still load.
- **Entries that use `${VAR}` are checked against the allowlist.** The organization's own servers skip the allowlist check only when they use no variable expansion.

Blocked servers disappear from `/mcp` and `claude mcp list` without telling the user why. When you introduce a new restriction, tell people beforehand which servers will stop.

## 7. Narrowing MCP tools with permission rules

**After allowing a server, you can still apply ask or deny per tool.** Permission rules name MCP tools like this:

- `mcp__puppeteer`: every tool from the server named `puppeteer`
- `mcp__puppeteer__*`: the same, written as a wildcard
- `mcp__puppeteer__puppeteer_navigate`: one specific tool from that server

Deny and ask rules accept `"mcp__*"` to mean every tool from every server. Allow rules, by contrast, accept wildcards only after a literal `mcp__<server>__` prefix; `"mcp__*"` in allow is skipped with a warning. The design prevents blanket auto-approval of every server.

A working rule of thumb: allow read-only tools, ask on tools that write or send data out, and deny tools you don't want used. For remote servers using OAuth, `oauth.scopes` pins the scopes Claude Code requests. The docs present it as the way to restrict a server to a security-team-approved subset when the authorization server advertises more scopes than you want to grant.

## 8. A vetting checklist for third-party MCP servers

**Judge a server not by how useful it is, but by what you hand it and how far it reaches.** Turning these items into an intake template makes the process manageable.

| Area | What to check |
|---|---|
| Source | Is the source code public? Who maintains it, and is it still being updated? Writing it in-house is the surest option |
| Where it runs | Local (stdio) or remote (HTTP)? If local, assume full access and ask whether you can read its code |
| Permissions it needs | Which tokens or API keys does it ask for? Does it want write access when read-only would do? |
| Token scope | Can you issue a dedicated account or least-privilege token? For OAuth, can `oauth.scopes` narrow it? |
| Network egress | Where does the server connect? Does it pull in untrusted content such as web pages or email (an injection entry point)? |
| Data that leaves | Will code, customer data or internal documents go out as tool arguments? Does a remote provider's retention policy fit your rules? |
| Version pinning | Does the setup fetch the latest version every time, for example via `npx`? Pin the version and update only after review |
| How you'll control it | Can you write it into the allowlist with `serverUrl` or `serverCommand`? Can you allow only the tools you need? |

If you're worried about environment secrets reaching local servers, `CLAUDE_CODE_SUBPROCESS_ENV_SCRUB=1` strips credentials from the environments of child processes, including stdio MCP servers (GitHub tokens and proxy settings are left in place).

## 9. An example allowlist configuration

**The example below is a starting point for the approved-catalog pattern, built only from keys confirmed in the official docs.** The URLs and package name are placeholders; replace them with what you have vetted.

```json
{
  "allowManagedMcpServersOnly": true,
  "allowedMcpServers": [
    { "serverUrl": "https://api.githubcopilot.com/*" },
    { "serverUrl": "https://mcp.example.internal/*" },
    { "serverCommand": ["npx", "-y", "@example-org/docs-mcp-server@1.4.2"] }
  ],
  "deniedMcpServers": [
    { "serverUrl": "https://*.untrusted.example.com/*" }
  ],
  "permissions": {
    "ask": [
      "mcp__github"
    ]
  }
}
```

Some notes on the intent.

- With `allowManagedMcpServersOnly`, any widening in user or project settings is ignored. The denylist still merges from every scope, so users can always block more for themselves.
- The `serverCommand` entry spells out the full arguments, version included. A single differing argument means no match, so a server started with another version or other arguments is blocked.
- The denylist entry `https://*.untrusted.example.com/*` contains `*` in the hostname, so it matches every port on those subdomains. A pattern with the hostname written out in full and no port matches only the default port, so watch how you write ports when blocking one specific host.
- `mcp__github` is set to ask so that tools from a server the user named `github` prompt on every call. Since users choose names, the allowlist above is what controls which server runs; the ask rule only adds a confirmation step.
- Because this goes in managed settings, you can deliver it by file, MDM or server-managed settings (unlike `managed-mcp.json`, it works through server-managed settings).

## 10. What to watch after rollout

**MCP governance doesn't end at deployment.** If you've set up OpenTelemetry, `OTEL_LOG_TOOL_DETAILS=1` adds MCP server and tool names to tool events, so you can count which servers people actually connect to (the variable is off by default). Drop allowlisted servers nobody uses, and send frequently requested ones through vetting.

---

## Summary: MCP is about turning trust decisions into settings

MCP servers greatly extend what Claude Code can do, but a local server's process runs outside the sandbox with the user's full access, and servers bring outside content into Claude's context. Permission rules govern only Claude's calls to their tools (section 7), not what the server process does. Since Anthropic doesn't audit them, the organizational baseline is four things: **vet them, write the allowlist with `serverUrl` and `serverCommand`, make only the organization's list apply with `allowManagedMcpServersOnly`, and keep CI from loading `.mcp.json`.**

Controls beyond MCP, such as permission modes and the sandbox, are covered in [security governance for rolling Claude Code out to an organization](/blog/claude-code-enterprise-security-governance-managed-settings-guide). If you'd like outside help designing your MCP vetting criteria or allowlist, I take enquiries at [Claude Code security governance for organizations](/security-audit/claude-code).
