Follow
← all articles

GitHub Copilot Developer Playbook · Part 1.2

code-agents · 10 min read

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.

M

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:

ClientMechanism
VS CodeRepo-shareable .vscode/mcp.json, or personal settings.json
Visual StudioConfigure a server in Copilot Chat (Server ID, type, URL)
JetBrainsEdits an mcp.json file directly
XcodeGUI-mediated (MCP tab, then Edit Config, which opens mcp.json underneath)
EclipseA 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.

SurfaceToolsResources / promptsRemote OAuth
IDE-level MCP (client-side)YesYes (where the server provides them)Yes (growing, editor-specific)
Copilot cloud agentYesNoNo
Copilot code reviewYesNoNo

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:

MechanismOwner / levelGoverned surfacesWhat it controlsKey limitations
"MCP servers in Copilot" policyOrganization or enterprise (Business/Enterprise plans only)Wherever GitHub's own MCP support is generally availableWhether MCP can be used with Copilot at allDoes not control the GitHub MCP server's use inside third-party host apps (Cursor, Windsurf, Claude)
MCP registry allow-listOrganization or enterpriseSupported IDEs and Copilot CLI. Confirmed not to apply to cloud agent or code reviewWhich 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 configurationRepositoryBoth cloud agent and code reviewWhich specific tools a repo-configured server exposes: a tools field is a required keyConfigured 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: listsRepository, organization, or enterprise (wherever that agent's profile is defined)Only that one agentRestricts that specific agent's own tool accessPer-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:

SettingValues
Enable firewallEnabled / Disabled / Let repositories decide
Recommended allowlistEnabled / Disabled / Let repositories decide
Allow repository custom rulesNo enumerated values documented
Organization-level custom allowlistNo enumerated values documented
Repository-level custom allowlistNo 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.