Skip to content

feat: v0.22.0 (MCP proxy operator allowlist/denylist for tools/list and tools/call)#112

Merged
vaaraio merged 1 commit into
mainfrom
feat/mcp-proxy-tools-list-filter
May 20, 2026
Merged

feat: v0.22.0 (MCP proxy operator allowlist/denylist for tools/list and tools/call)#112
vaaraio merged 1 commit into
mainfrom
feat/mcp-proxy-tools-list-filter

Conversation

@vaaraio
Copy link
Copy Markdown
Owner

@vaaraio vaaraio commented May 20, 2026

Summary

Adds operator-side tool filtering to the MCP proxy via two new repeatable CLI flags:

  • --allow-tool NAME: if any are given, only those tools pass through.
  • --deny-tool NAME: those tools are filtered. Denylist wins on overlap.

Effects:

  • tools/list responses are filtered before the client sees them. The LLM never learns that hidden tools exist.
  • tools/call to a filtered tool is rejected at the perimeter with an MCP isError: true payload (decision: "FILTERED", reason: "Tool filtered by operator policy"). The upstream is not contacted and the risk pipeline is not invoked.

Backward compatible: no flags means current passthrough behavior. The pipeline-based interception path is unchanged for non-filtered tool calls.

Why

The proxy already governs each tools/call through the risk pipeline at runtime. That covers the "is this specific call safe right now" question. It does not cover the "should the LLM ever see this tool at all" question.

Example: an MCP client running against github/github-mcp-server is exposed to write tools like delete_repository, create_branch, merge_pull_request. A deployment may want a read-only posture without depending on the LLM to refrain. Denying those tools at the perimeter shapes the LLM's tool surface to match operator policy, independent of the risk score.

Small surface, real policy-semantic upgrade: from "score every call" to "score every call AND control what's reachable."

Usage

python -m vaara.integrations.mcp_proxy \
  --upstream /path/to/github-mcp-server \
  --upstream-arg stdio \
  --allow-tool search_repositories \
  --allow-tool get_pull_request \
  --allow-tool list_issues

Read-only against GitHub MCP. Anything else returns Tool filtered by operator policy on call, and never appears in tools/list to begin with.

Test plan

  • pytest tests/test_integrations_mcp_proxy.py — 14 tests pass (8 new, covering denylist drops, allowlist restricts, denylist-wins-on-overlap, no-policy passthrough, filtered tools/call returns block, allowlist tools/call still pipelines)
  • Full suite: 726 passed, 12 skipped, no regressions
  • Smoke against real github-mcp-server with --deny-tool delete_repository and verify tools/list and tools/call both reject (deferred to follow-up unless reviewer wants it pre-merge)

Summary by CodeRabbit

  • New Features
    • Added operator-side tool filtering for MCP proxy via repeatable --allow-tool and --deny-tool CLI flags. Filtered tools are excluded from tool listings and blocked from execution without forwarding to upstream. Denylist takes precedence over allowlist.

Review Change Stack

…/call

Add --allow-tool and --deny-tool repeatable CLI flags to the MCP proxy.
Filtered tools are dropped from tools/list responses before the client
sees them, and any tools/call to a filtered tool is rejected at the
perimeter with an MCP isError payload (decision=FILTERED) without
forwarding upstream or invoking the risk pipeline. Denylist wins on
overlap with allowlist. Backward compatible: no flags = passthrough.

Use case: hide write/delete tools (delete_repository, create_branch)
when the upstream MCP server exposes more capability than the
deployment policy allows. The LLM client never learns the tool
exists.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 42ea5a7b-9fe8-4904-b7f1-d7b3d744a3fb

📥 Commits

Reviewing files that changed from the base of the PR and between cfd746a and b98e22f.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/vaara/integrations/mcp_proxy.py
  • tests/test_integrations_mcp_proxy.py

📝 Walkthrough

Walkthrough

The MCP proxy gains operator-configurable tool filtering with allow and deny lists (denylist takes precedence). Filtered tools are removed from tools/list responses and tools/call requests targeting them return early with an MCP error, before pipeline interception or upstream forwarding. CLI flags --allow-tool and --deny-tool wire the policy into the proxy.

Changes

Operator-side Tool Filtering Policy

Layer / File(s) Summary
Policy filtering logic
src/vaara/integrations/mcp_proxy.py
Constructor parameters allowlist and denylist are normalized into sets; _tool_filtered(name) helper implements the decision logic: denylist blocks any tool; allowlist restricts to only listed tools; denylist takes precedence when both exist.
Request handler enforcement
src/vaara/integrations/mcp_proxy.py
_handle_tools_list() filters upstream tool list and returns a shallow response copy with only non-filtered tools; _handle_tools_call() rejects filtered tools early with MCP error payload (decision: "FILTERED", reason: "Tool filtered by operator policy") before pipeline and upstream invocation.
CLI integration
src/vaara/integrations/mcp_proxy.py
Repeatable --allow-tool NAME and --deny-tool NAME flags parse tool names into sets and wire them into proxy constructor as allowlist and denylist parameters.
Test coverage and documentation
tests/test_integrations_mcp_proxy.py, CHANGELOG.md
_make_proxy() helper centralizes proxy construction with mocked dependencies; tools/list tests validate denylist/allowlist filtering semantics and overlap precedence; tools/call tests validate early "FILTERED" rejection without upstream/pipeline calls for blocked tools and normal flow for allowed tools; CHANGELOG documents feature, flags, filtering behavior, and error payload structure.

Sequence Diagram(s)

[No diagram generated: changes are primarily configuration, policy enforcement, and test validation without complex multi-component interaction flow suitable for sequence visualization.]

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • vaaraio/vaara#100: Introduces the base VaaraMCPProxy that is extended here to add operator-side tool filtering and early rejection behavior.

Poem

🐰 A proxy stands guard at the tool house door,
With lists of allowed and denied evermore,
The denylist wins when in doubt,
Filtered tools get bounced right out,
Operators now control what's in store! ✨

🚥 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 accurately and concisely summarizes the main change: adding operator-side allowlist/denylist filtering for tools/list and tools/call in the MCP proxy.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/mcp-proxy-tools-list-filter

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

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

@vaaraio vaaraio merged commit bd51f59 into main May 20, 2026
10 checks passed
@vaaraio vaaraio deleted the feat/mcp-proxy-tools-list-filter branch May 20, 2026 08:10
@vaaraio
Copy link
Copy Markdown
Owner Author

vaaraio commented May 20, 2026

End-to-end smoke against the real github/github-mcp-server binary (42 tools advertised). Three scenarios, all green.

A: --deny-tool delete_file --deny-tool merge_pull_request

  • tools/list: 42 → 40, both denied names absent
  • tools/call delete_file: returns isError: true with decision: "FILTERED", reason: "Tool filtered by operator policy". Upstream never contacted, pipeline never invoked. Stderr line: WARNING tools/call rejected at perimeter (operator filter): delete_file
  • tools/call search_repositories: pipeline scores it (risk=0.113 [0.000, 0.302], allow), forwards to upstream, upstream returns 401 from the dummy PAT (wiring confirmed)

B: --allow-tool search_repositories --allow-tool pull_request_read --allow-tool list_issues

  • tools/list: 42 → 3 (exactly the allowlist)
  • tools/call create_branch (outside allowlist): FILTERED block
  • tools/call search_repositories (inside allowlist): pipeline + upstream

C: no flags (regression check)

  • tools/list: 42 tools unchanged. Backward compatible.

Audit-trail behavior is asymmetric by design: filtered calls leave only a stderr WARNING and no DB write (the proxy maintains the fiction that hidden tools do not exist). Allowed calls produce the full action_requestedrisk_scoreddecision_madeoutcome_recorded hash-chained sequence as before.

@vaaraio vaaraio changed the title feat(mcp-proxy): operator allowlist/denylist for tools/list and tools/call feat: v0.22.0 (MCP proxy operator allowlist/denylist for tools/list and tools/call) May 20, 2026
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.

1 participant