Passing env vars to Cursor MCP servers
Cursor doesn't load .env automatically for MCP servers — something I assumed it would, and every new MCP install I hit the same wall: the server starts with zero env and complains about missing keys. It's a real gap in the product, and there's no one right fix. Four options, each with tradeoffs.
1. Hardcode in mcp.json
The env field on each server takes literal strings — no ${VAR} interpolation, no .env loading. If .cursor/ is in .gitignore (it is by default), the secrets never leave your machine.
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..." }
}
}
}This is what I ended up using for every personal workspace. Simplest, zero indirection, no magic.
2. Inherit from .zshrc / login shell
Cursor launches with your shell's environment, so anything exported in ~/.zshrc or via launchctl setenv is visible to MCP servers. Good when you want one key (OPENAI_API_KEY, ANTHROPIC_API_KEY) available across every project. Bad when you want per-project isolation — any repo can read any key.
3. Shell wrapper in the app bundle
The same trick I use for two Cursor profiles — replace Cursor.app/Contents/MacOS/Cursor with a shell script that sources a profile-scoped env file before execing the real binary:
#!/bin/sh
set -a
. "$HOME/.cursor_work.env"
set +a
exec "/Applications/Cursor.app/Contents/MacOS/Cursor.real" "$@"Clean isolation between work and personal Cursor profiles — each bundle sources its own env file. Costs a Cursor restart every time you edit the file, and the wrapper has to be restored after Cursor auto-updates.
4. Cursor Secrets
In Settings → Secrets. Only available for Cloud Agent / remote sessions. Local MCP processes don't see them. Nice in theory, not a solution for local dev.
What I actually do
For solo work: hardcode. .cursor/ isn't committed, so there's no leak risk, and hardcoding removes all indirection.
For a team repo where you want to commit mcp.json: commit the template without secrets (.cursor/mcp.json), add .env.cursor.example listing required vars, and document that each developer exports them in .zshrc or their own shell profile. Effectively option 2 with a written contract.
The gap is real — Cursor doesn't ship a native .env loader for MCP — so until it does, pick the least magical option that fits your trust boundary. For me that's hardcode; for a team, it's inherited shell env with a documented list.