API keys in mcp.json: how to keep secrets out of your Cursor MCP config (and out of git)
By Pritesh Ghodge, Senior Solutions Engineer (cybersecurity) · Last updated 14 November 2026
Scan your app free — check none of those keys ended up in your live site too.
Abbi Labs looks at your live site from the outside: secret keys in the JavaScript, Supabase tables readable with your public key, exposed .env and .git files, source maps, and security headers. No sign-up for the quick check.
No sign-up. Read-only public-resource and protocol metadata checks. No model prompts or tool execution. Scan only sites you own or are allowed to test.
Run on every deploy or use from Cursor / Claude Code via MCP →
Weekly AI-app security checklist
One short email a week: a practical check for apps built with Supabase, Next.js and AI tools (RLS, leaked keys, headers and more). Unsubscribe anytime.
Almost every MCP server README has the same snippet: a block for mcp.json with "API_KEY": "paste-your-key-here". You paste it, the agent can suddenly talk to your database or payment provider, and you move on. The problem is where that file lives. Cursor reads MCP servers from two places, and one of them is inside your project.
Where the keys usually hide
.cursor/mcp.jsonin your project. Cursor's docs suggest it for project-specific tools. It's a normal file in your repo, sogit add .picks it up.~/.cursor/mcp.jsonin your home folder. Global tools. Not in git, but readable by anything running as you.- Other agents' configs. Claude Code uses
.mcp.jsonin the project, VS Code uses.vscode/mcp.json. Same risk, same fix.
1. Find any real keys in your MCP configs
# MCP configs in this project and your home folder
ls .cursor/mcp.json ~/.cursor/mcp.json .mcp.json .vscode/mcp.json 2>/dev/null
# anything that looks like a real key (not an ${env:...} reference)
grep -nE "sk_(live|test)_|rk_live_|sk-[A-Za-z0-9_-]{20,}|service_role|eyJhbGci|ghp_|xox[bp]-" \
.cursor/mcp.json ~/.cursor/mcp.json .mcp.json .vscode/mcp.json 2>/dev/null
# was an MCP config ever committed?
git log --all --oneline -- .cursor/mcp.json .mcp.json .vscode/mcp.jsonA Supabase service_role key, a Stripe sk_live_, a GitHub token or an OpenAI key in that file is the one to worry about. If the last command prints anything, look at each commit with git show.
2. Move the keys to environment variables
Cursor supports interpolation in mcp.json: ${env:NAME} reads an environment variable and ${workspaceFolder} is the project root. For a local (stdio) server:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "mcp-server"],
"env": { "API_KEY": "${env:API_KEY}" }
}
}
}Or load a file with envFile (stdio servers only) and keep that file out of git:
"envFile": "${workspaceFolder}/.env.mcp"
# .gitignore
.env*For a remote (HTTP/SSE) server, envFile isn't supported. Set the variable in your shell profile and reference it in the header:
"headers": { "Authorization": "Bearer ${env:MY_SERVICE_TOKEN}" }3. Prefer OAuth over a pasted key
Many hosted MCP servers now support OAuth, so there's no long-lived key in any file at all. In Cursor a remote server is just a url; you sign in when prompted. Stripe is a current example: its local @stripe/mcp package is deprecated and the hosted server wants OAuth or an agent key. Details: Stripe MCP migration.
4. Give the agent a smaller key
Cursor's docs also say to limit API key permissions to the minimum required. Don't hand your coding agent the Supabase service_role key or a full Stripe secret key if a scoped or read-only one will do. Whatever key the agent holds, a prompt injection in a page or issue it reads can try to use.
5. Keep tool approval on
Cursor asks for approval before running MCP tools by default, and MCP follows the same Run Modes as terminal commands. Leave approval on for any server that can change live data or spend money, and look at the arguments before you click.
6. If a key was committed, roll it
Rotate it at the provider first (Stripe Dashboard, Supabase API settings, GitHub tokens, your AI provider's keys page). Only then clean up the file and history. A key that sat in a public repo for even a few minutes should be treated as copied. More on that: Supabase service_role key exposed: what to do.
7. Check the same key didn't ship in your app
The MCP config is dev tooling, but agents often copy the same key into .env while wiring up the app. If it got a NEXT_PUBLIC_ or VITE_ prefix, it's in the JavaScript every visitor downloads: why that happens. Open your live site, DevTools, Sources, search for the key's prefix, or run the free quick check above.
What an outside scan adds, and what it can't do
Abbi Labs can't see your mcp.json, your laptop or your repo, so steps 1 to 6 are on you. It checks what your live site serves: secret keys in the JavaScript, Supabase tables readable with your public key, exposed .env/.git files, source maps and security headers.
FAQ
Is it safe to put an API key in mcp.json?
It works, but it's a plain-text file. The project file .cursor/mcp.json sits inside your repo, so a normal git add . commits it. The global ~/.cursor/mcp.json stays on your machine but any tool or extension that can read your home folder can read it. Cursor's own docs say to use environment variables for secrets and never hardcode them.
How do I use environment variables in Cursor's mcp.json?
Cursor supports config interpolation in mcp.json: ${env:NAME} reads an environment variable, ${workspaceFolder} is the project root. Use it in env for local (stdio) servers, for example "API_KEY": "${env:API_KEY}", and in headers for remote servers, for example "Authorization": "Bearer ${env:MY_SERVICE_TOKEN}".
What is envFile in mcp.json?
For local stdio servers, envFile points Cursor at an environment file to load more variables, for example "${workspaceFolder}/.env". Make sure that file is in .gitignore. Cursor's docs say remote (HTTP/SSE) servers don't support envFile; set the variable in your shell profile or system environment and use ${env:NAME} instead.
Should I commit .cursor/mcp.json?
Sharing the server list with your team is fine and useful, as long as it only contains ${env:...} references and no real keys. If you're not sure, add it to .gitignore until you've checked it.
I already committed mcp.json with a key. What now?
Roll (rotate) the key at the provider first, then clean up. Deleting the file or rewriting history doesn't un-leak a key someone may already have copied, especially if the repo was ever public.
Does Abbi Labs check my mcp.json?
No. Your MCP config lives on your laptop or in your repo, not on your live site, so an outside scan can't see it. The free quick check looks at what your site serves: secret-looking keys in the JavaScript, Supabase tables readable with your public key, exposed .env and .git files, source maps and security headers.