Skip to content

feat: v0.21.0 MCP proxy (transparent governance for upstream MCP servers)#100

Merged
vaaraio merged 4 commits into
mainfrom
release/v0.21.0
May 19, 2026
Merged

feat: v0.21.0 MCP proxy (transparent governance for upstream MCP servers)#100
vaaraio merged 4 commits into
mainfrom
release/v0.21.0

Conversation

@vaaraio
Copy link
Copy Markdown
Owner

@vaaraio vaaraio commented May 19, 2026

Summary

VaaraMCPProxy sits between an MCP client (Claude Code, Cursor, any MCP-capable host) and an upstream MCP server (SAP ADT MCP, SAP Graph API MCP, SAP Cloud ALM MCP, any community-built MCP server). Every tools/call request from the client routes through Vaara's interception pipeline before reaching the upstream. Allowed calls forward transparently and report the upstream outcome back to the scorer. Blocked calls return an MCP isError: true response with the block reason. Other MCP methods (initialize, tools/list, resources, notifications) forward unchanged.

Distinct from mcp_server.py, which exposes Vaara itself as an MCP server for agents that consult Vaara as a tool. The proxy is the other direction: Vaara in the middle of a client-upstream pair, intercepting tool calls in flight.

What's in

  • src/vaara/integrations/mcp_proxy.py: VaaraMCPProxy + CLI. Invoke as python -m vaara.integrations.mcp_proxy --upstream <cmd> [--upstream-arg ...].
  • src/vaara/integrations/_mcp_upstream.py: UpstreamMCPClient with subprocess lifecycle and JSON-RPC id demultiplexing on a background reader thread. Internal module, not part of the public surface.
  • tests/test_integrations_mcp_proxy.py: six smoke tests covering blocked tool calls, allowed forward, severity mapping, the _vaara_agent_id strip, non-tools/call passthrough, and invalid request handling.
  • README "MCP proxy" subsection under integrations.
  • CHANGELOG entry dated 2026-05-19.
  • Version bump across pyproject.toml, src/vaara/__init__.py, clients/ts/package.json (0.20.0 → 0.21.0).

Strategic frame

The community SAP MCP servers shipped at SAP Sapphire 2026 plus the Anthropic-SAP partnership announcement put SAP ABAP / Graph / Cloud ALM behind Claude Code in enterprise developer workflows. None of the parties (SAP, Anthropic, the community MCP server authors) ships the runtime governance layer the EU AI Act high-risk obligations require for those tool calls. The proxy is that layer in OSS today.

Test plan

  • pytest tests/ passes (718 passed, 12 skipped)
  • Six new tests in tests/test_integrations_mcp_proxy.py all green
  • Manual smoke test against a real upstream MCP server (deferred to the demo PR)
  • Demo examples/sap-mcp-proxy-demo/ lands in a follow-up PR

Summary by CodeRabbit

Release Notes v0.21.0

  • New Features

    • Released MCP proxy service providing transparent governance of tool calls through policy-based filtering and audit logging capabilities.
  • Documentation

    • Added MCP proxy integration guide with configuration examples and startup instructions.
    • Updated CHANGELOG documenting version 0.21.0 with feature details and release context.

Review Change Stack

vaaraio and others added 2 commits May 19, 2026 08:36
PyPI page already shows supported Python versions. Trims badge row to keep it on one line.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ers)

VaaraMCPProxy sits between an MCP client (Claude Code, Cursor, any MCP-capable host) and an upstream MCP server (SAP ADT MCP, SAP Graph API MCP, SAP Cloud ALM MCP, any community-built MCP server). Every tools/call request from the client routes through Vaara's interception pipeline before reaching the upstream. Allowed calls forward transparently and report the upstream outcome back to the scorer. Blocked calls return an MCP isError response with the block reason. Other MCP methods (initialize, tools/list, resources, notifications) forward unchanged.

Adds src/vaara/integrations/mcp_proxy.py with the proxy and CLI entry, src/vaara/integrations/_mcp_upstream.py with the subprocess client and JSON-RPC id demultiplexer on a background reader thread, and tests/test_integrations_mcp_proxy.py with six smoke tests covering blocked tool calls, allowed forward, severity mapping, the _vaara_agent_id strip, non-tools/call passthrough, and invalid request handling.

Distinct from mcp_server.py, which exposes Vaara itself as an MCP server for agents that consult Vaara as a tool. The proxy is the other direction: Vaara in the middle of a client-upstream pair, intercepting tool calls in flight.

TS client bumped lockstep to 0.21.0. 718 tests passing.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 19, 2026

Warning

Rate limit exceeded

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

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 29bb0672-02d6-4faf-b945-cca4f0f6b447

📥 Commits

Reviewing files that changed from the base of the PR and between 235054d and 8d479cd.

📒 Files selected for processing (2)
  • src/vaara/integrations/_mcp_upstream.py
  • tests/test_integrations_mcp_proxy.py
📝 Walkthrough

Walkthrough

This PR releases version 0.21.0 with an MCP-aware proxy that transparently intercepts and classifies model tool calls through Vaara's policy engine. It includes upstream subprocess client infrastructure, the proxy request routing and interception logic, CLI wiring, and integration test coverage.

Changes

MCP proxy feature and release

Layer / File(s) Summary
Release version and documentation updates
CHANGELOG.md, README.md, clients/ts/package.json, pyproject.toml, src/vaara/__init__.py
Version bumped to 0.21.0 across all manifests. CHANGELOG documents the proxy feature and supporting modules. README adds MCP proxy documentation describing behavior, startup, and client configuration.
Upstream MCP client subprocess infrastructure
src/vaara/integrations/_mcp_upstream.py
UpstreamMCPClient spawns an upstream MCP server subprocess, maintains a background reader thread for line-delimited JSON-RPC message parsing, demultiplexes responses by id using threading.Event, and provides synchronous request/notify methods with timeout and lifecycle management.
MCP proxy request routing and interception
src/vaara/integrations/mcp_proxy.py
VaaraMCPProxy reads JSON-RPC from stdin, forwards notifications to upstream, intercepts tools/call through InterceptionPipeline (extracting agent_id, validating tool names, returning MCP errors when blocked), forwards allowed calls upstream, derives outcome severity from responses, reports outcomes via pipeline, and provides CLI entry point with optional audit database.
Integration test suite for proxy behavior
tests/test_integrations_mcp_proxy.py
Mocked integration tests validate blocked tools/call requests return MCP tool errors with deny reasons, allowed calls forward and report outcomes with correct severity, upstream errors map to high severity, _vaara_agent_id override is extracted and stripped before forwarding, non-tools/call requests pass through unchanged, and invalid requests return code -32600.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • vaaraio/vaara#20: The MCP proxy's tools/call interception relies on InterceptionPipeline.intercept() and tool classification behavior that were modified in PR #20 (shadow-mode enforce handling and heuristic/cluster classification).

Poem

🐰 A proxy hops between two worlds today,
JSON-RPC packets in its furry way,
It whispers to Vaara: "Shall this tool run?"
Block it or allow—decisions made in one!
Version bumped, docs shine, tests give their cheer,
The MCP gateway is finally here! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.83% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: introducing v0.21.0 with an MCP proxy feature for transparent governance of upstream MCP servers, which aligns with the primary scope of the PR.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch release/v0.21.0

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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.

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

🧹 Nitpick comments (1)
tests/test_integrations_mcp_proxy.py (1)

46-139: ⚡ Quick win

Add a regression test for upstream-close mid-request behavior.

Please add one smoke test where upstream request waiting is interrupted by upstream shutdown, and assert _handle_request returns a JSON-RPC internal error response instead of raising.

🤖 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 `@tests/test_integrations_mcp_proxy.py` around lines 46 - 139, Add a smoke test
that ensures _handle_request handles an upstream shutdown by making
p._upstream.request raise an exception (e.g., ConnectionError or the project's
upstream-closed exception) while processing a request and asserting that
_handle_request returns a JSON-RPC internal error response (error.code ==
-32603) instead of letting the exception propagate; create the test in the same
style as existing tests, use the proxy fixture to get p and pipeline, set
p._upstream.request.side_effect to the exception, call p._handle_request with
any request that causes an upstream.request (e.g., a tools/list or tools/call
request), and assert the returned value is a dict with "error" and code -32603
and that p._upstream.request was invoked.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/vaara/integrations/_mcp_upstream.py`:
- Around line 104-107: The code currently uses "assert pending.response is not
None" after "if not pending.event.wait(timeout=timeout):" which can raise
AssertionError and bypass caller error handling; change this to explicitly raise
ProxyError when pending.response is None (e.g., raise ProxyError("Upstream MCP
server shut down or closed connection before responding")) so callers get a
predictable ProxyError instead of AssertionError; apply the same replacement in
the other similar block that checks pending.response (the second occurrence
around the 159-164 area) and include any useful context (timeout value or
request id) in the error message if available.

---

Nitpick comments:
In `@tests/test_integrations_mcp_proxy.py`:
- Around line 46-139: Add a smoke test that ensures _handle_request handles an
upstream shutdown by making p._upstream.request raise an exception (e.g.,
ConnectionError or the project's upstream-closed exception) while processing a
request and asserting that _handle_request returns a JSON-RPC internal error
response (error.code == -32603) instead of letting the exception propagate;
create the test in the same style as existing tests, use the proxy fixture to
get p and pipeline, set p._upstream.request.side_effect to the exception, call
p._handle_request with any request that causes an upstream.request (e.g., a
tools/list or tools/call request), and assert the returned value is a dict with
"error" and code -32603 and that p._upstream.request was invoked.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 04af745f-3f3a-44bd-baaa-587bf04b90de

📥 Commits

Reviewing files that changed from the base of the PR and between ff5f16e and 235054d.

📒 Files selected for processing (8)
  • CHANGELOG.md
  • README.md
  • clients/ts/package.json
  • pyproject.toml
  • src/vaara/__init__.py
  • src/vaara/integrations/_mcp_upstream.py
  • src/vaara/integrations/mcp_proxy.py
  • tests/test_integrations_mcp_proxy.py

Comment on lines +104 to +107
if not pending.event.wait(timeout=timeout):
raise ProxyError(f"Upstream MCP server did not respond within {timeout}s")
assert pending.response is not None
return pending.response
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 | 🔴 Critical | ⚡ Quick win

Return ProxyError on upstream shutdown instead of asserting.

If the reader thread exits, it wakes pending requests without a response. This path hits the assert at Line 106, which can raise AssertionError (or be removed with -O) and escape caller error handling. Convert this to an explicit ProxyError when no response is available.

💡 Proposed fix
         try:
             self._write(payload)
             if not pending.event.wait(timeout=timeout):
                 raise ProxyError(f"Upstream MCP server did not respond within {timeout}s")
-            assert pending.response is not None
-            return pending.response
+            if pending.response is None:
+                raise ProxyError("Upstream MCP server closed before responding")
+            if not isinstance(pending.response, dict):
+                raise ProxyError("Upstream MCP server returned non-object JSON-RPC")
+            return pending.response
         finally:
             with self._lock:
                 self._pending.pop(payload["id"], None)

Also applies to: 159-164

🤖 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_upstream.py` around lines 104 - 107, The code
currently uses "assert pending.response is not None" after "if not
pending.event.wait(timeout=timeout):" which can raise AssertionError and bypass
caller error handling; change this to explicitly raise ProxyError when
pending.response is None (e.g., raise ProxyError("Upstream MCP server shut down
or closed connection before responding")) so callers get a predictable
ProxyError instead of AssertionError; apply the same replacement in the other
similar block that checks pending.response (the second occurrence around the
159-164 area) and include any useful context (timeout value or request id) in
the error message if available.

Reader-thread exit during a pending request was setting the event without
populating response, so request() hit assert pending.response is not None.
AssertionError escapes the caller's ProxyError handler and is optimized
out under python -O. Raise ProxyError explicitly with a regression test
that simulates the reader-exit race deterministically.

Flagged by CodeRabbit on PR #100.
try:
if self._proc.stdin is not None:
self._proc.stdin.close()
except Exception:
self._proc.wait(timeout=5)
except subprocess.TimeoutExpired:
self._proc.kill()
except Exception:
@vaaraio vaaraio merged commit c6f1cca into main May 19, 2026
10 checks passed
@vaaraio vaaraio deleted the release/v0.21.0 branch May 19, 2026 14:42
vaaraio added a commit that referenced this pull request May 19, 2026
…ME section (#105)

Two problems shipped in #100 and #101 that were fixed here:

1. README's MCP proxy code example used `npx --upstream-arg @sap/adt-mcp-server`, a fictional npm package. The MCP proxy block was also buried as a third-tier subsection under Framework integrations, despite v0.21.0 being the headline ship and having two worked demos.

2. SAP demo's example config used `npx -y @mario-andreschak/mcp-abap-abap-adt-api`, which is not published on npm. The real install path for that server is git clone + npm install + npm run build, then run with `node /path/to/dist/index.js`.

Fixes:

- README: removed the buried `### MCP proxy` subsection under Framework integrations and added a top-level `## MCP proxy` section between TypeScript client and OVERT. Uses `@sap/mdk-mcp-server` (verified on npm at version 0.3.7) as the one-line example. Links both demo folders.

- examples/sap-mcp-proxy-demo/claude_code_config.example.json: primary `sap-mdk-via-vaara` block now uses the real `@sap/mdk-mcp-server` invocation. Sibling `_alternative_abap_adt` block uses the real `node /PATH_TO_YOUR/mcp-abap-abap-adt-api/dist/index.js` invocation. Sibling `_alternative_btp_odata` block clarified as clone+build.

- examples/sap-mcp-proxy-demo/README.md: prereqs section names each upstream's real install path. Before/After config blocks now show the real MDK invocation. Note steering ABAP/OData readers to the alternative blocks in the example config.

Verified upstreams via npm registry and GitHub API: `@sap/mdk-mcp-server` is published, official, Apache-2.0; `mario-andreschak/mcp-abap-abap-adt-api` is on GitHub (134 stars, MIT) but not on npm; `SAP/mdk-mcp-server` repo is Apache-2.0, 31 stars.

Co-authored-by: vaaraio <267591518+vaaraio@users.noreply.github.com>
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.

2 participants