Skip to content

Enforce the enterprise MCP allow/deny policy on the agent-host path - #333291

Draft
joshspicer wants to merge 2 commits into
mainfrom
joshspicer/mcp-policy-agent-host
Draft

Enforce the enterprise MCP allow/deny policy on the agent-host path#333291
joshspicer wants to merge 2 commits into
mainfrom
joshspicer/mcp-policy-agent-host

Conversation

@joshspicer

@joshspicer joshspicer commented Aug 29, 2026

Copy link
Copy Markdown
Member

Fixes #328241

The bug

ChatAllowedMcpServers / ChatDeniedMcpServers are enforced in McpServer (_policyBlock / _evaluatePolicy), which governs MCP servers VS Code launches itself. A delegated agent-host session never goes through that code.

Instead, resolveCustomizationRefs -> collectNonPluginMcpServers projects eligible servers into a synthetic synced plugin's .mcp.json and hands it to the agent host, which is a separate process that launches the server on its own. Nothing on that path consulted the allow/deny policy, so a server blocked for the local agent connected normally in a delegated session.

This matches the customer report exactly: with an allowlist of ["npx","--yes","--registry","..."], deepwiki (type: http) was blocked for the local agent but connected and answered read_wiki_structure in a delegated session. Developer: Policy Diagnostics reported the policy as applied because it was applied - just not on this path.

The fix

Evaluate the policy in resolveMcpServerForAgentHostDelivery, ahead of every delivery branch, so client forwarding, plugin-contributed servers and provider built-ins are all gated. A blocked server becomes NotDelivered with a new BlockedByPolicy reason, so it is both withheld from the bundle and reported honestly in the UI rather than being shown as forwarded.

Details worth reviewing:

  • The check runs against the resolved configuration that would actually be forwarded, so it covers http and stdio identically and matches the same fields the local path matches.
  • It fails closed on unverifiable input: when an allow verdict relied on a URL or command still carrying ${...}, the server is re-checked with that field dropped and blocked unless it is still allowed on its name alone. So a serverUrl wildcard that merely matched the literal ${...} text does not grant access, while an allowlist entry matching by serverName is honoured (the URL never affected that verdict). McpServer can defer this until launch resolves; here, forwarding is the last decision point.
  • Both AgentCustomizationScope and AgentHostMcpServerSupportScope now recompute on onDidChangeAllowedMcpServers, so a newly blocked server is withdrawn instead of lingering for the life of the session.
  • The identity derivation moves into a shared mcpServerIdentityFromConfiguration, used by AllowedMcpServersService and by this path, so the two enforcement points cannot drift.

Explicitly out of scope

Servers the agent host discovers itself — workspace-root .mcp.json (AgentHostMcpServerDelivery.RuntimeDiscovered) — are unaffected. The client never forwards those, and the agent host has no access to chat.mcp.allowedServers (the setting declares no agentHostSync, and allowedMcpServers is not part of IAgentHostManagedSettingsPermissions). Closing that remaining gap means plumbing the matchers to the host and gating per provider, which is a separate change and partly runtime-side.

One further limit worth calling out: for ProviderBuiltIn (a provider's own bundled GitHub MCP server) the client can now correctly report the block, but it cannot stop the provider from starting its own built-in server — that also needs host-side support.

Tests

New resolveCustomizationRefs - enterprise MCP policy suite, driving the real AllowedMcpServersService off configuration rather than a stub, covering the reported allowlist:

  • http server blocked by the allowlist is not forwarded (the deepwiki repro)
  • stdio server blocked by the allowlist is not forwarded
  • a server matching the allowlist is still forwarded
  • http and stdio servers blocked by the denylist are not forwarded
  • with no allow/deny list configured, every server is still forwarded

All three blocking tests were confirmed to fail against the unpatched code.

Validation

  • npm run typecheck-client - clean for all touched files
  • npm run valid-layers-check - no violations
  • ./scripts/test.sh --grep "resolveCustomizationRefs" - 33 passing
  • ./scripts/test.sh --grep "MCP|Mcp|mcp" - 722 passing
  • ./scripts/test.sh --grep "agentHost" - 199 passing

`ChatAllowedMcpServers` / `ChatDeniedMcpServers` were enforced only in
`McpServer`, which governs servers VS Code launches itself. A delegated
agent-host session never goes through that code: eligible servers are
projected into a synthetic synced plugin and handed to a separate process
that launches them on its own, so a blocked server connected and served
tools there while being correctly blocked for the local agent.

Evaluate the policy in `resolveMcpServerForAgentHostDelivery`, the single
choke point shared by the forwarding path and the support assessment, so
that a blocked server is both withheld from the bundle and reported
honestly in the UI. The check runs against the resolved configuration that
would actually be forwarded, and covers `http` and `stdio` servers alike; a
configuration whose URL or command still carries unresolved `${...}`
variables cannot match an allow entry and so fails closed.

Both scopes now also recompute when the policy changes, so a newly blocked
server is withdrawn instead of lingering for the life of the session.

The identity derivation shared by every enforcement path moves to
`mcpServerIdentityFromConfiguration` so the delegated path cannot drift
from the local one.

Host-discovered workspace `.mcp.json` servers remain out of scope: the
agent host finds those itself and never consults the client.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings August 29, 2026 00:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

Review tier: Balanced
Findings: 1 High severity · 1 Medium severity · 3 Low severity

New issues introduced by this change (5)
Severity Finding
High severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostMcpServerSupport.ts — This gate is bypassed by the earlier AgentPlugin and ProviderBuiltIn returns (lines 239–245). A…
Medium severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostMcpServerSupport.ts — This does not provide the documented fail-closed behavior for unresolved URLs or commands.…
Low severity src/​vs/​workbench/​contrib/​chat/​test/​browser/​agentSessions/​resolveCustomizationRefs.test.ts — The resolver-specific JSDoc now attaches to makeAllowedMcpServersService, where it describes…
Low severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostMcpServerSupport.ts — Condense this method-body comment to one line; repository guidance caps inline comments at one…
Low severity src/​vs/​workbench/​contrib/​chat/​browser/​agentSessions/​agentHost/​agentHostActiveClientService.ts — Condense this method-body comment to one line; repository guidance caps inline comments at one line.
What changed in this PR

Enforces enterprise MCP allow/deny policies for MCP servers forwarded to delegated Agent Host sessions.

Changes:

  • Applies policy matching to resolved forwarded configurations.
  • Refreshes Agent Host customizations when policy changes.
  • Adds shared identity derivation and policy tests.
File Description
allowedMcpServers.ts Adds shared MCP identity derivation.
allowedMcpServersService.ts Reuses shared identity logic.
agentHostMcpServerSupport.ts Adds policy-based delivery blocking.
agentHostMcpServerSupportScope.ts Refreshes support assessments on policy changes.
agentHostLocalCustomizations.ts Filters blocked forwarded servers.
agentHostActiveClientService.ts Republishes customizations after policy changes.
resolveCustomizationRefs.test.ts Adds enterprise policy tests.
agentHostMcpServerSupport.test.ts Updates support test dependencies.
agentHostClientTools.test.ts Adds policy-service test stubs.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

…ables

Address review feedback on the MCP policy enforcement.

The policy check ran after the `AgentPlugin` and `ProviderBuiltIn` early
returns, so a denied server contributed by a client-synced plugin, or the
provider's built-in GitHub replacement, stayed available and was reported as
supported even though the local `McpServer` path blocks it. Move the check
ahead of both branches so every handoff path is gated, not just client
forwarding. A plugin syncs as a directory rather than a per-server config, so
a denied child is withheld by marking it disabled in `childEnablement`, which
the host maps to `disabledMcpServers`.

The documented fail-closed behavior was also not implemented: `isServerAllowed`
still returns `true` for a matching `serverName`, or for a `serverUrl` wildcard
that happens to match the literal `${...}` text. Forwarding is the last point
at which the client can refuse -- unlike `McpServer`, which re-evaluates once
the launch resolves -- so an allow verdict that relied on unresolved text is
now re-checked with that field dropped and blocked unless the server is still
allowed on its name alone. An allowlist entry matching by name is honoured,
since the URL never affected that verdict.

Also derive the policy identity from the launch the same way `McpServer` does,
so a definition without a usable command still matches `serverName` rules.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ChatAllowedMcpServers enterprise policy is not enforced for agent-host (delegated) chat sessions

2 participants