Skip to content

feat: add --proxy mode for transparent HTTP MCP interception#19

Merged
nikitaclicks merged 25 commits into
mainfrom
feat/mcp-proxy-mode
Apr 14, 2026
Merged

feat: add --proxy mode for transparent HTTP MCP interception#19
nikitaclicks merged 25 commits into
mainfrom
feat/mcp-proxy-mode

Conversation

@nikitaclicks
Copy link
Copy Markdown
Owner

@nikitaclicks nikitaclicks commented Apr 13, 2026

Summary

  • Adds --proxy <url> flag to context-cutter-mcp — makes it a transparent stdio↔HTTP proxy in front of any HTTP MCP server
  • Large tool responses (≥ --proxy-threshold, default 2 KB) are stored as handles and replaced with a compact preview; query_handle is injected alongside the upstream tools
  • --proxy-token-file delegates auth to a token file (e.g. Claude Code's saved OAuth token), with automatic browser-based re-authentication via OAuth 2.0 + PKCE when the token expires
  • MCP server starts immediately so the client handshake completes at once; upstream OAuth + tool discovery runs in the background
  • Normal mode (no flags) is completely unchanged

Usage

{
  "clickup": {
    "command": "npx",
    "args": [
      "-y", "context-cutter-mcp",
      "--proxy", "https://mcp.clickup.com/mcp",
      "--proxy-token-file", "~/.claude/clickup-token"
    ]
  }
}

With explicit auth header instead of a token file:

{ "args": ["...", "--proxy-header", "Authorization: Bearer $TOKEN"] }

What Claude sees for large responses

[context-cutter] Response stored (18.3 KB → handle: hdl_a1b2c3d4e5f6)

Preview:
  id: "86cxyz123"
  name: "Fix login bug"
  status: "in progress"
  assignees: Array[2]
  custom_fields: Array[8]

Call query_handle("hdl_a1b2c3d4e5f6", "$.field") to extract specific fields.

CLI flags added

Flag Default Description
--proxy <url> Upstream HTTP MCP URL
--proxy-threshold <bytes> 2048 Interception threshold
--proxy-header <K: V> Extra upstream header (repeatable)
--proxy-token-file <path> Bearer token file; re-read per request, auto-OAuth on expiry

Test plan

  • cargo test — all Rust unit tests pass
  • pytest — Python tests pass including 2 new integration tests (mock HTTP MCP server)
  • Normal mode (context-cutter-mcp with no flags) unchanged
  • --help shows all proxy flags
  • Tested against a real HTTP MCP endpoint — large responses intercepted, query_handle extracted specific fields

🤖 Generated with Claude Code

nikitaclicks and others added 25 commits April 13, 2026 13:59
…_mode()

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ndshake

Adds ProxyServer with upstream tool merging, query_handle_tool_def()
for advertising the local tool, and init_proxy_server() async fn that
runs the MCP initialize + tools/list handshake with the upstream server.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Implements list_tools and call_tool for ProxyServer: local query_handle
dispatch + upstream forwarding via spawn_blocking. Adds text_content
helper and intercept_if_large stub (Task 5 placeholder).

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replace intercept_if_large stub with real implementation that stores large
responses as handles and returns a preview with build_preview_text/format_preview_value.
Add 5 TDD unit tests covering passthrough, interception, and preview formatting.
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Replaces the inline prefix check with is_localhost_proxy_url(), which validates
that the character immediately after "http://localhost" or "http://127.0.0.1" is
a port separator, path, query, fragment, or end-of-string — blocking URLs like
http://localhost.attacker.com from matching.
Adds two pytest integration tests (marked `integration`) that spin up
an in-process HTTP MCP mock server, run the binary in proxy mode, and
verify large responses are intercepted with a handle+preview while
small responses pass through unchanged. Also registers the `integration`
marker in pyproject.toml.

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
…ground

Claude Code was killing the proxy because it got no MCP response while
the proxy was blocked waiting for OAuth. Now the server accepts stdio
right away; list_tools/call_tool wait on a Notify that fires once the
upstream handshake (and any browser OAuth flow) completes.
@nikitaclicks nikitaclicks marked this pull request as ready for review April 14, 2026 12:01
@nikitaclicks nikitaclicks merged commit 4ce52c2 into main Apr 14, 2026
6 checks passed
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