MCP Clients Compared: Claude Desktop, Cursor, VS Code, Windsurf, Cline, and Zed
The Model Context Protocol solved the server-side fragmentation problem so thoroughly that a client-side one replaced it: six popular AI tools now speak MCP, and each one has its own config file, its own UI path, its own rules about where environment variables may come from, and its own tolerance for remote OAuth flows. If you have ever copied a server config from one editor's docs into another and watched nothing happen, this guide is for you.
Everything below assumes a server that works over stdio or Streamable HTTP. If you are choosing the transport first, read MCP stdio vs remote transports.
Where each client keeps config
| Client | Config location | stdio | Remote HTTP | OAuth in UI |
|---|---|---|---|---|
| Claude Desktop | ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) | Yes | Yes | Yes |
| Cursor | Settings → MCP, stored in ~/.cursor/mcp.json (global) or .cursor/mcp.json (project) | Yes | Yes | Yes |
| VS Code (Copilot Chat, agent mode) | .vscode/mcp.json or user settings.json under chat.mcp.servers | Yes | Yes | Yes |
| Windsurf | Settings → MCP, ~/.codeium/windsurf/mcp_config.json | Yes | Yes | Partial |
| Cline (VS Code extension) | MCP Servers panel, stored in extension globalStorage | Yes | Yes | Manual headers common |
| Zed | settings.json under context_servers | Yes | Yes | Limited |
Project-scoped config (Cursor, VS Code) is the right default for servers tied to one repository; global config suits personal tools every project needs. Do not commit secrets into project config; reference environment variables and document them in the README.
stdio config, client by client
The stdio shape is nearly identical everywhere, which is the protocol doing its job. Claude Desktop and Cursor use the canonical block:
{
"mcpServers": {
"orders": {
"command": "npx",
"args": ["-y", "@acme/orders-mcp"],
"env": {
"ORDERS_API_BASE": "http://localhost:4010",
"API_TOKEN": "${ORDERS_TOKEN}"
}
}
}
}VS Code expresses the same server declaratively, and supports stdio through a command entry:
{
"servers": {
"orders": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@acme/orders-mcp"],
"envFile": "${workspaceFolder}/.env.mcp"
}
}
}Zed uses its own key name but the same fields:
{
"context_servers": {
"orders": {
"command": { "path": "npx", "args": ["-y", "@acme/orders-mcp"] },
"env": { "ORDERS_API_BASE": "http://localhost:4010" }
}
}
}Cline is configured through its MCP Servers panel rather than a hand-edited file, which makes it the easiest for non-technical teammates and the hardest to script for everyone else.
Remote HTTP config
For a hosted server the config collapses to a URL, and the client is responsible for the OAuth 2.1 dance:
{
"mcpServers": {
"orders": {
"url": "https://mcp.example.com/orders/mcp"
}
}
}Claude Desktop and Cursor open the browser-based authorization flow automatically and store the refresh token. VS Code does the same in recent releases, surfacing consent as a notification. Windsurf and Zed have historically lagged on dynamic client registration; if the authorization server does not support pre-registered clients, you may need to pass a personal access token in headers:
{
"mcpServers": {
"orders": {
"url": "https://mcp.example.com/orders/mcp",
"headers": { "Authorization": "Bearer ${ORDERS_PAT}" }
}
}
}Treat that as a compatibility fallback, not the design target; the OAuth flow is described in MCP authentication explained.
The gotchas that waste an afternoon
PATH is not your shell's PATH. Desktop apps launched from the GUI on macOS inherit a minimal environment. If npx or python works in your terminal but the client reports command not found, use an absolute path (which npx) or wrap the launch in a shell script that sources your profile.
npx caching hides updates. -y @acme/orders-mcp without a version spec can serve a cached build. Pin versions for team-shared configs, or add a @latest policy everyone understands.
Tool count limits. Clients cap how many tools they expose to the model, typically in the dozens. A server advertising 300 tools gets silently truncated. Aggregate and name tools carefully; the gateway pattern in one MCP gateway for all your internal APIs addresses this directly.
Restart after edits. Most clients read config at launch. Cursor and VS Code watch their config files; Claude Desktop historically required a full restart. When a server does not appear, restart before debugging the server.
One client per stdio process is expected. Running the same stdio server in two open editors starts two processes, each with its own state. Anything shared belongs in a remote server, not in process memory.
Verify the server before blaming the client. Every "my tools don't appear" ticket I have seen split roughly evenly between client config and a server that fails initialize. Run the server through the Inspector first; the procedure is in how to test and debug an MCP server.
How to choose for a team rollout
For a company standardizing on internal MCP servers, the operational answer is usually:
- Publish remote Streamable HTTP servers with OAuth as the supported path; nobody hand-edits JSON for shared infrastructure.
- Let developers additionally run stdio builds locally against mocks and staging data.
- Document the two config blocks (URL and stdio) in one place, with the exact client versions that support OAuth in UI, and treat the rest as individual preference.
When the servers themselves are generated from OpenAPI specs, both configs point at builds of the same toolset: a local stdio build for offline, spec-driven work and a hosted build for shared services. You can generate either target from a spec in the online demo, and the end-to-end remote setup is walked through in connecting Cursor and Claude Code to your internal API over MCP.