Connect AI clients
How to add MCP servers to Claude Desktop without plaintext keys
Claude Desktop reads its local MCP servers from one JSON file, and any API key you give a server ends up in that file as plain text.
New to MCP? What is an MCP server? covers the basics first.
You'll add a local server to Claude Desktop, look at exactly what the config file exposes, and then change the setup so the file has nothing worth stealing.
Prerequisites
- Claude Desktop, recent enough to have Settings → Developer → Edit Config. I checked paths and permissions on 5.1231.2.
- Node.js 20 or later. Most local servers, and the
mcp-remotebridge used later, run throughnpx. - A text editor and, for the audit in Step 6,
jq.
Step 1: Find the config file
Open Claude Desktop's settings from the menu bar (the app menu, not the settings inside a chat), go to Developer, and click Edit Config. That creates the file if it doesn't exist and opens it. It lives at:
| OS | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
On my Mac the file also holds a preferences block the app writes itself, so add your servers under mcpServers and leave the rest alone.
Step 2: Add one server
Each entry under mcpServers is a name, a command, its arguments and an optional env block. This one runs the MCP project's reference server and passes it a key:
{
"mcpServers": {
"everything": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-everything"],
"env": {
"DEMO_API_KEY": "sk-demo-not-a-real-key"
}
}
}
}Quit Claude Desktop completely and open it again. It reads the file only at launch. Then click Add files, connectors, and more (the + at the bottom left of the chat box), hover over Connectors and choose Manage connectors, and the server should be listed with its tools.
If it isn't, the logs are in ~/Library/Logs/Claude on macOS and %APPDATA%\Claude\logs on Windows. mcp.log has connection errors, and mcp-server-everything.log has whatever the server printed to stderr:
tail -n 20 -f ~/Library/Logs/Claude/mcp*.logThe config file only takes local servers that Claude Desktop starts as processes. Remote servers go in through Customize → Connectors → Add custom connector, and Anthropic's connector docs say those connect "from Anthropic's cloud infrastructure, rather than from your local device", so a server on localhost or your VPN can't be added that way.
Step 3: See where that key ends up
The value in env is stored in the file exactly as you typed it. Claude Desktop hands it to the server as an environment variable when it starts the process. Here is who can read it.
Other people on the machine, usually not. The file Claude Desktop created on my Mac is -rw------- in a drwx------ folder, so only my account can open it. A file you create yourself, or copy in from somewhere, gets your default mode instead. My throwaway copy came out -rw-r--r--, readable by every account on the machine.
Every process running as you, yes. File permissions don't stop your own programs. That includes the MCP servers Claude Desktop starts: a filesystem server you granted your home folder can read ~/Library too, and the model decides what it reads. The key is also in the running server's environment. I launched the entry above the way Claude Desktop does and ran ps against it:
ps eww -p 23738... DEMO_API_KEY=sk-demo-not-a-real-key ...Your backups, yes. tmutil isexcluded reports the file as [Included], so every Time Machine backup has a copy of the key, and so does any cloud backup of your home folder.
Your dotfiles repo, if you put it there. People who manage settings with a dotfiles repository often symlink this file into it. Then the key is in git history, and a public repo publishes it.
Putting the key in args instead, for example --header "Authorization: Bearer ...", changes nothing. It's still in the file, and it also shows in the process list without the e flag.
Step 4: Prefer a desktop extension when the server ships one
Desktop extensions are servers packaged as .mcpb files (the format was called .dxt until September 2025). You install one from Settings → Desktop app → Extensions (Settings → Extensions in older versions), or drag a .mcpb file into the Settings window. The extension's manifest declares the settings it needs, and a field marked "sensitive": true is, in Anthropic's words, encrypted "using the operating system's secure storage": Keychain on macOS, Credential Manager on Windows. Claude Desktop substitutes the value when it starts the server, so the key never goes in claude_desktop_config.json.
That fixes the file and the backups. The key still reaches the server's environment at launch, and the server still holds a long-lived key for whatever API it calls.
Step 5: Route servers through a gateway so the file holds no keys
The other way out is to give the file nothing to hold. Put the key in a gateway, point Claude Desktop at the gateway, and let the gateway attach the credential on each call. Because the config file only takes local processes, the usual bridge is mcp-remote, which runs locally and forwards to a URL:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://127.0.0.1:7777/github/mcp"]
}
}
}That URL is the local proxy from the Statio desktop app, which writes this entry for you when you configure Claude Desktop. The proxy attaches a fresh, short-lived sign-in token to each request, the GitHub key stays encrypted in Statio, and the entry has no env block. Calls are checked against per-tool permissions before the key is used.
Any gateway that keeps the upstream key works the same way. If your gateway wants a static token in a header, mcp-remote can send one with --header, but then you are back to a secret in the file. Prefer one that signs you in, and note that mcp-remote keeps OAuth tokens it obtains in ~/.mcp-auth. How to keep API keys out of your AI agent's reach covers the patterns in more detail.
Step 6: Audit your own config
These commands print server and variable names, never values. Run them against the real path:
CONFIG="$HOME/Library/Application Support/Claude/claude_desktop_config.json"
stat -f '%Sp' "$CONFIG"
jq -r '.mcpServers // {} | to_entries[] | "\(.key): env keys = \((.value.env // {}) | keys | join(", "))"' "$CONFIG"
jq -r '.mcpServers // {} | to_entries[] | select((.value.args // []) | map(test("bearer|token|authorization"; "i")) | any) | .key' "$CONFIG"
ls -l "$CONFIG" | cut -c1On my throwaway copy, with one key in env and one bearer token in args:
-rw-r--r--
everything: env keys = DEMO_API_KEY
my-api: env keys =
my-api
-Then go through the list:
- The mode is
-rw-------. If not,chmod 600 "$CONFIG". - No server has a live key under
env. Move each one to an extension's sensitive field or a gateway. - No server has a token in
args. - The last command prints
-. Anlmeans the file is a symlink, so find out where it points and whether that place is in git. - No filesystem server has been granted your home folder or
~/Library. - Any key that was ever in the file gets rotated. Your backups still have the old copy.
Conclusion
Claude Desktop now runs your servers without a usable key in its config file, and you have a check you can rerun whenever someone adds a server.
The same problem exists in every client's config. Add MCP servers to Claude Code and set up MCP servers in Cursor cover those files, and per-tool permissions decide what each server may do once the key is out of reach.