An mcp.json example has one object that maps server names to how each server starts: command, args and env for a local server, or a url and headers for a remote one. Claude Code (.mcp.json) and Cursor (.cursor/mcp.json) put that object under mcpServers; VS Code (.vscode/mcp.json) puts it under servers. The examples below show a risky file first, then a safer version for each tool: exact versions, folder access limited to what the job needs, HTTPS for remote servers, and secrets referenced from the environment instead of typed in. Every key name comes from the vendor's documentation, listed under Sources.
A risky mcp.json example, and what is wrong with it
This file loads, works, and is the kind of thing that turns up on developer machines every day.
{
"mcpServers": {
"files": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/"]
},
"github": {
"command": "npx",
"args": ["-y", "some-github-mcp"],
"env": { "GITHUB_TOKEN": "ghp_paste-your-real-token-here" }
},
"notes": {
"type": "http",
"url": "http://notes.example.net/mcp"
}
}
} It has four problems. The filesystem server is given /, the whole disk; the MCP documentation warns that this server runs with your user account permissions and can do any file operation you can. Neither package is pinned, so npx -y fetches whatever was published last each time it starts, which is how one bad release of a package reaches every machine at once. The GitHub token is typed into the file, where it gets copied, synced and committed. And the remote server uses plain http on a host nobody on the team owns.
None of these is exotic. They map to the supply chain, secret exposure and over-broad access risks in the MCP specification's security best practices, and to several of the 12 checks in our MCP security checklist.
A safer .mcp.json example for Claude Code
Claude Code reads project servers from .mcp.json at the repository root. Entries with a command are local stdio servers; remote servers set type to "http". Claude Code expands ${VAR} and ${VAR:-default} in command, args, env, url and headers.
{
"mcpServers": {
"files": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem@1.2.3",
"/Users/me/projects/my-app/docs"
]
},
"github": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": { "Authorization": "Bearer ${GITHUB_MCP_TOKEN}" }
}
}
} The filesystem server now gets one project folder, given as an absolute path, which the MCP documentation recommends for this server. The package carries an exact version; 1.2.3 stands for whichever version your team reviewed. The token comes from the developer's environment. Two details from the docs are worth knowing. If a variable is unset and has no default, the server still loads with the literal text, and claude mcp list shows a warning. And Claude Code reads some of its own credential variables, such as ANTHROPIC_API_KEY, as empty in a remote server's URL and headers, which keeps them from being sent to that server.
A safer mcp.json example for Cursor
Cursor reads .cursor/mcp.json in a project and ~/.cursor/mcp.json for every project. It uses the same mcpServers key, but a different interpolation syntax: ${env:NAME} for environment variables and ${workspaceFolder} for the project root.
{
"mcpServers": {
"local-tools": {
"command": "python",
"args": ["${workspaceFolder}/tools/mcp_server.py"],
"env": { "API_KEY": "${env:API_KEY}" }
},
"remote-server": {
"url": "https://api.example.com/mcp",
"headers": { "Authorization": "Bearer ${env:MY_SERVICE_TOKEN}" }
}
}
} Cursor also supports envFile for local servers, pointing at a file such as ${workspaceFolder}/.env. Its docs note that envFile only works for stdio servers, so remote servers should take their tokens from the shell environment through interpolation. If you use an env file, keep it out of git.
A safer mcp.json example for VS Code and GitHub Copilot
VS Code's .vscode/mcp.json has a top-level servers object, an optional inputs array, and requires a type on every server: "stdio", "http" or "sse". Inputs let VS Code ask for a value when the server starts, and "password": true hides what you type.
{
"inputs": [
{
"type": "promptString",
"id": "api-token",
"description": "API token",
"password": true
}
],
"servers": {
"docs": {
"type": "stdio",
"command": "npx",
"args": ["-y", "some-docs-mcp@1.2.3"],
"env": { "DOCS_API_KEY": "${input:api-token}" }
},
"remote": {
"type": "http",
"url": "https://api.example.com/mcp",
"headers": { "Authorization": "Bearer ${input:api-token}" }
}
}
} Nobody has to type the token into a file, and the file is safe to commit. VS Code also supports envFile and cwd for stdio servers. On macOS and Linux it can sandbox local servers to limit their file and network access, configured with a sandbox section in the same file.
Risky vs safer: the differences side by side
| Field | Risky | Safer |
|---|---|---|
| Package | npx -y package | npx -y package@1.2.3, a version someone reviewed |
| File access | / or the home folder | One project folder, such as ./docs |
| Secrets | Token typed into env or headers | ${VAR}, ${env:VAR} or ${input:id} |
| Remote URL | http:// on an unknown host | https:// on the vendor's own domain |
| Transport | Local server listening on HTTP with no token | stdio for local servers, as the MCP spec recommends |
| Review | Edited directly, never reviewed | Changes go through a pull request |
What to check before you commit an mcp.json
A project file is shared by design, so a mistake in it is shared too. Before an mcp.json change is merged, a reviewer should be able to answer five questions from the diff alone.
First, who publishes each package, and does that match the name? A server called after a well-known company but published by someone else is the pattern behind every malicious MCP server reported so far. Second, is every package pinned to an exact version that someone has looked at? Third, does each server get only the folders, scopes and tokens its job needs? Fourth, does any value look like a real secret rather than a reference to one? Scanning the diff for strings that start like known token formats catches most slips. Fifth, is every remote URL HTTPS on a domain the team recognizes?
If the answer to any of these is unclear, the server can still be approved, but someone should decide that knowingly and say so in the pull request. That written decision is also what an auditor asks for when they want to know how MCP servers are approved. Look up any server you are unsure about on the NoFly list before approving it.
Copying an mcp.json example between tools
A common source of broken configs is moving an entry from one tool to another. Change mcpServers to servers when moving into VS Code, and add a type to every entry. Change the variable syntax: ${VAR} in Claude Code becomes ${env:VAR} in Cursor and an input or environment reference in VS Code. Check the path variables too, since ${workspaceFolder} means nothing to Claude Code. After moving an entry, restart the tool and confirm the server is listed: claude mcp list in Claude Code, the MCP section of Cursor's settings, and the server list in VS Code. A server that fails quietly is easy to miss, and one that loads with a literal placeholder instead of its token can fail in confusing ways.
The same server can also appear in several tools at once, because VS Code can be set to read the Cursor and Claude Desktop files. When you need to find MCP servers on developer machines, check every one of these files, not just the one you expect.
Next step: check the mcp.json example you already have
The safest mcp.json example is the one that matches what is actually on your team's machines. Look up any server in your file on the NoFly list, or use the MCP security scanner to read every config file across your org and score each server it finds.