mcpstepbystep

Tutorials Foundations

How to connect an MCP server to Claude, Cursor, VS Code, and ChatGPT (every config file)

Copy-paste MCP configs for Claude Desktop, Claude Code, Cursor, VS Code, ChatGPT, Windsurf and Gemini CLI — config paths, transports, env vars, Windows fixes.

updated 2026-07-19 ⏱ 20–30 min

The whole point of MCP is that a server written once works everywhere — but every client invented its own config file, its own JSON shape, and its own quirks. This page is the reference: one section per client, copy-paste configs, and a summary table. Bookmark it; the examples use the dev-notes server from the Python tutorial, so substitute your own command and paths.

The matrix

ClientConfig locationTransportsEnv vars / secrets
Claude Desktopclaude_desktop_config.json (macOS: ~/Library/Application Support/Claude/, Win: %AppData%\Claude\)stdio local; remote via claude.ai Connectorsenv block (literal values)
Claude Codeclaude mcp add CLI; project .mcp.jsonstdio, Streamable HTTP, SSE (legacy)env block; ${VAR} expansion in .mcp.json
Cursor.cursor/mcp.json (project) or ~/.cursor/mcp.json (global)stdio, Streamable HTTP/SSE via urlenv block (literal values)
VS Code (Copilot).vscode/mcp.json (workspace) or user-level mcp.jsonstdio, Streamable HTTP, SSEenv block; ${input:…} prompts, ${env:VAR}
ChatGPTNo file — Settings → Connectors (UI only)Remote Streamable HTTP onlyOAuth 2.1 only
Windsurf~/.codeium/windsurf/mcp_config.jsonstdio; remote via serverUrlenv block (literal values)
Gemini CLI~/.gemini/settings.json or project .gemini/settings.jsonstdio, Streamable HTTP (httpUrl), SSE (url)env block; $VAR/${VAR} expansion

Three rules apply to every stdio config below: use absolute paths (GUI apps don’t inherit your shell PATH or cwd), restart the client fully after editing, and on Windows escape backslashes as \\ in JSON.

Claude Desktop

File: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %AppData%\Claude\claude_desktop_config.json (Windows). Also reachable via Settings → Developer → Edit Config.

{
  "mcpServers": {
    "dev-notes": {
      "command": "/Users/you/.local/bin/uv",
      "args": [
        "--directory",
        "/Users/you/projects/dev-notes",
        "run",
        "server.py"
      ],
      "env": {
        "NOTES_API_KEY": "sk-your-key-here"
      }
    }
  }
}

Notes:

  • The env block takes literal values only — no variable expansion. Anything you put here sits in plaintext on disk; keep real secrets out of servers you don’t have to.
  • Local config is stdio-only. Remote servers connect through Connectors (Settings → Connectors on claude.ai/desktop), which handle OAuth in the UI instead of this file.
  • Quit fully from the menu bar/tray to reload; logs live at ~/Library/Logs/Claude/ (macOS) or %AppData%\Claude\logs\ (Windows).

Claude Code

Claude Code is CLI-first — three transports:

# stdio (everything after -- is the launch command)
claude mcp add --transport stdio dev-notes -- \
  uv --directory /Users/you/projects/dev-notes run server.py

# Streamable HTTP (remote)
claude mcp add --transport http github https://api.githubcopilot.com/mcp/

# SSE (legacy servers only)
claude mcp add --transport sse legacy-server https://example.com/sse

Manage with claude mcp list, claude mcp get dev-notes, claude mcp remove dev-notes, and /mcp inside a session (status, tools, OAuth login for remote servers).

The part unique to Claude Code is scopes (--scope):

  • local (default) — you, this project only.
  • user — you, every project.
  • project — written to a .mcp.json at the repo root, checked into git, shared with your team:
{
  "mcpServers": {
    "dev-notes": {
      "command": "uv",
      "args": ["--directory", "${workspaceFolder}", "run", "server.py"],
      "env": {
        "NOTES_API_KEY": "${NOTES_API_KEY}"
      }
    }
  }
}

.mcp.json supports ${VAR} environment-variable expansion — the only first-party client that lets a committed config reference secrets without embedding them. Teammates get prompted to approve the file’s servers on first use.

Cursor

File: .cursor/mcp.json in the project root (per-project) or ~/.cursor/mcp.json (all projects). Same mcpServers shape as Claude Desktop:

{
  "mcpServers": {
    "dev-notes": {
      "command": "node",
      "args": ["/Users/you/projects/dev-notes-ts/build/index.js"],
      "env": {
        "NOTES_API_KEY": "sk-your-key-here"
      }
    },
    "remote-example": {
      "url": "https://example.com/mcp"
    }
  }
}

A command entry is stdio; a url entry is remote (Streamable HTTP, with SSE fallback for legacy servers). Enable and inspect servers under Cursor Settings → MCP, where each server has a toggle and a tool list. Tools run in Agent mode (the chat side panel), with per-call approval unless you enable auto-run. If you carry many servers, disable the unused ones — every enabled tool definition is context the model pays for on each request, and Cursor caps how many active tools it will send.

VS Code (GitHub Copilot)

VS Code has native MCP support in Copilot’s agent mode. Workspace file: .vscode/mcp.json — note the differences: the top-level key is servers (not mcpServers), each entry declares a type, and secrets get a first-class mechanism called inputs:

{
  "inputs": [
    {
      "type": "promptString",
      "id": "notes-api-key",
      "description": "API key for the dev-notes server",
      "password": true
    }
  ],
  "servers": {
    "dev-notes": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/build/index.js"],
      "env": {
        "NOTES_API_KEY": "${input:notes-api-key}"
      }
    },
    "remote-example": {
      "type": "http",
      "url": "https://example.com/mcp"
    }
  }
}

${input:…} prompts once and stores the value encrypted — the cleanest secret handling of any client here; ${env:VAR} and ${workspaceFolder} also work. Alternatives to hand-editing: the MCP: Add Server command, or MCP: Open User Configuration for a user-level mcp.json that applies across workspaces. Servers start on demand; tools appear in agent mode’s tools picker, and the MCP: List Servers command shows status and logs.

ChatGPT (connectors)

ChatGPT is the odd one out: no local stdio, no config file. It only connects to remote MCP servers over Streamable HTTP, authenticated with OAuth 2.1 (it registers itself via dynamic client registration). Your laptop-only stdio server cannot connect here — deploy it behind HTTPS first (the deployment tutorials later in this path cover exactly that).

Setup is entirely in the UI:

  1. Settings → Connectors → Create (availability varies by plan; connector creation is aimed at paid and business tiers).
  2. Enter the server’s URL (for example https://your-server.example.com/mcp) and complete the OAuth flow.
  3. By default, connectors surface search and fetch tools designed for deep research. To use arbitrary tools, enable developer mode (Settings → Connectors → Advanced), which unlocks full tool calling with per-call confirmation.

If you only remember one thing: Claude/Cursor/VS Code configs describe how to launch a server; ChatGPT only accepts an address where one is already running.

Windsurf

File: ~/.codeium/windsurf/mcp_config.json, or via the UI (Windsurf Settings → Cascade → MCP → Add Server). Claude-Desktop-style shape, with one Windsurf-specific key for remote servers — serverUrl:

{
  "mcpServers": {
    "dev-notes": {
      "command": "node",
      "args": ["/Users/you/projects/dev-notes-ts/build/index.js"],
      "env": {
        "NOTES_API_KEY": "sk-your-key-here"
      }
    },
    "remote-example": {
      "serverUrl": "https://example.com/mcp"
    }
  }
}

Tools run inside Cascade (the agent panel). After editing the file, press the refresh button in the MCP panel or restart Windsurf.

Gemini CLI

File: ~/.gemini/settings.json (user-wide) or .gemini/settings.json in the project (shared with the repo). MCP servers live under the familiar mcpServers key:

{
  "mcpServers": {
    "dev-notes": {
      "command": "uv",
      "args": ["--directory", "/Users/you/projects/dev-notes", "run", "server.py"],
      "env": {
        "NOTES_API_KEY": "$NOTES_API_KEY"
      },
      "timeout": 30000
    },
    "remote-http": {
      "httpUrl": "https://example.com/mcp"
    },
    "remote-sse-legacy": {
      "url": "https://example.com/sse"
    }
  }
}

Mind the transport keys: command = stdio, httpUrl = Streamable HTTP, url = legacy SSE — mixing up the last two is the classic Gemini CLI mistake. Environment variables expand with $VAR or ${VAR} syntax. Inside the CLI, /mcp lists servers, their status, and their tools.

Windows: the special cases

Windows breaks stdio configs in three specific ways, across all clients:

  1. JSON path escaping. Every backslash must be doubled: "C:\\Users\\you\\projects\\server\\build\\index.js". A single \ is an invalid JSON escape and the whole file is silently rejected — which looks like “my server just doesn’t appear”.
  2. spawn npx ENOENT. On Windows, npx, npm, and friends are .cmd shims that can’t be spawned directly by every client. Launch through the shell instead:
{
  "mcpServers": {
    "some-npx-server": {
      "command": "cmd",
      "args": ["/c", "npx", "-y", "@example/mcp-server"]
    }
  }
}
  1. Full paths to interpreters. where.exe uv, where.exe node, where.exe python — and use that output as the command. GUI clients on Windows are even less likely than macOS ones to see your PATH, especially for user-profile installs like uv’s default %USERPROFILE%\.local\bin.

Troubleshooting

spawn npx ENOENT / spawn uv ENOENT / spawn node ENOENT The client can’t find the executable: GUI apps don’t inherit your shell PATH. Use the absolute path from which <cmd> (macOS/Linux) or where.exe <cmd> (Windows) as the command. On Windows, .cmd shims like npx additionally need "command": "cmd", "args": ["/c", "npx", ...].

The server appears in one client but not another, same machine Almost always a config-shape mismatch: mcpServers vs servers (VS Code), url vs httpUrl vs serverUrl (Cursor vs Gemini vs Windsurf), or a type field VS Code wants that others ignore. Diff your JSON against the block for that client above — the shapes are close enough to look interchangeable, and they aren’t.

No such tool available when the model tries to call a tool The model referenced a tool the client didn’t send. Causes: the server was toggled off (Cursor/VS Code tool pickers), the tool cap was hit in a client that limits active tools, or the server restarted with different tool names mid-session. Check the client’s MCP panel for the live tool list, re-enable, and start a fresh conversation.

MCP error -32000: Connection closed right after startup The launch command in your config fails outside a shell. Run it verbatim in a terminal; fix whatever error you see (missing build, wrong --directory, wrong interpreter). If it works in the terminal but not the client, it’s PATH or cwd — return to rule one: absolute paths everywhere.

Edited the JSON, nothing changed Two candidates: the client hasn’t re-read the file (Claude Desktop needs a full quit from the tray/menu bar; Windsurf needs the MCP refresh button), or your edit broke the JSON — a trailing comma or single backslash makes the file unparseable and most clients fail silently to the previous state. Validate the file, then restart properly.

Next step

Every client is wired up — so the next failure you hit will be inside a session, not a config file. Time to learn the debugging workflow with the MCP Inspector: reproduce issues outside the client, read the right logs, and stop guessing.

newsletter

One practical MCP guide in your inbox. No news, no hype.

Tutorials and decision frameworks as they ship. Unsubscribe anytime.