Configuring MCP Servers: `.mcp.json` Schema and Scopes
Exact configuration: server types (stdio, HTTP, SSE), all required/optional fields, environment variables, per-scope settings, and managed MCP for organizations.
Learning objectives
- Understand the complete `.mcp.json` schema for all server types
- Configure stdio (local) and HTTP (remote) servers with exact field names
- Use environment variables and dynamic headers for authentication
- Apply settings scopes: local, project, user, and managed
- Control MCP server availability with allowlists and denylists
ToolDix original visual
Frame
Name the outcome and constraints.
Build
Try one bounded workflow.
Review
Keep evidence, revise, and share.
MCP servers are configured in .mcp.json (project scope) or ~/.claude.json (user scope), with optional enforcement via managed settings. The configuration schema differs slightly by server type (stdio vs. HTTP), and scopes determine who can see and use each server.
The complete .mcp.json schema
A .mcp.json file in your project root defines servers shared with your team:
{
"mcpServers": {
"server_name_1": { ... server config ... },
"server_name_2": { ... server config ... }
}
}
The mcpServers object is a map where each key is a server name you choose, and each value is a server configuration object.
Server type 1: Stdio (local process)
Stdio servers run as local processes on your machine. Claude Code starts the process, connects via stdin/stdout with JSON-RPC messages.
Schema
{
"type": "stdio",
"command": "/absolute/path/to/executable",
"args": ["arg1", "arg2"],
"env": {
"VAR_NAME": "value"
}
}
Required fields:
type(string): Always"stdio"for local servers.command(string): Absolute path to the executable. Relative paths are not supported. Use$HOMEor construct full path.
Optional fields:
args(array of strings): Command-line arguments passed to the executable. Claude Code runs:command arg1 arg2 ...env(object): Environment variables passed to the subprocess. Keys are variable names, values are strings. These supplement (not replace) the parent process's environment.
Example: Playwright MCP server
{
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
This tells Claude Code to run: npx -y @playwright/mcp@latest
The server responds with tools like browser_navigate, browser_click, browser_screenshot.
Example: Custom server with environment
{
"type": "stdio",
"command": "/usr/local/bin/my-server",
"args": ["--mode", "production"],
"env": {
"API_KEY": "${API_KEY}",
"DEBUG": "false",
"LOG_LEVEL": "info"
}
}
Claude Code:
- Expands
${API_KEY}by reading from the process environment (see below) - Starts:
/usr/local/bin/my-server --mode production - Sets env vars:
API_KEY=<from process>,DEBUG=false,LOG_LEVEL=info
Server type 2: HTTP (remote service)
HTTP servers are hosted at a URL. Claude Code POSTs tool calls and requests to the server endpoint.
Schema
{
"type": "http",
"url": "https://mcp.example.com/api",
"headers": {
"Authorization": "Bearer token_string",
"X-Custom-Header": "value"
}
}
Required fields:
type(string): Always"http"for remote servers.url(string): HTTPS endpoint URL where Claude Code sends tool calls. Must be reachable from your machine.
Optional fields:
headers(object): Custom HTTP headers sent with every request. Used for authentication, API keys, custom metadata.
Example: GitHub MCP server
{
"type": "http",
"url": "https://api.github.com/mcp",
"headers": {
"Authorization": "Bearer ghp_..."
}
}
Claude Code POSTs to https://api.github.com/mcp with the token in the Authorization header.
Example: Sentry MCP server (OAuth)
{
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
}
For OAuth servers, headers are not needed at setup. Claude Code prompts you to authenticate in your browser on first use.
Server type 3: SSE (Server-Sent Events, rare)
SSE servers send events from server to client. Uncommon; most servers use HTTP POST.
Schema
{
"type": "sse",
"url": "https://mcp.example.com/events"
}
Less documented and rarely used. Stick with stdio and HTTP for most cases.
Authentication: static tokens vs. OAuth vs. dynamic headers
Static token in headers
For services with API keys or bearer tokens that don't expire:
{
"type": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer sk_live_abcdef123456"
}
}
Risk: Token is stored in plain text in .mcp.json. For shared projects, never commit real tokens.
Environment variable expansion
Use ${VAR_NAME} or ${VAR_NAME:-default} to read from environment variables:
{
"type": "http",
"url": "https://api.example.com/mcp",
"headers": {
"Authorization": "Bearer ${API_TOKEN}"
}
}
At runtime, Claude Code reads $API_TOKEN from your shell environment. Commit the file with ${API_TOKEN}; the actual token is only in your environment.
# Set the token before running Claude Code
export API_TOKEN="sk_live_..."
claude
OAuth (browser sign-in)
Some servers (Sentry, Linear, GitHub) support OAuth. No token needed in config; Claude Code prompts you to sign in on first use:
{
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
}
First time Claude tries to use it:
/mcp
Select: sentry
> Authenticate
[Browser opens, you sign in]
Status: Connected
Configuration scopes: where servers live
MCP servers can be defined at different scopes, each with different visibility and sharing:
| Scope | File Location | Shared? | Use Case | Precedence |
|---|---|---|---|---|
| Managed | /Library/Application Support/ClaudeCode/managed-mcp.json (macOS)/etc/claude-code/managed-mcp.json (Linux)C:\Program Files\ClaudeCode\managed-mcp.json (Windows) | Yes (IT deployed) | Organization-wide policy; users cannot override | 1 (highest) |
| Local | ~/.claude.json (per-project state, in your home directory) | No (not shared) | Personal servers used in all projects; per-project OAuth state | 2 |
| Project | .mcp.json in repo root | Yes (git committed) | Team-shared servers; part of reproducible setup | 3 |
| User | ~/.claude.json (global) | No (personal only) | Personal servers used everywhere | 4 |
Precedence: If the same server name is defined in multiple scopes, the highest-precedence one wins (Managed > Local > Project > User).
Which scope to use
Use Managed (enterprise policy file): Deploy identical servers to all employees; users cannot add their own MCP servers.
Use Project (.mcp.json): Team wants a shared GitHub server, a Slack server, and maybe a custom internal tool. Commit .mcp.json to git so teammates get the same servers when they clone.
Use User (~/.claude.json): You personally use a Playwright server across all your projects, or you have a personal API key to an internal tool. Not shared; stays on your machine.
Use Local (~/.claude.json per-project state): Claude Code stores per-project OAuth state here automatically (e.g., Sentry session tokens). You don't write to this directly.
Example: project-level server in .mcp.json
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.github.com/mcp",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
},
"internal-api": {
"type": "http",
"url": "https://internal.example.com/mcp",
"headers": {
"Authorization": "Bearer ${INTERNAL_TOKEN}"
}
}
}
}
Team members clone the repo and run:
export GITHUB_TOKEN="ghp_..."
export INTERNAL_TOKEN="..."
claude
First time, they approve each server. Then servers are cached and ready.
Managing server access with settings
You can control which servers Claude Code allows using settings in any .claude/settings.json:
{
"allowedMcpServers": [
{ "serverUrl": "https://api.github.com/*" },
{ "serverUrl": "https://mcp.sentry.dev/*" }
],
"deniedMcpServers": [
{ "serverUrl": "https://untrusted.example.com/*" }
],
"enabledMcpjsonServers": ["github", "playwright"],
"disabledMcpjsonServers": ["internal-api"]
}
Settings fields:
allowedMcpServers(array): List of allowed servers by URL or command. If set, only matching servers load.deniedMcpServers(array): List of blocked servers. Always applied; overrides allowlist.enabledMcpjsonServers(array): Names of servers from.mcp.jsonto enable.disabledMcpjsonServers(array): Names of servers from.mcp.jsonto disable.allowManagedMcpServersOnly(boolean, managed only): If true, only servers inmanaged-mcp.jsonare allowed.
Allowlist and denylist matching
Entries match by URL pattern (with * wildcards), exact command string, or server name:
{
"allowedMcpServers": [
{ "serverUrl": "https://api.github.com/*" },
{ "serverUrl": "https://*.internal.example.com/*" },
{ "serverCommand": ["npx", "-y", "@playwright/mcp@latest"] }
],
"deniedMcpServers": [
{ "serverUrl": "https://untrusted.example.com/*" }
]
}
URL matching rules:
https://api.github.com/*matcheshttps://api.github.com/mcphttps://*.example.com/*matcheshttps://api.example.com/mcp,https://data.example.com/mcp, etc.*://example.com/*matches both HTTP and HTTPS to that domainhttp://localhost:*/*matches any port on localhost
Command matching: Must match exactly, including all arguments in order.
Managed MCP for organizations
If you're an IT administrator, deploy managed-mcp.json at the system level to enforce which servers run in your organization:
Exclusive control: all users get the same servers
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.github.com/mcp"
},
"sentry": {
"type": "http",
"url": "https://mcp.sentry.dev/mcp"
},
"internal-tools": {
"type": "stdio",
"command": "/usr/local/bin/company-mcp-server",
"env": {
"COMPANY_API_URL": "https://internal.example.com"
}
}
}
}
Users cannot add, modify, or use any other MCP servers. claude mcp add fails with: Cannot add MCP server: enterprise MCP configuration is active and has exclusive control over MCP servers.
Disable MCP entirely
{
"mcpServers": {}
}
All MCP functionality is disabled; users see no MCP servers in /mcp and cannot add any.
Allowlist with policy
For an approved-catalog approach, combine managed-mcp.json with allowlists in user/project settings:
In managed-mcp.json:
{
"allowedMcpServers": [
{ "serverUrl": "https://api.github.com/*" },
{ "serverUrl": "https://mcp.sentry.dev/*" }
],
"allowManagedMcpServersOnly": true
}
Only servers in the allowlist can be added. Users cannot add servers outside the list, even if they know the URL.
Troubleshooting configuration
Server shows as "Connected" but no tools appear
The server started but didn't return tools. Check the server's logs:
# For stdio servers, the command runs and outputs to stdout
# Test manually:
npx @playwright/mcp@latest
For HTTP servers, the endpoint is unreachable or returns wrong format.
"Failed to connect"
Stdio server: The command doesn't exist or isn't executable. Check the path:
# Test if command exists
/usr/local/bin/my-server --version
HTTP server: The URL is unreachable. Test with curl:
curl -I https://api.example.com/mcp
Settings don't take effect
Claude Code reads settings at session start. If you edit .claude/settings.json while a session is active, exit and restart:
# Exit Claude Code
# Edit .claude/settings.json
# Start a new session
claude
Example: complete team MCP setup
Here's a realistic .mcp.json for a team:
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.github.com/mcp",
"headers": {
"Authorization": "Bearer ${GITHUB_TOKEN}"
}
},
"slack": {
"type": "http",
"url": "https://mcp.slack.com/api/mcp",
"headers": {
"Authorization": "Bearer ${SLACK_BOT_TOKEN}"
}
},
"postgres": {
"type": "stdio",
"command": "/usr/local/bin/mcp-postgres",
"args": ["--host", "db.internal.example.com", "--port", "5432"],
"env": {
"PGUSER": "${DB_USER}",
"PGPASSWORD": "${DB_PASSWORD}"
}
},
"playwright": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}
Add .mcp.json to git. Team members:
git clone <repo>
export GITHUB_TOKEN="ghp_..."
export SLACK_BOT_TOKEN="xoxb-..."
export DB_USER="claude_code"
export DB_PASSWORD="..."
claude
On first run, Claude Code prompts to approve each server. After that, all four servers are available for every session.
Common mistake
Storing plaintext secrets in .mcp.json and committing to git. Always use environment variable expansion (${VAR_NAME}) for tokens and passwords, and set them in your shell before running Claude Code. Keep real secrets out of version control. If a secret is already in git history, rotate it immediately.
Sources and license context
These references informed the lesson. ToolDix adds its own explanation, workflow, and practice rather than reproducing source material. Every link below leaves ToolDix and opens the publisher's own site in a new tab.
- MCP Reference (opens code.claude.com in a new tab)External · code.claude.com (Anthropic terms apply)
- Managed MCP (opens code.claude.com in a new tab)External · code.claude.com (Anthropic terms apply)
- Settings (opens code.claude.com in a new tab)External · code.claude.com (Anthropic terms apply)
Keep going
Read these next on ToolDix.
Original lessons that build on what you just read.