Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 39 additions & 12 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,46 @@ and this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.ht

## [Unreleased]

## [0.22.0] - 2026-05-20

**Theme: MCP proxy operator-side tool filtering.** Adds two repeatable
CLI flags to the MCP proxy that let an operator shape the upstream
tool surface visible to the AI client: `--allow-tool NAME` (if any are
given, only those tools pass through) and `--deny-tool NAME` (those
tools are filtered, wins on overlap with allowlist). 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 proxy
perimeter with an MCP `isError: true` payload (`decision: "FILTERED"`,
`reason: "Tool filtered by operator policy"`) without forwarding to
the upstream or invoking the risk pipeline.

### Added
- MCP proxy: operator-side tool filtering at the perimeter.
`python -m vaara.integrations.mcp_proxy` now accepts repeatable
`--allow-tool NAME` and `--deny-tool NAME` flags. 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 proxy with an
MCP `isError: true` payload (`decision: "FILTERED"`,
`reason: "Tool filtered by operator policy"`) without forwarding
upstream or invoking the risk pipeline. Denylist wins on overlap with
allowlist. Backward compatible: no flags = current passthrough
behavior. Use case: hide write/delete tools (e.g. `delete_repository`,
`create_branch`) from an MCP client when the upstream server exposes
more capability than the deployment policy allows.
- `src/vaara/integrations/mcp_proxy.py`: `VaaraMCPProxy.__init__`
accepts `allowlist: Optional[set[str]]` and `denylist:
Optional[set[str]]`. New `_tool_filtered(name)` helper applied to
both `tools/list` (filters the `result.tools` array) and
`tools/call` (returns a `FILTERED` block payload before the
pipeline runs).
- CLI: `--allow-tool NAME` and `--deny-tool NAME`, both repeatable.
Backward compatible: no flags = passthrough.
- `tests/test_integrations_mcp_proxy.py`: eight new tests covering
denylist drops, allowlist restricts, denylist-wins-on-overlap,
no-policy passthrough, filtered tools/call returns block, and
allowlisted tools/call still routes through the pipeline.

### Verified
- End-to-end smoke against `github/github-mcp-server` (42 real tools):
`--deny-tool` drops named entries from `tools/list` and rejects
matching `tools/call`; `--allow-tool` restricts `tools/list` to the
allowlist and routes allowlisted calls through the pipeline as
before; no-flag run is identical to v0.21.0 behaviour.

### Use case
Hide write/delete tools (e.g. `delete_file`, `merge_pull_request`)
when the upstream MCP server exposes more capability than the
deployment policy allows. The LLM client never learns the tool
exists, which is materially different from instructing the model
not to call it.

## [0.21.0] - 2026-05-19

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,8 @@ python -m vaara.integrations.mcp_proxy \

Point your MCP client at the proxy instead of the upstream. The audit chain captures every tool call without changing client or upstream behavior. Distinct from `mcp_server`, which exposes Vaara itself as an MCP server for agents that consult Vaara as a tool.

**Operator-side tool filtering** (since v0.22.0). The proxy accepts repeatable `--allow-tool NAME` and `--deny-tool NAME` flags. Filtered tools are dropped from `tools/list` responses before the client sees them and any matching `tools/call` is rejected at the proxy perimeter without contacting the upstream. Use this to hide write/delete tools (e.g. `delete_file`, `merge_pull_request`) when the upstream server exposes more capability than the deployment policy allows. Denylist wins on overlap with allowlist. No flags = passthrough.

Worked examples with real upstream servers:

- [`examples/github-mcp-proxy-demo/`](examples/github-mcp-proxy-demo/). Vaara in front of [`github/github-mcp-server`](https://github.com/github/github-mcp-server) (GitHub's official MCP server, MIT-licensed). End-to-end verified: real subprocess, 42 tools advertised, hash-chained audit trail recorded.
Expand Down
2 changes: 1 addition & 1 deletion clients/ts/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@vaara/client",
"version": "0.21.0",
"version": "0.22.0",
"description": "TypeScript client for the Vaara HTTP API. Conformal risk scoring, hash-chained audit, policy reload, named detectors.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"

[project]
name = "vaara"
version = "0.21.0"
version = "0.22.0"
description = "Adaptive AI Agent Execution Layer for risk scoring, audit trails, and regulatory compliance"
requires-python = ">=3.10"
license = "Apache-2.0"
Expand Down
5 changes: 5 additions & 0 deletions src/vaara/integrations/mcp_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@
community-built MCP server). Forwards every request to the upstream, but
routes ``tools/call`` through Vaara's interception pipeline first. Allowed
calls flow through transparently. Blocked calls return an MCP tool error.

Optional operator-side filtering (``--allow-tool``/``--deny-tool``): when set,
the proxy filters the upstream's ``tools/list`` response before the client
sees it, and rejects ``tools/call`` to a filtered tool at the perimeter with
a ``FILTERED`` block payload, without contacting the upstream.
Comment on lines +9 to +12
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Docstring now conflicts with forwarding semantics.

With filtering enabled, not every request is forwarded upstream (tools/call can be blocked at the perimeter). Please adjust the earlier “Forwards every request” sentence so the module contract is unambiguous.

✏️ Proposed wording adjustment
-Forwards every request to the upstream, but
-routes ``tools/call`` through Vaara's interception pipeline first. Allowed
-calls flow through transparently. Blocked calls return an MCP tool error.
+Forwards MCP traffic to the upstream, with ``tools/call`` routed through
+Vaara's interception pipeline first. Allowed calls flow through transparently.
+Blocked calls (policy or operator filter) return an MCP tool error without
+contacting the upstream.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/vaara/integrations/mcp_proxy.py` around lines 9 - 12, The module
docstring incorrectly says the proxy "forwards every request" while the
implementation optionally filters and blocks some requests; update the top-level
docstring in mcp_proxy.py to state that the proxy normally forwards requests
but, when operator-side filtering (--allow-tool/--deny-tool) is enabled, it will
filter the upstream tools/list response and may block tools/call at the
perimeter (returning a FILTERED block payload) instead of forwarding them
upstream; mention the relevant endpoints (tools/list, tools/call) and the
filtering flags so the contract is unambiguous.

"""

from __future__ import annotations
Expand Down