MCP Security in VS Code: Trusting Servers Safely
VS Code gives you four places to control an MCP server: where it is configured, the trust decision before it starts, the approval before each tool call, and the policies an organisation can set. What each one does, and the settings that quietly switch them off.
Updated 7 min read
VS Code protects you from an MCP server at four points. Where the server is configured decides who added it and who reviews it. A trust decision comes before a server first starts, and again whenever its configuration changes. An approval comes before each tool call in agent mode, unless you have told VS Code to stop asking. And an organisation can set policies that limit which servers may run at all. Most problems come from the settings that remove a check for convenience: trusting a whole workspace, approving a tool for ever, or switching on global auto-approve. This guide goes through each point as the VS Code documentation describes it today.
For practices that apply to any MCP client, see MCP security best practices for teams; for what can go wrong in the first place, MCP security risks. This piece is only about VS Code and Copilot.
Where servers are configured
VS Code reads MCP servers from two kinds of place, and they carry different risks.
- The workspace:
.vscode/mcp.json, with a top-levelserversobject, or a portable.mcp.jsonat the root of the project, withmcpServers. These are files in the repository, so they arrive with a clone and change with a pull. - Your user profile: run “MCP: Open User Configuration” for an
mcp.jsonwhose servers are available in every workspace you open. - For Copilot sessions, the Agent Host (opens in a new tab) also reads a user-level
~/.copilot/mcp-config.json, alongside the workspace.mcp.json. It does not read.vscode/mcp.jsonitself; VS Code forwards the servers configured there, except those that need interactive input such as${input:...}variables.
Two more routes are easy to miss. With chat.mcp.discovery.enabled, VS Code can discover servers configured in other apps, such as Claude Desktop, Cursor and Devin Desktop (formerly Windsurf). And Settings Sync can sync MCP servers between machines if you tick them. Both are convenient; both mean a server can appear in your editor without you adding it there. Know whether they are on.
GitHub’s own guidance (opens in a new tab) adds a practical rule: use one location per server, because the same server in both places can conflict. For a team, the workspace file is usually the right place for shared servers, because a change to it is a change to the repository and goes through review like any other.
The trust decision before a server starts
The first time a server starts, and whenever its configuration changes, VS Code shows a dialog asking whether you trust it, with a link to review the configuration. Read that configuration. For a local server it is the command VS Code is about to run on your machine, and the documentation (opens in a new tab) is blunt that local MCP servers can run arbitrary code: add them only from trusted sources and review the publisher and configuration before starting one.
The detail to know is how this interacts with Workspace Trust. Servers in the workspace’s .vscode/mcp.json or root .mcp.json inherit it: once you trust a folder, its servers can start without a separate MCP trust prompt. In Restricted Mode, workspace MCP configuration is blocked and those servers do not start. So the question “do you trust the authors of the files in this folder?” is also, now, “may the commands in its MCP file run?”. Open unfamiliar repositories in Restricted Mode until you have read them.
If you trusted something you should not have, “MCP: Reset Trust” clears the MCP trust decisions. It does not change Workspace Trust, which you reset separately.
Approving tool calls in agent mode
By default, an agent’s tool call that is not already auto-approved asks for your confirmation. When it asks, you can approve that one use, or grant approval for the session, for the workspace, or for all future calls. The wider the grant, the less you see.
VS Code separates two approvals that sound alike. Pre-approval lets a tool run without the confirmation dialog. Post-approval lets its result go into the chat without your review. The second matters for MCP, because tool results are text the model reads, and text from outside is where prompt injection arrives. The same split exists for fetched URLs: contacting a URL and adding its content to the chat are approved separately.
- Review what you have granted with “Chat: Manage Tool Approval”.
- Clear every saved approval with “Chat: Reset Tool Confirmations”.
- Use
chat.tools.eligibleForAutoApprovalto mark tools that must always ask; a tool set tofalsethere always needs manual approval. - Use the Configure Tools button in the chat input to switch individual tools off altogether. A tool the agent cannot see is one it cannot call.
Auto-approve, and what it costs
The permissions picker (opens in a new tab) in the chat input offers Manual (the default, which follows your approval settings), an experimental Assisted mode in which an LLM judge assesses each call and asks you about the ones it does not approve, and Allow all, which runs every tool call without confirmation. Autopilot is an agent mode rather than a permission level: it approves all tools and answers blocking questions itself. The setting chat.tools.global.autoApprove turns off confirmation in every workspace, and typing /yolo or /autoApprove does the same for one session.
The documentation’s own warnings are the right ones: use Allow all and Autopilot only when you trust the workspace and understand the implications, and global auto-approval removes prompts everywhere. The practical risk is simple. With confirmations off, an instruction hidden in a web page, an issue or a tool result can become a tool call before you see it. The documentation also says terminal command auto-approval is a best-effort convenience, not a security boundary, because commands can be written to slip past pattern rules.
{
"chat.tools.global.autoApprove": false
}A sensible middle ground: approve read-only tools for the workspace, keep anything that writes, deletes, sends or spends on ask, and never approve results from servers that fetch outside content without reading them.
Secrets: input variables, not literals
A committed mcp.json is read by everyone with the repository, so an API key typed into it is published. VS Code’s answer is input variables (opens in a new tab): declare an input, reference it as ${input:id}, and VS Code prompts for the value when the server first starts and stores it securely for later. Set password to true so it is hidden as you type. An envFile is the other option, as long as the file itself is not committed.
{
"inputs": [
{
"type": "promptString",
"id": "search-key",
"description": "Search API key",
"password": true
}
],
"servers": {
"search": {
"type": "stdio",
"command": "npx",
"args": ["-y", "example-search-server"],
"env": { "SEARCH_API_KEY": "${input:search-key}" }
}
}
}One catch: VS Code does not forward a server that needs an ${input:...} variable to Agent Host sessions, so that server is available in a Local session only. For remote servers, a browser sign-in is better still: nothing is stored in the file at all, and each person who opens the repository signs in as themselves.
Sandboxing local servers
On macOS and Linux a stdio server can run in a sandbox with sandboxEnabled set to true, and a top-level sandbox object restricts where it may write (filesystem.allowWrite) and which domains it may reach (network.allowedDomains). Note the trade-off the documentation states: with sandboxing on, the server’s tool calls are auto-approved, on the basis that the sandbox is the control. Keep the allowed paths and domains tight if you rely on it.
What an organisation can enforce
For Copilot Business and Copilot Enterprise, GitHub’s “MCP servers in Copilot” policy decides whether MCP can be used at all, and it is disabled by default. It does not govern Copilot Free or individual paid plans. Administrators can also point Copilot at an internal MCP registry (opens in a new tab) and choose between allowing all servers or registry only.
On the VS Code side, the enterprise AI settings documentation (opens in a new tab) lists policies that can be deployed through Windows Group Policy, macOS managed preferences or a managed-settings.json file:
- An MCP access policy with three values: any source, the configured registry only, or MCP switched off.
- Allowed and denied server lists that match a server by its configured name, remote URL or local command; only allowed servers can run, and denied ones are always blocked.
- A policy that disables auto-approval, hiding the Assisted and Allow all options.
- A policy for which tools are eligible for auto-approval, so chosen tools always ask.
The server has to hold the line too
Everything above happens in the editor, on one person’s machine. It decides whether a call is made. It cannot decide what the call is allowed to do once it arrives, and a colleague with different settings, or a different client, makes calls too. The server should check permission on every call, regardless of the client.
That is how fenbs works when Copilot connects to it: the workspace file holds only a URL, each person signs in through the browser, and the token is narrowed by the scopes they tick (read, write, comment) and capped by their role on the board, checked on every call. Every change is recorded in History with the assistant’s name and the person it acted for, and revoking a token in Settings stops it at once without signing anyone out. The steps are on the GitHub Copilot integration page.
Related
How scopes and roles combine: assistant tokens and scopes. Choosing the smallest role for a connection: how to give an AI agent access to your project board. The other clients: Cursor and Claude Code.