Skip to content

fix(core): scope tools.core wildcard deny to built-in tools#28365

Closed
vedhakoushik wants to merge 3 commits into
google-gemini:mainfrom
vedhakoushik:fix/core-tools-mcp-exclusion
Closed

fix(core): scope tools.core wildcard deny to built-in tools#28365
vedhakoushik wants to merge 3 commits into
google-gemini:mainfrom
vedhakoushik:fix/core-tools-mcp-exclusion

Conversation

@vedhakoushik

Copy link
Copy Markdown

Summary

Fixes a bug where setting tools.core to any value — including [] — silently disabled every MCP tool, regardless of trust settings, because a wildcard DENY rule had no way to exclude MCP tools from matching it.

Details

  • Added an opt-in builtinOnly field to PolicyRule (types.ts). When set, the rule never matches MCP tool calls, regardless of its toolName pattern.
  • ruleMatches() now short-circuits builtinOnly rules for any tool call with a serverName (i.e. an MCP tool).
  • The Core Tools Allowlist Enforcement rule in config.ts now sets builtinOnly: true, so tools.core matches its documented scope ("Restrict the set of built-in tools") without also silently excluding MCP tools from mcpServers.<name>.trust, mcp.allowed, and mcp.autoAllowInHeadless.
  • Because PolicyEngine.getExcludedTools() (which drives which tool declarations reach the model) shares the same ruleMatches() used for call-time checks, this one change fixes both the declaration-level exclusion and the call-time deny.
  • Clarified tools.core in docs/reference/configuration.md to state explicitly that it does not affect MCP tools.

Related Issues

Fixes #28361

How to Validate

  1. settings.json:
    {
      "mcpServers": {
        "github": {
          "command": "github-mcp-server",
          "args": ["stdio"],
          "trust": true
        }
      },
      "tools": {
        "core": []
      }
    }
  2. Run: gemini --approval-mode yolo -p "Call any tool from the github MCP server"
  3. Before this fix: the model gets no tool declarations for the github MCP server and any attempted call fails with Tool "<name>" not found. After this fix: the MCP tool is discoverable and callable.
  4. Run the regression tests: npm test -w @google/gemini-cli-core -- src/policy/config.test.ts src/policy/policy-engine.test.ts

Pre-Merge Checklist

  • Updated relevant documentation and README (if needed)
  • Added/updated tests (if needed)
  • Noted breaking changes (if any)
  • Validated on required platforms/methods:
    • MacOS
      • npm run
      • npx
      • Docker
      • Podman
      • Seatbelt
    • Windows
      • npm run
    • Linux
      • npm run
      • npx
      • Docker

🤖 Authored with Claude Code. First-time contributor here — happy to adjust anything (e.g. naming of the builtinOnly field) to match maintainer preference.

Setting settings.tools.core to any value, including [], added a wildcard
'*' DENY rule with no way to exclude MCP tools from matching it. That DENY
(priority 4.24) outranked every MCP-specific allow mechanism —
mcpServers.<name>.trust (4.2), mcp.allowed, and mcp.autoAllowInHeadless
(4.1) — so all MCP tools were silently denied regardless of trust
settings. Because PolicyEngine.getExcludedTools() (which drives which
tools are removed from the model's declarations) reuses the same rule
matching as call-time checks, the tools weren't just denied at call time —
their declarations never reached the model, so it blind-guessed tool
names until exhausting maxSessionTurns. This also contradicted
tools.core's documented scope: "Restrict the set of built-in tools."

Add an opt-in `builtinOnly` field to PolicyRule. When set, the rule never
matches MCP tools (serverName !== undefined), regardless of its toolName
pattern — including the wildcard '*'. Set builtinOnly: true on the Core
Tools Allowlist Enforcement rule in config.ts, so it stays scoped to
built-in tools as documented while leaving MCP tools governed by their own
trust/allow mechanisms.

Because getExcludedTools() and the real-time check() share the same
ruleMatches() function, this one change fixes both the declaration-level
exclusion and the call-time deny.

Added regression tests: builtinOnly matching in ruleMatches() and
getExcludedTools(), a priority-ordering test reproducing the exact
issue scenario (MCP trust ALLOW at 4.2 now wins over the builtinOnly
DENY at 4.24), and end-to-end tests through createPolicyEngineConfig +
PolicyEngine reproducing the issue's minimal repro (tools.core: []
plus a trusted MCP server). Also clarified the tools.core docs to
state explicitly that it does not affect MCP tools.

Fixes google-gemini#28361

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Bellam vedha kousik <bellam.vedhakoushik@gmail.com>
@vedhakoushik vedhakoushik requested review from a team as code owners July 12, 2026 11:28
@github-actions github-actions Bot added the size/m A medium sized PR label Jul 12, 2026
@github-actions

github-actions Bot commented Jul 12, 2026

Copy link
Copy Markdown

📊 PR Size: size/L

  • Lines changed: 259
  • Additions: +258
  • Deletions: -1
  • Files changed: 6

@google-cla

google-cla Bot commented Jul 12, 2026

Copy link
Copy Markdown

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request resolves an issue where the tools.core configuration incorrectly applied a wildcard denial to all tools, including those provided by MCP servers. By introducing a builtinOnly property to policy rules, the system can now distinguish between built-in tools and MCP tools, ensuring that global core tool restrictions do not override individual MCP trust settings. This change fixes both tool declaration visibility and call-time authorization, restoring expected behavior for MCP server integration.

Highlights

  • Policy Rule Scoping: Introduced a builtinOnly flag to PolicyRule to ensure that specific rules, such as wildcard denials, only apply to built-in tools and do not inadvertently affect MCP tools.
  • Core Tools Enforcement: Updated the Core Tools Allowlist Enforcement rule to use builtinOnly: true, preventing tools.core settings from silently disabling MCP tools.
  • Policy Engine Logic: Modified ruleMatches() to short-circuit rules marked as builtinOnly when an MCP tool (which has a serverName) is being evaluated.
  • Documentation: Clarified docs/reference/configuration.md to explicitly state that tools.core does not affect MCP tool availability.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize the Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counterproductive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot 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.

Code Review

This pull request fixes a bug where configuring the core tools allowlist (tools.core) incorrectly disabled Model Context Protocol (MCP) tools. It introduces a builtinOnly flag on policy rules to ensure wildcard rules meant for built-in tools do not affect MCP tools, updating the policy engine, configuration logic, documentation, and tests. The review feedback identifies a potential edge case in ruleMatches where serverName might be undefined for MCP tools during static analysis (e.g., in getExcludedTools), and suggests checking the tool name prefix to prevent accidental exclusion of MCP tools.

Comment on lines +124 to +126
if ('builtinOnly' in rule && rule.builtinOnly && serverName !== undefined) {
return false;
}

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.

high

In ruleMatches, the check to skip MCP tools for builtinOnly rules relies solely on serverName !== undefined. However, in some contexts (such as PolicyEngine.getExcludedTools() when toolMetadata is missing or incomplete), serverName may be undefined even for MCP tools. Since all MCP tools are prefixed with mcp_ (and thus satisfy isMcpToolName), we should also check isMcpToolName(toolCall.name) to ensure that builtinOnly rules are reliably scoped to built-in tools and do not accidentally match and deny MCP tools.

  if (
    'builtinOnly' in rule &&
    rule.builtinOnly &&
    (serverName !== undefined || (toolCall.name && isMcpToolName(toolCall.name)))
  ) {
    return false;
  }
References
  1. Tool security policies are applied based on tool.name, not tool.kind.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good catch — confirmed and fixed in 36c4982. Traced it to a real path: PolicyEngine.getExcludedTools()'s toolMetadata param is optional, and even in production tool-registry.ts's buildToolMetadata() only attaches _serverName when tool instanceof DiscoveredMCPTool — any other MCP tool wrapper (or a caller that omits toolMetadata) would silently lose the protection.

builtinOnly now checks serverName !== undefined || isMcpToolName(toolCall.name), using the already-imported isMcpToolName() helper as the structural fallback that doesn't depend on caller-supplied metadata. Added regression tests for both the direct check() path and getExcludedTools() with no metadata provided.

@gemini-cli gemini-cli Bot added priority/p1 Important and should be addressed in the near term. area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality labels Jul 12, 2026
Addresses Gemini Code Assist review on google-gemini#28365: the builtinOnly check
relied solely on serverName !== undefined, but serverName comes from
caller-supplied tool metadata (PolicyEngine.getExcludedTools()'s
toolMetadata param) and can be undefined even for a genuine MCP tool
call — e.g. toolMetadata is omitted, or a tool has no entry in the map
because it wasn't recognized as DiscoveredMCPTool upstream
(tool-registry.ts's buildToolMetadata()). In that case a builtinOnly
wildcard DENY would still match the MCP tool and reintroduce the
original bug for that code path.

All MCP tools are structurally guaranteed to carry the mcp_ prefix
(MCP_TOOL_PREFIX / isMcpToolName(), already imported and used elsewhere
in this file), independent of any metadata being correctly populated.
builtinOnly now checks serverName OR isMcpToolName(toolCall.name), so
it stays reliable even when metadata is missing or incomplete.

Added regression tests for both the direct check() path and the
getExcludedTools() path with no metadata provided.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Bellam vedha kousik <bellam.vedhakoushik@gmail.com>
@github-actions github-actions Bot added the size/l A large sized PR label Jul 12, 2026
Signed-off-by: Bellam vedha kousik <bellam.vedhakoushik@gmail.com>
@vedhakoushik

Copy link
Copy Markdown
Author

Superseded by #28388 — same fix, clean re-submission without the Co-Authored-By trailer that was blocking the CLA check.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/agent Issues related to Core Agent, Tools, Memory, Sub-Agents, Hooks, Agent Quality priority/p1 Important and should be addressed in the near term. size/l A large sized PR size/m A medium sized PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Any settings.tools.core value (even []) emits a wildcard DENY that silently excludes all MCP tools – breaks run-gemini-cli's shipped pr-review example

1 participant