[FEATURE] Server-level tool allowlists in .mcp.json, so one remote MCP server (e.g. an enterprise gateway) can be registered as several per-project servers

Status Open
Reported on v2.1.259
Maintainer reply None cached
Activity 0 comments · opened Sep 3, 2026

Preflight

  • [x] I have searched existing requests. The closest are #7328 (closed as completed), #39491 / #24000 (MCP profiles, closed as duplicate) and #30996 (closed as not planned). This request is deliberately narrower and addresses what those resolutions do not cover — see "Why the existing mechanisms don't close this" below.
  • [x] This is a single feature request.

Problem Statement

I want to declare several distinct local MCP servers that all point at the same single remote MCP endpoint, each exposing a different, explicitly chosen subset of that endpoint's tools — so that a given project only ever loads the tools that are useful to that project.

This is not a hypothetical tidiness concern. It is becoming the dominant enterprise deployment shape:

  • Enterprises buy N MCP-enabled SaaS products and put them all behind one MCP gateway (Kong, Solo/gloo, Azure APIM, IBM Context Forge, Docker MCP Gateway, internal gateways, …).
  • The gateway is the only endpoint an employee is allowed to configure — the individual upstream servers are not reachable directly, and the user cannot edit the gateway's own configuration.
  • So the single user-visible server exposes the union of every purchased product's tools: hundreds of tools, of which any given project needs maybe five.

The result is that a Jira-only project drags in Salesforce, ServiceNow, Confluence, Slack, an internal deploy API, etc. — every session, in every repo. Tool schemas clobber the project's context window, and tool selection accuracy degrades because the model has to discriminate among a large set of near-synonymous tools ("create_issue" in four different products).

The unit of configuration today is the server. But behind a gateway the server is no longer a meaningful unit — it is a bundle. I need to be able to cut it.

Proposed Solution

Allow a tool filter to be declared on the MCP server entry itself, in .mcp.json / claude mcp add, with allowlist semantics, and allow the same remote endpoint to be registered more than once under different names.

// .mcp.json  (project scope — checked in, travels with the repo)
{
  "mcpServers": {
    "jira": {
      "type": "http",
      "url": "https://mcp-gw.corp.example.com/mcp",
      "headers": { "Authorization": "Bearer ..." },
      "tools": ["jira_search", "jira_get_issue", "jira_comment"]
    },
    "deploy": {
      "type": "http",
      "url": "https://mcp-gw.corp.example.com/mcp",   // same endpoint, different view
      "headers": { "Authorization": "Bearer ..." },
      "tools": ["deploy_status", "deploy_rollback"]
    }
  }
}

Key properties I'm asking for:

  1. **Allowlist, and only an allowlist.** A flat "tools": [...] array, deliberately with no deny counterpart and no nested object — the shape itself should make it unambiguous that the list is closed and exhaustive. A tool the gateway adds tomorrow does not silently appear in my context. A denylist cannot express "only these" against an upstream that someone else grows.
  2. Filtering happens at the client↔server boundary. Tools outside the allowlist are dropped from tools/list handling and never reach the model's tool set at all — no schema tokens, no deferred-tool entry, nothing.
  3. The same URL may be registered under multiple names, each with its own filter and its own mcp__<name>__ namespace. This is what makes "one gateway → several purpose-built local servers" possible.
  4. Config lives with the project (.mcp.json at project scope), so the filter is versioned alongside the code that needs it, and reviewable.
  5. Glob support ("tools": ["jira_*"]) for gateways with regular naming, and ["*"] as the explicit "everything" escape hatch. Omitting the key entirely keeps today's behaviour, so this is fully backward compatible.

Prior art: GitHub Copilot already does this, with almost exactly this syntax

The syntax above is not invented — it is deliberately aligned with what GitHub Copilot's coding agent already ships. In its MCP configuration, tools is a required key on every server entry, and it is a flat array of tool-name strings, with "*" meaning all:

{
  "mcpServers": {
    "notionApi": {
      "type": "local",
      "command": "docker",
      "args": ["run", "--rm", "-i"],
      "tools": ["*"]
    }
  }
}

GitHub's own documentation for the field:

tools (string[]): The tools from the MCP server to enable. […] We strongly recommend that you allowlist specific read-only tools, since the agent will be able to use these tools autonomously and will not ask you for approval first. You can also enable all tools by including * in the array.

Note that GitHub made this mandatory rather than optional — a server entry without a tool list is not valid config. They evidently concluded that "which tools does this server contribute?" is a question the user must answer explicitly, for autonomy-safety reasons as much as context reasons. VS Code's agent mode reaches the same place through a different door, letting you toggle individual MCP tools per server and group them into named tool sets.

Adopting the same field name and the same flat-array shape would also mean enterprises writing one tool policy per project could reuse it across both agents rather than maintaining two dialects.

Why the existing mechanisms don't close this

I'm aware of the two mechanisms cited when #7328 was closed, and I use both. Neither solves the case above:

1. Lazy loading / deferred tools via tool search. This helps a lot with raw token cost, and I'm grateful for it. But it is a mitigation of the symptom, not control of the surface: the tools are still there, still discoverable, still candidates for selection. It also trades a fixed cost for a variable one — the model now spends round-trips searching a haystack of hundreds of enterprise tools it will never legitimately need in this repo. And it is an automatic threshold-driven behaviour, not something I can express intent about per project.

2. Per-tool permissions.deny rules (mcp__server__tool). These gate a tool, but they do not solve the problem I actually have — and in particular they do not prevent conflict (unless I've missed something):

  • Permission rules are keyed on mcp__<server>__<tool>, i.e. on the server name. With one gateway there is exactly one server name, so there is no way to derive two differently-scoped views of it. I cannot say "in this project, this endpoint means Jira only" — I can only subtract from the single global identity.
  • They are denylist-shaped. Against a gateway whose tool list is controlled by someone else and grows without my involvement, enumerating what to exclude is unbounded and silently goes stale. Every product the enterprise buys next quarter lands in my context by default.
  • They do not resolve name collisions. When two products behind the gateway both expose create_ticket or search, they arrive in a single flat mcp__gateway__* namespace with no way to disambiguate which upstream they belong to, and no way to keep only one of them. Registering the endpoint twice under jira / servicenow with disjoint allowlists fixes this by construction; a deny rule cannot.
  • Denying is a permission statement, expressed in settings.json, about what may be invoked. What I want is a composition statement, expressed in .mcp.json, about what this server is in this project. Those are different concerns, and conflating them means the answer to "what tools does this project have?" is spread across two files with different scoping rules.

Alternative Solutions Considered

Filtering upstream, at the gateway — not available. This is the first thing we looked at and it is a dead end in practice. The gateway presents a single authentication covering the whole bundle, and there is no enterprise-recognized header or parameter for tool filtering — nothing standardised in the MCP spec, and nothing our gateway vendor implements. GitHub's toolsets selector is a vendor-specific convenience on GitHub's own server, not something a corporate gateway can be assumed to offer. So there is no request I can shape from the client that makes the endpoint return less. The filtering has to happen in the client, or it does not happen.

Run a local proxy/filter MCP server per project that fronts the corporate gateway and re-exports a subset. This works and people are building exactly these (sub-mcp, mcp-checkup and friends were both linked on #7328). But it means every developer runs an extra process per project, re-implements auth passthrough to the gateway, and maintains a shim — for a filter that is ten lines of config. In many enterprises, inserting an unvetted local proxy in front of the gateway is precisely the thing security review exists to stop.

Ask the gateway team to expose per-team endpoints. Not tractable: the gateway is centrally owned, and project-level tool selection is a local, fast-changing decision that cannot round-trip through a platform team.

Use a different coding agent. This is our actual current workaround, and I'd rather it weren't: for the repositories sitting behind the corporate gateway, we use GitHub Copilot as the preferred coding agent, because it lets us pick the tools per server and Claude Code does not. That is the whole of the reason. I'm filing this because I would prefer to bring those repos back.

The recurrence itself is the signal

Please read the closure history as data rather than as "already answered". Independent users have asked for this at least six times — #4380, #7328, #16851, #24000, #30996, #39491 — filed over roughly eight months, closed variously as completed, duplicate, not planned, and stale, from different angles (per-agent, per-server, profiles, denylists) but with one shared root cause: the MCP server is the only unit of configuration, and behind a gateway the server is a bundle, not a unit.

Six independent reports is not six people missing the documentation. And the resolutions offered so far are unusually clear evidence that the demand is real but unmet: the lazy-loading answer treats the token symptom, the deny-rule answer treats the permission symptom, and the community's response to both was to go build third-party proxies and token-audit tools to fill the gap.

Combined with a direct competitor shipping it — Copilot has per-tool selection per server plus tool sets — I think this is worth a second look rather than another duplicate closure. It is currently a reason teams choose the other tool.

Additional Context

  • Version: 2.1.259
  • The enterprise-gateway shape is the motivating case, but the same config solves ordinary ones: registering a filesystem or SSH server twice with different scopes, or trimming a large public server (Playwright, GitHub) to the three tools a repo actually uses.
  • Related: #4380, #7328, #16851, #24000, #30388, #30480, #30996, #39487, #39491.

View original on GitHub ↗