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-level servers object, or a portable .mcp.json at the root of the project, with mcpServers. 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.json whose 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.json itself; 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.eligibleForAutoApproval to mark tools that must always ask; a tool set to false there 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.

settings.json: keep global auto-approve off
{
  "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.

.vscode/mcp.json
{
  "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.

Questions people ask.

Where is mcp.json in VS Code?

Either in the workspace, as .vscode/mcp.json or a portable .mcp.json at the project root, or in your user profile, opened with the MCP: Open User Configuration command. Workspace servers travel with the repository; user servers are available in every workspace you open.

Does trusting a workspace let its MCP servers run?

Yes. According to the VS Code documentation, workspace MCP servers inherit Workspace Trust, so once a folder is trusted its servers can start without a separate MCP trust prompt. In Restricted Mode they do not start. Open unfamiliar repositories in Restricted Mode until you have read their MCP configuration.

Is it safe to turn on auto-approve for MCP tools?

For read-only tools you know, approving them for a workspace is reasonable. Global auto-approve and Allow all remove every confirmation, so an instruction hidden in fetched content or a tool result can trigger a tool call before you see it. Keep tools that write, delete, send or spend on ask.

How do I keep API keys out of a committed mcp.json?

Use input variables: declare a promptString input with password set to true and reference it as input:id in the server configuration. VS Code asks for the value when the server first starts and stores it securely. An env file that is not committed also works, and for remote servers a browser sign-in avoids storing a key at all.

Start with one thing.

There is nothing to set up first. Write one line and you’ve started.