Connecting MCP Servers in Claude Code
In the previous article we covered the MCP architecture: three roles (Host/Client/Server), three primitives (tools/resources/prompts), and two transports (stdio and streamable HTTP). Now let's move to practice — how exactly to connect a server to a specific project, where configuration is stored, and how to avoid security mistakes.
The central command: claude mcp add
All MCP server management goes through the claude mcp subcommand. The basic syntax depends on the transport type.
For HTTP servers (cloud services):
# Connect a remote server over HTTP
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
# With an authorization header (static token)
claude mcp add --transport http github https://api.githubcopilot.com/mcp/ \
--header "Authorization: Bearer YOUR_GITHUB_PAT"For stdio servers (local processes):
# Important: the double dash -- separates claude flags from the server command
claude mcp add --transport stdio airtable --env AIRTABLE_API_KEY=key123 \
-- npx -y airtable-mcp-server
# PostgreSQL via a local proxy
claude mcp add --transport stdio db \
-- npx -y @bytebase/dbhub --dsn "postgresql://ro:pass@localhost:5432/prod"Note the -- (double dash): without it, Claude Code will try to parse the server's arguments as its own flags and throw an error. This is a classic gotcha when first working with stdio servers.
Managing connected servers — a few commands worth knowing:
claude mcp list # Show all servers and their status
claude mcp get github # Details for a specific server
claude mcp remove github # Remove a serverInside a Claude Code session, /mcp is available — a panel showing the status of each server, the number of tools, and an OAuth authorization button.
/mcp
MCP Servers
──────────────────────────────────────────
● github Connected 42 tools
● sentry Connected 8 tools
! notion Auth Required [Connect]
○ local-db DisconnectedThree scopes: where configuration is stored
The scope determines two things: where the configuration physically lives and who else can see it.
graph TD
A[claude mcp add] --> B{--scope?}
B -->|local (default)| C[~/.claude.json<br/>under project path]
B -->|project| D[.mcp.json<br/>in project root]
B -->|user| E[~/.claude.json<br/>globally]
C --> F[You only,<br/>current project]
D --> G[Whole team<br/>via git]
E --> H[You only,<br/>all projects]Local (default) — the server is available only to you, only in the current project. Stored in ~/.claude.json under the project path. Never goes into git; no one else sees it.
# Both calls are equivalent — local is the default
claude mcp add --transport http stripe https://mcp.stripe.com
claude mcp add --transport http stripe --scope local https://mcp.stripe.comProject — the server is available to the entire team, because the configuration is written to .mcp.json in the project root. This file is committed to git. Every team member who opens the project will see the same server.
claude mcp add --transport http paypal --scope project https://mcp.paypal.com/mcpThe result — .mcp.json is created or updated:
{
"mcpServers": {
"paypal": {
"type": "http",
"url": "https://mcp.paypal.com/mcp"
}
}
}User — the server is available to you across all projects. Also stored in ~/.claude.json, but not tied to any specific project.
claude mcp add --transport http hubspot --scope user https://mcp.hubspot.com/anthropicQuick-reference table:
| Scope | Loaded in | Visible to others | Stored in |
|---|---|---|---|
local | Current project | No | ~/.claude.json |
project | Current project | Yes, via git | .mcp.json |
user | All projects | No | ~/.claude.json |
The .mcp.json file: manual configuration and environment variables
The .mcp.json file is not just an artifact produced by claude mcp add — it can and should be edited by hand, especially for team-wide settings. A typical scenario: the config is committed to the repository, but each developer supplies their own API key via an environment variable.
Claude Code supports variable expansion directly in .mcp.json:
{
"mcpServers": {
"api-server": {
"type": "http",
"url": "${API_BASE_URL:-https://api.example.com}/mcp",
"headers": {
"Authorization": "Bearer ${API_KEY}"
}
},
"local-db": {
"command": "npx",
"args": ["-y", "@bytebase/dbhub", "--dsn", "${DATABASE_URL}"],
"env": {
"LOG_LEVEL": "${LOG_LEVEL:-info}"
}
}
}
}The ${VAR:-default} syntax is standard shell style: if the variable is not set, the value after :- is used. Expansion works in the command, args, env, url, and headers fields. If a required variable is not set (no default provided), Claude Code will be unable to parse the config.
In practice this means: URLs and server names can safely be committed to .mcp.json, but secrets (tokens, passwords) should be moved to .env or the system environment. Add .env to .gitignore, but not .mcp.json.
Transports in detail
We already covered stdio and streamable HTTP in the previous article at the protocol level. Here are the practical nuances of connecting them.
Streamable HTTP — the recommended option for cloud services. In the JSON config the type is streamable-http (per the spec), but the CLI accepts both http and streamable-http.
{
"mcpServers": {
"my-api": { "type": "streamable-http", "url": "https://api.example.com/mcp" }
}
}SSE (Server-Sent Events) — a legacy transport, officially deprecated. If a server offers a choice, go with HTTP. Some older servers still use SSE so support exists, but new integrations should not be built on it.
WebSocket — for servers that push events on their own (CI notifications, monitoring). Does not support OAuth or the --transport CLI flag; configured only via .mcp.json or claude mcp add-json:
claude mcp add-json events-server \
'{"type":"ws","url":"wss://mcp.example.com/socket","headers":{"Authorization":"Bearer TOKEN"}}'OAuth for remote servers
Most cloud MCP servers (Sentry, Notion, Slack, and others) require authentication via OAuth 2.0. The process is two steps:
1. Add the server with claude mcp add — no token needed, just the URL.
2. Run /mcp inside Claude Code and follow the instructions in the browser.
# Step 1: add the server
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
# Step 2: inside the session run /mcp → click Connect → the browser opens automaticallyClaude Code detects that the server returned 401 Unauthorized and marks it in /mcp as requiring authentication. Tokens are stored in the system keychain (macOS) or in a credentials file — not in .mcp.json. They are refreshed automatically when they expire.
If the OAuth provider requires a pre-registered redirect_uri (does not support Dynamic Client Registration), you need to explicitly set a port and register the application in advance:
claude mcp add --transport http \
--client-id your-client-id --client-secret --callback-port 8080 \
my-service https://mcp.example.com/mcpThe --client-secret flag without a value enables masked input in the terminal — the secret will not end up in the shell history. For CI, use the MCP_CLIENT_SECRET environment variable instead.
Security: what to know before connecting
MCP servers execute tools on your behalf. A few rules always worth following:
Verify the server's source. A server that loads external content (web pages, documents, API responses) could potentially pass hostile instructions to Claude — this is prompt injection. For unfamiliar servers, read the source code or look for security audits.
Grant minimal permissions. For a GitHub PAT, select only the needed repositories with the minimum set of permissions — not repo:write for the whole account when read-only access to one repository is enough. For databases, use a read-only user if you don't need write access.
Project-scope requires explicit approval. Claude Code will not silently connect a .mcp.json server — on first launch it will show an approval prompt. This is protection against accidentally pulling in malicious servers from cloned repositories. To reset approval choices: claude mcp reset-project-choices.
Secrets go in the environment, not the config. .mcp.json is committed to git. If a token ends up in there, consider it compromised. Use ${VAR} and .env (in .gitignore).
# Bad — token in the config, which will go into git
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ghp_real_token_here" }
}
}
}
# Good — token from an environment variable
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": { "Authorization": "Bearer ${GITHUB_PAT}" }
}
}
}Diagnosing problems
A few common situations and how to debug them:
- Server hangs on startup.
MCP_TIMEOUT=10000 claude— reduce the timeout to 10 seconds to get an error quickly instead of waiting indefinitely. - Server returns too many tokens.
MAX_MCP_OUTPUT_TOKENS=50000 claude— increase the limit (default is 25,000 tokens; a warning appears when 10,000 tokens are exceeded). - HTTP/SSE server dropped mid-session. Claude Code reconnects automatically with exponential backoff (up to 5 attempts, starting at 1 s). Status is visible in
/mcp. - Server in
.mcp.jsonstuck as "Pending approval". Runclaudeinteractively — an approval prompt will appear.
See also
- Model Context Protocol: архитектура и основы — primitives, transports, and MCP's place in the ecosystem
- Создание собственного MCP-сервера — FastMCP, SDK, MCP Inspector
- Практика: GitHub, базы данных и веб-API через MCP — real-world integrations and security patterns
- Hooks — события жизненного цикла — deterministic hooks as an alternative to MCP for local automation
- Что выбрать: команда, навык, субагент, MCP или хук — when MCP is the only right choice
- Настройки и иерархия конфигурации — how MCP scopes fit into the overall Claude Code settings hierarchy