Skip to content

feat(mcp): filter allowed tools by readOnlyHint#1425

Open
RohanSogani wants to merge 2 commits into
lightseekorg:mainfrom
RohanSogani:feat/mcp-allowed-tools-readonly-filter
Open

feat(mcp): filter allowed tools by readOnlyHint#1425
RohanSogani wants to merge 2 commits into
lightseekorg:mainfrom
RohanSogani:feat/mcp-allowed-tools-readonly-filter

Conversation

@RohanSogani
Copy link
Copy Markdown
Contributor

@RohanSogani RohanSogani commented May 1, 2026

Description

Problem

The Responses API allowed_tools field on MCP tools was not fully applied before exposing MCP tools to the model.

The array form should restrict model-visible MCP tools by name:

{
  "type": "mcp",
  "server_url": "...",
  "allowed_tools": ["tool_a", "tool_b"]
}

The Responses API also supports the filter object form:

  {
    "allowed_tools": {
      "read_only": true,
      "tool_names": ["tool_a"]
    }
  }

Before this change, SMG could parse the object form, but the runtime MCP session only carried a flat list of tool names. Any read_only restriction was projected to an empty allowlist because readOnlyHint-
backed filtering was not implemented.

Resolves #163.

Solution

Add a generic MCP tool exposure filter that preserves both tool_names and read_only.

McpToolSession now filters the MCP tool inventory using both constraints:

  • tool_names, when provided
  • read_only, matched against MCP tool annotations / readOnlyHint

Missing or false readOnlyHint is treated conservatively as not read-only.

This keeps the implementation generic and does not add any provider-specific or OCI-specific behavior.

Changes

  • Added McpToolExposureFilter.
  • Changed McpServerBinding.allowed_tools to carry the exposure filter instead of only Vec.
  • Updated Responses MCP allowed_tools projection to preserve read_only.
  • Updated MCP session filtering to evaluate ToolEntry.annotations.read_only.
  • Preserved existing name-based allowlist behavior.
  • Preserved Anthropic MCP toolset name-only filtering by mapping it into the new filter type.
  • Added tests for read-only filtering and combined read_only + tool_names filtering.

Test Plan

Repro from repo root:

cargo +nightly fmt --all -- --check
cargo check -p smg-mcp -p smg
cargo test -p smg-mcp allowed_tools -- --nocapture
cargo test -p smg project_allowed_tools -- --nocapture

Verified:

  • allowed_tools: ["tool_a"] still filters by tool name.
  • allowed_tools: { "read_only": true } exposes only tools with readOnlyHint=true.
  • allowed_tools: { "read_only": true, "tool_names": [...] } applies both constraints.
  • Tools without a read-only annotation are not exposed when read_only=true.
  • Existing MCP routing checks still compile.
Checklist
  • cargo +nightly fmt passes
  • cargo check -p smg-mcp -p smg passes
  • cargo clippy --all-targets --all-features -- -D warnings passes
  • (Optional) Documentation updated
  • (Optional) Please join us on Slack #sig-smg (https://slack.lightseek.org) to discuss, review, and merge PRs
```

Summary by CodeRabbit

  • New Features

    • Introduced enhanced MCP tool filtering supporting constraints on both tool names and read-only status for more granular control over exposed tools.
  • Chores

    • Updated internal tool allowlist handling across routers to support the new filtering mechanism.

Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
@github-actions github-actions Bot added mcp MCP related changes model-gateway Model gateway crate changes openai OpenAI router changes anthropic Anthropic router changes labels May 1, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 1, 2026

Warning

Rate limit exceeded

@RohanSogani has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 6 seconds before requesting another review.

To keep reviews running without waiting, you can enable usage-based add-on for your organization. This allows additional reviews beyond the hourly cap. Account admins can enable it under billing.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0f2c8110-c8fd-49c5-b9c1-44bf11e04fe3

📥 Commits

Reviewing files that changed from the base of the PR and between 585718f and e32e7d7.

📒 Files selected for processing (1)
  • crates/mcp/src/core/session.rs
📝 Walkthrough

Walkthrough

The PR introduces McpToolExposureFilter to replace the simple Option<Vec<String>> wire shape for allowed tools on MCP servers. This enables filtering by tool names and read-only status. Session construction now applies these filters during tool matching, and routers project allowed tools into the new filter format.

Changes

Cohort / File(s) Summary
Core Type Definition and Re-exports
crates/mcp/src/core/session.rs, crates/mcp/src/core/mod.rs, crates/mcp/src/lib.rs
Introduces McpToolExposureFilter struct with optional tool_names and read_only fields. Session construction indexes per-server filters and applies matches_allowed_tool_filter matching logic that validates read-only status and tool name inclusion. Re-exports added to core and library modules. Tests updated with new filter construction and validation of combined read-only/name filtering behavior.
Router Tool Allowlist Projection
model_gateway/src/routers/common/mcp_utils.rs, model_gateway/src/routers/anthropic/router.rs
Migrates project_allowed_tools from flattening McpAllowedTools to Option<Vec<String>> to projecting it into Option<McpToolExposureFilter>. Now preserves both tool_names and read_only constraints instead of failing closed. McpServerInput.allowed_tools field type updated. Unit tests renamed from "fail closed" to "preserved" expectations and validate filter fields.
Test Updates
model_gateway/src/routers/openai/mcp/tool_loop.rs
Test bindings updated to construct McpToolExposureFilter via tool_names() helper instead of raw Vec<String> for allowed_tools values.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Router
    participant MCP Utils
    participant Session
    
    Client->>Router: Request with McpAllowedTools
    Router->>MCP Utils: project_allowed_tools(allowed_tools)
    MCP Utils-->>Router: McpToolExposureFilter {tool_names?, read_only?}
    Router->>Session: McpServerInput {allowed_tools: Filter}
    Session->>Session: Index filters by server<br/>HashMap<&str, &McpToolExposureFilter>
    Session->>Session: matches_allowed_tool_filter()<br/>for each tool from server
    Note over Session: Validate read_only status<br/>and tool name membership
    Session-->>Router: Filtered tool inventory
    Router-->>Client: Response with filtered tools
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • PR #488: Directly preceded this work—both modify the per-server allowed_tools shape and session/router handling, with this PR building upon it by replacing Option<Vec<String>> with Option<McpToolExposureFilter>.
  • PR #1168: Both modify crates/mcp/src/core/session.rs and McpToolSession behavior; this PR adds exposure filtering while the other adds session-level visibility/redaction methods, so changes interact in the same code areas.
  • PR #370: Both modify session construction and tool exposure/lookup logic; this PR adds the exposure filter wire shape while the other refactors session to build exposed-name mappings and execution APIs.

Suggested labels

mcp, model-gateway, anthropic, openai, grpc

Suggested reviewers

  • CatherineSue
  • slin1237
  • key4ng

Poem

🐰 A filter springs forth, nimble and bright,
Tools sorted by name and read-only light,
Sessions now match with precision so keen,
The finest exposure the routers have seen! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(mcp): filter allowed tools by readOnlyHint' accurately describes the main change: implementing filtering of MCP allowed tools using the readOnlyHint attribute.
Linked Issues check ✅ Passed The PR successfully addresses issue #163 by implementing tool filtering via McpToolExposureFilter that preserves both tool_names and read_only constraints before exposing tools to the model.
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing allowed_tools filtering for MCP tools; no extraneous modifications detected beyond the stated objective.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
Review rate limit: 0/1 reviews remaining, refill in 41 minutes and 6 seconds.

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 585718fbd0

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/mcp/src/core/session.rs
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

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 replaces the basic tool allowlist in MCP server bindings with a more flexible McpToolExposureFilter, enabling tool filtering based on both names and read_only annotations. Changes span the core MCP session logic, gateway projection utilities, and associated tests. Feedback highlights a performance regression in the tool matching logic where HashSet allocations occur repeatedly within a loop; it is recommended to pre-calculate these sets to improve efficiency.

Comment thread crates/mcp/src/core/session.rs Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@crates/mcp/src/core/session.rs`:
- Around line 1397-1476: Add a unit test that covers alias entries when both
name filters and read_only annotations are applied: create a ToolEntry alias
(e.g., via whatever helper creates alias entries) whose target tool is annotated
read_only(true) and also include that alias name in McpToolExposureFilter names;
then construct a McpToolSession (as in tests using McpOrchestrator and
McpServerBinding) and assert that
matches_allowed_tool_filter()/session.mcp_tools()/session.has_exposed_tool()
allow the alias only when the target’s read_only annotation permits it. This
ensures the alias inventory path correctly consults ToolAnnotations.read_only
and the name-matching logic in matches_allowed_tool_filter().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 969e74e6-ad27-48f8-bd01-900894fe236c

📥 Commits

Reviewing files that changed from the base of the PR and between 04f9b2d and 585718f.

📒 Files selected for processing (6)
  • crates/mcp/src/core/mod.rs
  • crates/mcp/src/core/session.rs
  • crates/mcp/src/lib.rs
  • model_gateway/src/routers/anthropic/router.rs
  • model_gateway/src/routers/common/mcp_utils.rs
  • model_gateway/src/routers/openai/mcp/tool_loop.rs

Comment thread crates/mcp/src/core/session.rs
Signed-off-by: Rohan Sogani <ronsogani@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

anthropic Anthropic router changes mcp MCP related changes model-gateway Model gateway crate changes openai OpenAI router changes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

allowed_tools on MCP tools is ignored (no filtering before LLM)

1 participant