Skip to content

feat(proxy): implement upstream MCP forwarding (live call path was a no-op) - #280

Merged
imran-siddique merged 2 commits into
feat/cedar-advice-annotationsfrom
feat/upstream-forwarding
Jun 11, 2026
Merged

feat(proxy): implement upstream MCP forwarding (live call path was a no-op)#280
imran-siddique merged 2 commits into
feat/cedar-advice-annotationsfrom
feat/upstream-forwarding

Conversation

@imran-siddique

Copy link
Copy Markdown
Member

Summary

The most critical finding from today's codebase review: the live runtime could never execute a tool call. Proxy step 3 called self._mcp_gateway.call_tool() -- a method that does not exist on AGT's MCPGateway (it was # type: ignore'd). The resulting AttributeError was swallowed by the broad except Exception branch and recorded as an AGT deny, so every call returned 403. Every test mocked the nonexistent method, which is why CI stayed green.

This PR implements the actual upstream forwarding using AGT's intended interception API:

Step What Failure behavior
3a intercept_tool_call() -- rate limit, parameter sanitization deny (audit: agt_gateway:<reason>)
3b _forward_to_upstream() -- JSON-RPC 2.0 tools/call POST to the attested catalog server.url (httpx, 30s timeout) fault audit + 502 (UPSTREAM_UNAVAILABLE / UPSTREAM_TOOL_ERROR)
3c size guard -- config.max_response_size_bytes (DOS-002, previously unenforced on egress) deny (response_size_exceeded)
3d intercept_tool_response() -- injection/credential/PII scan, honors sanitized content deny (response_blocked_by_scanner, audit injection_detected)

Session sensitivity now derives from the catalog entry's declared sensitivity_level (attested data) instead of duck-typed attributes on a mock object. Upstream failures are 502s, not policy denies.

Not in scope (follow-ups): TLS fingerprint pinning of upstream connections (catalog.server.tls_fingerprint is currently unchecked), SSE transport.

Test plan

  • New tests/unit/test_upstream_forwarding.py: real HTTP round-trip against a local mock MCP server (text extraction, JSON-RPC error -> UpstreamToolError, unreachable -> UpstreamUnavailable)
  • All gateway-seam tests migrated to the real API shape via shared wire_mock_gateway() helper
  • Full suite: 661 passed, 1 skipped

Stacked on #279 (advice support).

Generated with Claude Code

… path)

Proxy step 3 called self._mcp_gateway.call_tool(), a method that does not
exist on AGT MCPGateway -- the AttributeError was swallowed by the generic
except branch, so EVERY tool call was denied in the live runtime. All tests
mocked the nonexistent method, which is why the suite stayed green.

New pipeline, matching AGT's intended interception API:

  3a. intercept_tool_call()      pre-call check (rate limit, sanitization)
  3b. _forward_to_upstream()     JSON-RPC 2.0 tools/call POST to the attested
                                 catalog entry's server.url via httpx
  3c. size guard                 enforce config.max_response_size_bytes (DOS-002)
  3d. intercept_tool_response()  injection/credential/PII scan; honors
                                 sanitized content

Upstream failures audit as faults and return 502 (UPSTREAM_UNAVAILABLE /
UPSTREAM_TOOL_ERROR), distinct from policy denies. Session sensitivity now
derives from the catalog entry's declared sensitivity_level; injection
detail derives from scanner threat categories.

Tests migrated to the new seam via a shared wire_mock_gateway() helper, plus
live-HTTP forwarding tests against a local mock MCP server. benchmarks.py
updated to the same seam.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@imran-siddique
imran-siddique merged commit 3350da7 into feat/cedar-advice-annotations Jun 11, 2026
@imran-siddique
imran-siddique deleted the feat/upstream-forwarding branch July 29, 2026 23:19
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