The most common security mistake in client-side MCP setups isn't exotic — it's a plaintext API key sitting in a config file that later gets pushed to a public GitHub repo, synced via a dotfiles manager, or copy-pasted into a shared workspace. Because mcp.json is just JSON on disk, anything with read access to it — a teammate, a CI log, a misconfigured backup — can read your key. This lesson covers the actual mechanism VS Code provides to avoid that, and the security model MCP itself expects for local servers.
The inputs pattern
VS Code's mcp.json supports an inputs array specifically so you don't have to hardcode sensitive values. You declare a placeholder with a type, an id, and a human-readable prompt; if you mark it "password": true, VS Code masks the input field. Then you reference that placeholder from a server's env block with ${input:your-id} instead of the literal secret:
{
"inputs": [
{
"type": "promptString",
"id": "weather-api-key",
"description": "API key for the weather service",
"password": true
}
],
"servers": {
"weather": {
"type": "stdio",
"command": "npx",
"args": ["-y", "weather-mcp-server"],
"env": {
"WEATHER_API_KEY": "${input:weather-api-key}"
}
}
}
}
The first time this server starts, VS Code prompts you for the value in a masked input box and stores it securely for later runs — the raw key never has to appear inside the JSON file that gets committed to source control.
Why stdio servers don't use OAuth
It's worth being precise here about what "securing API keys" actually means for a local, stdio-transport server, versus a remote one. The MCP authorization spec explicitly says implementations using STDIO transport should not follow its OAuth-based authorization flow, and should instead retrieve credentials from the environment — because a stdio server is a local child process you already trust enough to spawn, not a remote service being accessed over a network. That's exactly the gap the inputs mechanism fills: a secure way to get a credential from you into that local process's environment without writing it to disk in the clear. (A remote, HTTP-transport MCP server is a different case, with its own real authorization flow — see the next lesson.)
| Approach | Where the key lives | Risk |
|---|---|---|
Hardcoded in mcp.json |
Plaintext in a file likely tracked by git | High — leaks on commit, sync, or screen share |
inputs + ${input:id} |
VS Code's secure storage, prompted once | Low — file itself never contains the secret |
Shell env var set outside the file (e.g. .zshrc/.bashrc) |
Your shell environment, referenced by name | Low, but shared with every process you launch from that shell |
Practical Challenge
Take any MCP server config where you currently hardcode a key in env, and convert it to use an inputs entry with "password": true instead. Confirm the raw key no longer appears anywhere in the committed file.
Concept Check
Sources & Further Reading
- MCP configuration reference — official VS Code docs for the
inputsarray and${input:id}syntax used to avoid hardcoding secrets. - MCP Specification — Authorization — the official spec section stating that STDIO-transport implementations should retrieve credentials from the environment rather than use the OAuth-based flow defined for HTTP transports.
AI