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
I've watched this pattern play out more than once, especially 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. MCP servers are 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
Here's the thing that trips people up first: there isn't one place you configure MCP, and if you're picturing two, there's actually 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. Here's 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:
{
"inputs": [
{
"type": "promptString"
}
],
"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, ones nobody else sees. 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. Here's the whole thing side by side, so there's no ambiguity about which "MCP" you're talking about:
| 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." Here's 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 |
| 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 |
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. Nothing else. No writing, no deleting, no wandering into parts of the building they weren'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
Here's the surprise that made me want to write this post at all. 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. Here's 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 | present, no separate value list |
| Organization-level custom allowlist | present, no separate value list |
| Repository-level custom allowlist | present, no separate value list |
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. No approval gate to catch it. No firewall watching that door either.
// Not the door you assumed was locked. The other one.
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. - 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.
I keep coming back to the same thought writing about Copilot's plumbing: almost every surprise here is the same surprise wearing a different hat. Something that looks unified from a distance turns out, once you actually check, to be several separate systems that happen to share a name. Instructions weren't one precedence rule; they were two systems in a trenchcoat. 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 pointed at one specific door, and a very good one at that door specifically. 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.