GitHub Copilot Developer Playbook · Part 1.2
Copilot just hired a subcontractor - your firewall has no idea
MCP reaches outside systems through three primary configuration surfaces. Four controls govern access, but GitHub's agent firewall does not cover MCP traffic.
Marcin
Java since 2007, .NET since 2010 · Azure · lately deep in LLMs
This gap can show up on teams where "who's allowed past the hiring desk" is taken seriously for good reason: banking, pharma, anywhere with an actual compliance department. Someone wires up an MCP server over the weekend so Copilot can pull real ticket data instead of guessing, tests it in their own editor, gets genuinely excited, and then on Monday discovers the thing that worked all weekend doesn't exist anywhere the cloud agent can see. Not broken. Just never introduced to each other.
Here's the mental model that fixes this, and it's not that complicated once you see it: MCP is Copilot hiring a subcontractor. Your repo-wide instructions and path-specific files are the crew that's always on-site, permanently briefed, working off the same blueprint every day. An MCP server is the specialist you call in when the job needs something the regular crew doesn't carry: someone who shows up with their own toolbox, does one specific job, and leaves. You don't rewrite your onboarding doc every time you need an electrician. You call one in.
Which is a nice pitch until you find out how many separate hiring desks are handing out access badges. And this is the part that actually matters: which one the firewall isn't watching.
Two Completely Different Hiring Desks
There isn't one place you configure MCP. And if you're picturing two, there's a third genuine one hiding in plain sight. It both borrows from the other two and keeps its own separate ledger of who's hired.
Desk one is your own editor: VS Code, JetBrains, Xcode, whatever you use day to day. This can be purely personal (a subcontractor only you know about, settings.json) or shared with the whole crew (.vscode/mcp.json, checked into the repo so anyone who opens the project sees the same list). Every editor handles this slightly differently. Nobody standardized this, and pretending otherwise will just get you confused the first time you switch editors. The actual breakdown, since "it varies" isn't very actionable on its own:
| Client | Mechanism |
|---|---|
| VS Code | Repo-shareable .vscode/mcp.json, or personal settings.json |
| Visual Studio | Configure a server in Copilot Chat (Server ID, type, URL) |
| JetBrains | Edits an mcp.json file directly |
| Xcode | GUI-mediated (MCP tab, then Edit Config, which opens mcp.json underneath) |
| Eclipse | A distinct "Server Configurations" panel, not named mcp.json file editing |
Two of these are worth seeing as actual files, not just a table row. Here's VS Code, pointed at a small local server that fetches web content, the kind of thing you'd commit to .vscode/mcp.json so the whole team gets it, not just you. Worth pinning down what local means here, because it isn't your laptop: it's relative to whatever is hosting the MCP client at the time, which might be your editor or might be the cloud agent's own execution environment:
{
"servers": {
"fetch": {
"command": "uvx",
"args": ["mcp-server-fetch"]
}
}
}
And here's JetBrains pointed at a remote server instead, with a PAT passed as a raw Authorization header rather than through an OAuth flow:
{
"servers": {
"github": {
"url": "https://api.githubcopilot.com/mcp/",
"requestInit": {
"headers": {
"Authorization": "Bearer YOUR_GITHUB_PAT"
}
}
}
}
}
Worth noticing: the top-level key here is servers, not mcpServers like desk two's config further down. Different desk, different paperwork. The two schemas aren't interchangeable just because they're doing conceptually the same job.
One more habit worth stealing: once you've added a server, don't just trust it's there. In VS Code, run "MCP: List Servers" from the command palette and actually look. Cheaper than finding out mid-task that the subcontractor never showed up.
Desk two is GitHub.com itself: JSON sitting in the repository's own settings, configured by a repo admin. This is the one that actually matters for anything automated. It's what both the cloud agent and code review read. Your personal settings.json subcontractor doesn't exist as far as either of those is concerned. This is the gap that bit the person in my opening story: great subcontractor, wrong hiring desk.
And then there's the GitHub Copilot desktop app, which is its own thing entirely (not GitHub Mobile, a real desktop app), and it runs its own third, smaller hiring desk. It automatically sees whoever's already approved for your repos or Copilot CLI, but it also lets you add more subcontractors directly in its own settings. Genuinely a third door, not just a window into the other two.
(There's a fourth door too, technically: custom agents can list their own MCP servers directly in their profile. But that's specialized enough it deserves its own conversation, not a footnote here.)
What the Subcontractor Is Actually Allowed to Touch
Here's a limitation that gets stated wrong constantly, usually as a blanket "MCP doesn't support X." It doesn't work like that. In your own editor, a subcontractor can bring their full toolbox: tools, resources, prompts, the works, and OAuth-authenticated remote servers too, with support for that growing across VS Code, Visual Studio, JetBrains, Xcode, Eclipse, Cursor, and Windsurf. But the cloud agent and code review are stricter clients: tools only. No resources, no prompts, no OAuth-authenticated remote servers. The three surfaces side by side, so there's no ambiguity about which "MCP" you're talking about. The GitHub Copilot app isn't a row here on purpose: its own documentation doesn't say where its tools, resources, prompts, and OAuth boundaries land, so adding a row would mean guessing.
| Surface | Tools | Resources / prompts | Remote OAuth |
|---|---|---|---|
| IDE-level MCP (client-side) | Yes | Yes (where the server provides them) | Yes (growing, editor-specific) |
| Copilot cloud agent | Yes | No | No |
| Copilot code review | Yes | No | No |
Say "MCP doesn't support X" without naming which surface you mean, and you've just told half your team something that's only true for the other half.
Four People Hold Four Different Keys
Governance here isn't one lock; it's four, and they don't all open the same door. An org/enterprise policy decides whether MCP gets used at all, but it explicitly doesn't reach the GitHub MCP server running inside a third-party app like Cursor or Claude. That's a real, documented gap. A registry allow-list (still labeled public preview, worth remembering) controls which subcontractors are even on the approved-vendor list for IDEs and CLI, and (confirmed directly against GitHub's own support table) that allow-list doesn't reach the cloud agent or code review at all. Repository-level JSON config is the one with actual teeth: a required tools field that says exactly which tools a given subcontractor is allowed to touch, not a vague "sure, let them in." And a custom agent's own tool list is narrower still: it restricts that one agent, not a company-wide policy that follows every agent around.
Four keyholders, four different doors, and knowing which one you're actually holding matters more than knowing that "governance exists." The compressed version, for whenever you need to check which lock you're dealing with:
| Mechanism | Owner / level | Governed surfaces | What it controls | Key limitations |
|---|---|---|---|---|
| "MCP servers in Copilot" policy | Organization or enterprise (Business/Enterprise plans only) | Wherever GitHub's own MCP support is generally available | Whether MCP can be used with Copilot at all | Does not control the GitHub MCP server's use inside third-party host apps (Cursor, Windsurf, Claude) |
| MCP registry allow-list | Organization or enterprise | Supported IDEs and Copilot CLI. Confirmed not to apply to cloud agent or code review | Which servers are discoverable/usable: "Allow all" or "Registry only" | Public preview. Enforcement is server name/ID matching only, bypassable by editing config files, and strict prevention of non-registry servers "is not yet available" |
| Repository MCP JSON configuration | Repository | Both cloud agent and code review | Which specific tools a repo-configured server exposes: a tools field is a required key | Configured tools run autonomously, no approval prompt. Code review's own MCP support is itself public preview, and a separate, default-enabled toggle can disable MCP for code review alone |
Custom-agent tools: lists | Repository, organization, or enterprise (wherever that agent's profile is defined) | Only that one agent | Restricts that specific agent's own tool access | Per-profile only, not a cross-agent policy |
And since "a required tools field" is an abstract thing to picture until you've seen one, here's what a repo admin actually writes to hire a subcontractor with a narrow, specific set of permissions instead of a blank check:
{
"mcpServers": {
"github": {
"type": "local",
"command": "docker",
"args": ["run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "ghcr.io/github/github-mcp-server"],
"tools": ["list_issues", "issue_read", "search_code"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "$COPILOT_MCP_GITHUB_PERSONAL_ACCESS_TOKEN"
}
}
}
}
That tools array is the whole ballgame: this subcontractor can list issues, read one, and search code, and that is the entire list. It cannot write, cannot delete, and cannot wander into parts of the building it wasn't hired for. The credential comes in through an Agents secret, prefixed COPILOT_MCP_, referenced here rather than typed in plaintext. And notice what isn't in this file anywhere: an approval step. The moment this is saved, that's the deal. Nobody signs off per use.
Here's the Part Nobody's Watching
If you've set up GitHub's agent firewall, there's a very natural assumption to make: it's watching the door. It isn't. Direct from GitHub's own documentation:
"The firewall only applies to processes started by the agent via its Bash tool. It does not apply to Model Context Protocol (MCP) servers or processes started in configured Copilot setup steps."
Read that again if you skimmed it, because the implication is easy to miss. The firewall watches one specific door: Bash processes the agent starts, inside the GitHub Actions appliance. And even that narrower promise comes with GitHub's own caveat that "sophisticated attacks" can still get through. MCP traffic walks in through a completely different, unwatched door. Everything the firewall actually controls, for the record, none of which touches MCP:
| Setting | Values |
|---|---|
| Enable firewall | Enabled / Disabled / Let repositories decide |
| Recommended allowlist | Enabled / Disabled / Let repositories decide |
| Allow repository custom rules | No enumerated values documented |
| Organization-level custom allowlist | No enumerated values documented |
| Repository-level custom allowlist | No enumerated values documented |
And, to be fair to the thing, what the firewall does watch, it watches across both the cloud agent and code review, not just the cloud agent. It's a real control. It's just a control over a different door than the one MCP walks through.
If you've been treating "we have the firewall on" as your MCP security story, you don't have one. You have a security story for something else entirely.
And here's the second half of the surprise: once a subcontractor's configured (like the one in that JSON block above), they don't wait for a supervisor to say go. GitHub's own words: "Copilot will be able to use the tools provided by the server autonomously, and will not ask for your approval before using them." No knock, no check-in. That's true whether it's the cloud agent or code review pulling the trigger, because they share the same repo-level configuration. Combine that with two real risks worth taking seriously: two subcontractors both named search can get their signals crossed and the wrong one gets called (a tool-name collision), and anything a tool hands back gets fed straight to the model, which means a compromised or careless MCP server can plant instructions in what looks like ordinary data, with no approval gate to catch it and no firewall watching that door either.
What I'd Actually Do With This
- Treat "the firewall is on" and "MCP is secure" as two unrelated sentences. They are.
- Whoever writes the repository's MCP JSON should list specific tools, like the example above, not a wildcard. If a subcontractor only needs to read tickets, don't also hand them write access because it was easier to type
"*". - If a server needs a credential, it goes in as an Agents secret prefixed
COPILOT_MCP_, not a generic Actions secret. Different drawer, different lock. (Someone will still put it in the wrong drawer. It will be a Friday.) - Don't assume your personal editor config and your team's repo config are the same list. They're two different hiring desks with two different ledgers, and only one of them is what the cloud agent actually reads.
Writing about Copilot's plumbing, I keep noticing the same pattern underneath every surprise in this post: something that looks unified from a distance turns out, once you actually check, to be several separate systems that happen to share a name. MCP governance isn't one policy; it's four keyholders who've never met each other. The firewall isn't a perimeter around your whole house. It's a camera, and it's pointed at a different door than the one MCP uses. None of this is a knock on GitHub's design. It's what happens when a fast-moving feature grows new capabilities faster than any one mental model can keep up. The fix isn't cynicism. It's just checking which door you're actually standing at before you assume someone's watching it.