How to Connect MCP Servers to Claude Code, Cursor and VS Code (2026)
This guide is for engineers who want the same MCP servers working in Claude Code, Cursor and VS Code without pasting tokens into JSON files. By the end you will have a remote OAuth server and a local stdio server connected in each client, a shared project config your team can commit, secrets coming from the environment, and a checklist for the three failures everyone hits: the server failed to start, the tools are not showing, and the call timed out. I checked everything against current docs on 31 October 2026, with Claude Code 2.1.316 (latest on npm), VS Code 1.144 and the current Cursor docs. You need Node.js 20+ for npx servers, uv for uvx servers, and Docker for containerised ones. If you are still choosing which servers to connect, read the sibling post on the best MCP servers for DevOps in 2026 first.
Step 1: Decide transport, scope and where the config lives
Every MCP connection comes down to two choices. Transport: a local process over stdio (the client starts a command), or a remote server over Streamable HTTP (the client connects to a URL). SSE still works in most clients but Claude Code's docs label it deprecated. Scope: is this server just for you, or for everyone who clones the repo?
| Client | Personal (all projects) | Shared with the repo | Top-level key | Variable syntax |
|---|---|---|---|---|
| Claude Code | ~/.claude.json (user or local scope) |
.mcp.json in project root |
mcpServers |
${VAR}, ${VAR:-default} |
| Cursor | ~/.cursor/mcp.json |
.cursor/mcp.json |
mcpServers |
${env:VAR}, ${workspaceFolder} |
| VS Code (VS Code format) | user profile mcp.json |
.vscode/mcp.json |
servers |
${input:id}, ${env:VAR} |
| VS Code (portable format) | ~/.copilot/mcp-config.json |
.mcp.json in workspace root |
mcpServers |
${VAR}, ${VAR:-default} |
Sources: Claude Code MCP docs, Cursor MCP docs, VS Code MCP configuration reference. The useful detail in the last row: VS Code's portable format and Claude Code both read a workspace-root .mcp.json with mcpServers and ${VAR} expansion, so one committed file can serve both. Cursor still wants its own file under .cursor/.
Step 2: Add a remote OAuth server to Claude Code
Remote servers are the easy case: no runtime to install, and OAuth means no token in any file. Sentry's hosted server is a good first test because it is the example in Claude Code's own help text.
# Claude Code 2.1.x. Default scope is "local": this project, only you.
claude mcp add --transport http sentry https://mcp.sentry.dev/mcp
# Or make it available in every project you open
claude mcp add --scope user --transport http github https://api.githubcopilot.com/mcp/
Then authenticate. Inside a session, run /mcp, pick the server and follow the browser flow; tokens refresh automatically. From a shell, the current docs add claude mcp login, with --no-browser for SSH sessions where you paste the redirect back:
claude mcp login sentry
claude mcp login sentry --no-browser # prints the URL instead of opening a browser
If your authorization server needs a fixed redirect URI, register http://localhost:8080/callback and add the server with --callback-port 8080. For a pre-registered client, add --client-id and --client-secret (the secret is prompted with masked input, or read from MCP_CLIENT_SECRET in CI). Note that claude mcp remove deletes the local tokens but does not revoke them at the provider, so run claude mcp logout <name> first if revocation matters.
For servers that take a static token instead of OAuth, use a header. This is fine for a personal scope, but the value is stored as written:
claude mcp add --transport http datadog https://mcp.datadoghq.com/v1/mcp \
--header "Authorization: Bearer $DD_ACCESS_TOKEN"
Step 3: Add a local stdio server with secrets from the environment
Stdio servers are a command plus arguments. The -- matters: everything after it goes to the server untouched, and without it Claude Code tries to parse the server's flags as its own.
# GitHub's local server in Docker, pinned, read-only
claude mcp add --env GITHUB_PERSONAL_ACCESS_TOKEN="$GITHUB_PAT" --transport stdio github-local \
-- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN -e GITHUB_READ_ONLY=1 \
ghcr.io/github/github-mcp-server:v2.0.3
# Kubernetes server via npx; from v0.1.0 settings come from TOML, not flags
claude mcp add --transport stdio kubernetes \
-- npx -y kubernetes-mcp-server@0.1.1 --config "$HOME/.config/k8s-mcp/readonly.toml"
Two gotchas from the docs. If the server name comes straight after --env, the CLI reads the name as another KEY=value pair and rejects it, so keep another option such as --transport stdio in between. And --env with "$GITHUB_PAT" writes the expanded token into ~/.claude.json. That file is private to you, but it is still a plaintext secret on disk. Step 4 avoids that.
The docker run -i flag is required (the client talks to the container over stdin), and never use -d, which detaches and breaks the stdio connection. VS Code's troubleshooting table calls this out explicitly.
For the Kubernetes server, v0.1.0 (29 October 2026) removed runtime CLI flags such as --read-only. A minimal read-only file:
# ~/.config/k8s-mcp/readonly.toml
read_only = true
toolsets = ["core", "helm"]
[[denied_resources]]
group = ""
version = "v1"
kind = "Secret"
Step 4: Share servers with your team using .mcp.json
Project scope writes a .mcp.json at the repository root that you commit. Reference secrets as variables so each engineer supplies their own:
claude mcp add --scope project --transport http github https://api.githubcopilot.com/mcp/
Then edit the file to add headers and the stdio servers:
{
"mcpServers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${GITHUB_MCP_PAT}",
"X-MCP-Readonly": "true"
}
},
"grafana": {
"type": "stdio",
"command": "uvx",
"args": ["mcp-grafana@2.1.1", "--disable-write"],
"env": {
"GRAFANA_URL": "${GRAFANA_URL:-http://localhost:3000}",
"GRAFANA_SERVICE_ACCOUNT_TOKEN": "${GRAFANA_SA_TOKEN}"
},
"timeout": 120000
}
}
}
Expansion works in command, args, env, url and headers. Three behaviours worth knowing, all from the Claude Code docs:
- An unset variable with no default is left as the literal
${VAR}text andclaude mcp listshows a warning. An empty string counts as set. - Some credential variables, including
ANTHROPIC_API_KEY,AWS_BEARER_TOKEN_BEDROCKandNPM_TOKEN, read as empty in a remote server'surlandheaders. Copy the value into a variable with your own name. - Every
"type"must be explicit. An entry with aurlbut notypeis read as stdio and rejected.
When a teammate opens the project, Claude Code asks them to approve project servers before using them. Until they do, claude mcp list shows ⏸ Pending approval. A cloned repo cannot approve its own servers through a committed .claude/settings.json while the folder is untrusted, which is the right default. If someone rejected a server by mistake, claude mcp reset-project-choices clears the decisions.
Step 5: Configure the same servers in Cursor
Cursor reads .cursor/mcp.json in the project and ~/.cursor/mcp.json globally. The shape is the same mcpServers object, but variables use ${env:NAME}, and stdio entries can load an envFile:
{
"mcpServers": {
"github": {
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${env:GITHUB_MCP_PAT}",
"X-MCP-Readonly": "true"
}
},
"grafana": {
"type": "stdio",
"command": "uvx",
"args": ["mcp-grafana@2.1.1", "--disable-write"],
"envFile": "${workspaceFolder}/.env"
},
"sentry": {
"url": "https://mcp.sentry.dev/mcp"
}
}
}
Per the Cursor docs, remote servers do not support envFile, so their headers must use variables from your shell or system environment. On macOS, an app launched from the Dock may not see variables you export in ~/.zshrc; starting Cursor from a terminal in the project avoids that. Remote servers get OAuth with PKCE, and for servers that need fixed credentials there is an auth object with CLIENT_ID, optional CLIENT_SECRET and scopes. Cursor asks for approval before running MCP tools by default. Put .env in .gitignore before you create it.
To debug, open the Output panel (Cmd+Shift+U on macOS) and select MCP Logs. You can disable a server temporarily without deleting its entry.
Step 6: Configure the same servers in VS Code
VS Code now has two formats. The long-standing VS Code format is .vscode/mcp.json with a top-level servers key and an inputs array that prompts for secrets once and stores them securely:
{
"inputs": [
{
"type": "promptString",
"id": "github_mcp_pat",
"description": "GitHub fine-grained PAT (read-only)",
"password": true
}
],
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp/",
"headers": {
"Authorization": "Bearer ${input:github_mcp_pat}",
"X-MCP-Readonly": "true"
}
},
"kubernetes": {
"type": "stdio",
"command": "npx",
"args": ["-y", "kubernetes-mcp-server@0.1.1", "--config", "${userHome}/.config/k8s-mcp/readonly.toml"]
}
}
}
The newer portable format is a workspace-root .mcp.json or ~/.copilot/mcp-config.json with mcpServers and ${VAR} references, the same shape as Claude Code. The reference has a capability table: the portable files have no inputs, no envFile and no per-server sandboxing, and servers that need ${input:...} values are not forwarded to Copilot harness sessions. The guided MCP: Add Server flow labels .vscode/mcp.json deprecated, and offers the workspace-root .mcp.json only when the experimental chat.mcp.workspaceRootConfig.enabled setting is on. My practical rule: if the team also uses Claude Code, commit a portable .mcp.json and use environment variables; if it is VS Code only and you want prompted secrets, .vscode/mcp.json still works.
You can also add a server from the command line:
code --add-mcp '{"name":"kubernetes","type":"stdio","command":"npx","args":["-y","kubernetes-mcp-server@0.1.1","--config","${userHome}/.config/k8s-mcp/readonly.toml"]}'
Workspace configs inherit Workspace Trust, so in restricted mode they do not start. For logs, run MCP: List Servers, pick the server and choose Show Output. If a server's tools changed, MCP: Reset Cached Tools clears the cache.
Step 7: Verify, then tune timeouts and output limits
A server showing as connected does not prove the agent can use it. Check status, then make one real tool call.
claude mcp list # health per server: Connected, Needs authentication, Failed to connect
claude mcp get grafana # scope, command, and an Issue: line on failure
Inside a session, /mcp shows each server's tool count and flags servers that expose no tools. Then ask for something only the server can answer, such as "list the five most recent failed workflow runs in this repo", and check the tool name in the transcript.
The limits that matter, from the Claude Code docs:
# Startup timeout for slow first runs (npx/uvx downloading packages), in ms
MCP_TIMEOUT=30000 claude
# Raise the per-result output cap (default 25,000 tokens; a warning shows above 10,000)
MAX_MCP_OUTPUT_TOKENS=50000 claude
A per-server "timeout" in milliseconds overrides MCP_TOOL_TIMEOUT for that server's calls, as in the Grafana entry above. Claude Code also defers MCP tool definitions by default (tool search), so many servers cost little context. If one server's tools must always be visible, set "alwaysLoad": true on that entry.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
✘ Failed to connect on a stdio server |
npx, uvx or docker not on the PATH the client sees |
Run the exact command in a terminal; use an absolute path such as /opt/homebrew/bin/uvx |
| First start times out, second works | Package download on first run exceeds the startup timeout | Pin the version, pre-fetch it once, or start with MCP_TIMEOUT=30000 |
⏸ Pending approval in claude mcp list |
Project .mcp.json server not yet approved |
Run claude interactively and approve; claude mcp reset-project-choices to redo |
! Needs authentication |
OAuth sign-in not completed or token expired | /mcp in a session or claude mcp login <name> |
Warning about an unset ${VAR} |
Variable not exported in the environment that started the client | Export it before launching, or use ${VAR:-default} |
Remote server returns 401 with ${NPM_TOKEN} in a header |
Claude Code reads some credential variables as empty in remote headers | Copy the value into a variable with your own name |
| Docker server exits immediately | Missing -i, or -d used |
Use docker run -i --rm ... with no -d |
Kubernetes server rejects --read-only |
v0.1.0 moved runtime flags to TOML | Put read_only = true in a file passed with --config |
| Server connected but tools not showing | Server disabled, toolset filter too narrow, or stale tool cache | Check the tool count in /mcp; in VS Code run MCP: Reset Cached Tools; in Cursor check MCP Logs |
| Workspace server never starts in VS Code | Folder is in restricted mode | Review the config, then trust the workspace; do not bypass trust for unreviewed repos |
FAQ
Where is the Claude Code MCP config file?
Project-scoped servers live in .mcp.json at the project root. User-scoped and local-scoped servers are stored in ~/.claude.json; local entries sit under the project's path in that file. Use claude mcp get <name> to see which scope a server came from.
Can Claude Code, Cursor and VS Code share one MCP config?
Partly. Claude Code and VS Code's portable format both read a workspace-root .mcp.json with mcpServers and ${VAR} variables. Cursor reads .cursor/mcp.json and uses ${env:VAR}, so it needs its own file, though the server entries are otherwise similar.
How do I keep MCP API keys out of git?
Reference environment variables in the committed file (${VAR} in Claude Code and portable VS Code files, ${env:VAR} in Cursor), use VS Code inputs with password: true, or prefer OAuth for remote servers. Never commit populated Authorization headers.
Why are my MCP tools not showing up?
Usually the server is not actually connected, is waiting for approval or authentication, or is filtered to a toolset that does not include the tool. Check claude mcp list, Cursor's MCP Logs or VS Code's Show Output first. In Claude Code, tool definitions are deferred by default, so they appear when Claude searches for them rather than all at once.
When a server misbehaves, the first thing I check is whether the exact command runs in a plain terminal with the same environment, because most "failed to start" reports are a PATH or variable problem, not MCP. In production I commit only variable references, give every server a read-only identity of its own, and pin every version, the same discipline as any other dependency. Official references: the Claude Code MCP docs, Cursor MCP docs, the VS Code MCP configuration reference and troubleshooting guide. For OAuth on your own servers, see how to deploy a secure remote MCP server with OAuth.
Related: How to Deploy a Secure Remote MCP Server with OAuth (2026), MCP Security in 2026: The Protocol Got Hardened. The Ecosystem Didn't. and What Is MCP (Model Context Protocol)? A Plain-English Guide for 2026.
I'm Ajay Kumar — I build and operate PandaStack, an open-source Firecracker microVM cloud for AI agents, and write about the infrastructure and tooling around it.
Need this kind of infrastructure work? See what I do or email hello@ajayk.sh.
Related
How to Debug an MCP Server with MCP Inspector (2026 Guide)
Debug MCP servers with MCP Inspector 2.13: web UI, CLI exit codes, the stdout logging trap Inspector will not catch, Streamable HTTP errors like 406 and -32020, protocol eras and -32022, logging after 2026-07-28, and where Claude, Cursor and VS Code keep MCP logs.
14 minOct 20, 2026How to Build an MCP Server in Python and TypeScript (2026)
Build, test and ship an MCP server on the official SDKs: Python mcp 2.3.0 and TypeScript @modelcontextprotocol/server 2.3.1, with tools, resources, prompts, MCP Inspector, Claude Desktop, Claude Code and VS Code config, stateless HTTP, and packaging.
15 minOct 31, 2026Best MCP Servers for DevOps in 2026: 7 Official Servers, Read-Only First
The official MCP servers worth connecting for DevOps work in late 2026: GitHub, Kubernetes, AWS, Terraform, Grafana, Sentry and Datadog. What each exposes, current versions, how to run them read-only, and the risks to plan for.
12 min