DynamicMCPProxy
# Dynamic MCP Proxy
A smart MCP proxy server that lazily loads relevant MCP tool servers based on your project context, keeping AI tool counts within recommended limits (≤ 50 tools for Google Antigravity). Designed specifically for high-autonomy environments where agents need to evolve their own toolsets.
Part of the [Anti-Gravity Agents Prompt Protocol](https://github.com/SPhillips1337/AntigravityAgentsPromptProtocol) ecosystem.
> **This project is a working, client-agnostic solution to [anthropics/claude-code#7336 — *Feature Request: Lazy Loading for MCP Servers and Tools*](https://github.com/anthropics/claude-code/issues/7336).** See [Related Work](#related-work--problem-context) below.
## How It Works
```
IDE connects → proxy exposes proxy_* tools + MCP Resources + Prompts
AI calls proxy_handshake({ tech_stack, task_description })
→ Matcher scores catalogue entries
→ Top-5 servers activated (lazily mounted as stdio subprocesses or SSE)
→ tools/list now includes those servers' tools
→ Budget cap (50 tools) enforced via LRU eviction
```
## Quick Start
### Prerequisites
Catalogue servers run via `npx` (Node.js) and `uvx` (uv). These must be on the PATH your IDE uses when spawning the proxy. Add them explicitly in the MCP config `env` block (see below). Find your paths with `type npx` and `type uvx`.
### Install
Install with the hardened installer (clone-or-install-in-place):
```bash
curl -fsSL https://raw.githubusercontent.com/SPhillips1337/DynamicMCPProxy/main/install.sh -o install.sh
chmod +x install.sh
./install.sh --dir "$HOME/DynamicMCPProxy"
```
If you already cloned the repository, run the installer from the project root:
```bash
./install.sh
```
The installer validates that an existing target directory is this repository before updating it, checks for required tools (`git` and `uv`), runs `uv sync`, and creates `proxy_config.json` from `proxy_config.json.example` when needed. For inspect-first installs, download and review the script before executing it; avoid piping remote scripts directly into a shell unless you trust the source.
Manual install remains:
```bash
uv sync
```
### Configure
Copy the example config:
```bash
cp proxy_config.json.example proxy_config.json
```
Set up your environment and private catalogue:
```bash
cp .env.example .env # fill in your API keys
# user.catalogue.json is auto-created or copy from your IDE's mcp_config.json
```
### Add to your IDE (Antigravity / opencode / Claude Desktop)
```json
{
"mcpServers": {
"dynamic-proxy": {
"command": "uv",
"args": [
"run",
"--quiet",
"--project",
"/path/to/dynamic-mcp-proxy-server",
"python", "-m", "src.proxy_server"
],
"env": {
"PATH": "/home/user/.nvm/versions/node/v20.18.1/bin:/home/user/.local/bin:/usr/local/bin:/usr/bin:/bin"
}
}
}
}
```
Adjust `PATH` to match your system (`type npx` and `type uvx` show the right directories).
## proxy_* Tools
| Tool | Description |
|---|---|
| `proxy_handshake(tech_stack, task_description, ...)` | Context handshake — activates relevant servers |
| `proxy_list_active_servers()` | Currently mounted servers + tool counts |
| `proxy_list_available_servers(filter_tag?)` | Browse catalogue |
| `proxy_activate_server(name, eager?)` | Mount a server (deferred by default) |
| `proxy_activate_from_spec(name, url, type?, lean?)` | Generate & mount server from OpenAPI/GraphQL (lean=True uses LAP for smaller specs) |
| `proxy_deactivate_server(name)` | Free up tool budget |
| `proxy_add_custom_proxy(name, url, tags, runtime)` | Add an ad-hoc server (SSE/HTTP only) |
| `proxy_list_tools(server_name?)` | List exact names of all mounted tools |
| `proxy_inspect_registry()` | Diagnostic: Full dump of current tool registry |
| `proxy_get_metrics()` | Live memory/CPU/uptime metrics |
| `proxy_get_usage()` | Server usage counts for self-evolving ranking |
| `proxy_reset_usage(server?)` | Reset usage stats (for testing/re-baselining) |
## MCP Resources
| URI | Description |
|---|---|
| `mcp://proxy/info` | Static metadata (version, capabilities) |
| `mcp://proxy/health` | Live health (uptime, memory, active tools) |
| `mcp://proxy/servers` | Full server inventory (active + available) |
## MCP Discovery Surface
| MCP Method | What the AI Sees |
|---|---|
| `tools/list` | Minimal `proxy_*` management tools |
| `resources/list` + `resources/read` | Live health, proxy info, server inventory |
| `prompts/list` | `suggest_tools_for_context` guided workflow |
## Catalogue
`catalogue.json` — 49 public MCP servers (GitHub, Docker, Postgres, Slack, Stripe, etc.).
`user.catalogue.json` — your private overlay (gitignored). Add personal servers here — local paths, private APIs, custom tools. Entries with the same name override the public catalogue.
```json
[
{
"name": "my-server",
"description": "My private MCP server",
"command": "python /path/to/server.py",
"tags": ["custom"],
"tech_stack": ["any"],
"runtime": "stdio",
"env_vars": ["MY_API_KEY"],
"pick": ["id", "status"],
"token_budget": 500
}
]
```
### Response Steering
Optimize AI context usage by shaping server responses before they reach the LLM. Applied to any server (stdio, SSE, REST):
- **`pick`**: Array of dot-notation paths to keep (all others dropped).
- **`omit`**: Array of paths to remove.
- **`template`**: Python-style format string (e.g., `"{id}: {content}"`) to flatten complex JSON into readable text.
- **`token_budget`**: Hard character cap (approx tokens * 4) to prevent context flooding.
### REST Bridge Support (via 40mcp)
The proxy supports `runtime: "rest"`, allowing it to act as a bridge for any OpenAPI or GraphQL API.
1. **Auto-Generation**: Use `proxy_activate_from_spec(name, url)` to generate a server config in `./configs/` and mount it instantly.
2. **Manual Config**: Add an entry with `"runtime": "rest"` and `"config_path": "configs/mysvc.json"`. The proxy uses the `40mcp` engine to map MCP tool calls to REST/GraphQL requests.
## Environment Variables
`.env` (gitignored) is loaded automatically at startup. Copy `.env.example` to get started:
```bash
cp .env.example .env
```
Keys follow the `env_vars` field in each catalogue entry. Values in real environment variables always take precedence over the `.env` file.
## Configuration
`proxy_config.json` (gitignored, auto-generated with safe defaults):
```json
{
"tool_budget": 50,
"auth_enabled": false,
"guardrails_enabled": true,
"rate_limit_rpm": 120,
"catalogue_path": "catalogue.json",
"audit_log_path": "audit.log",
"receipts_enabled": true,
"otel_enabled": false,
"otel_service_name": "dynamic-mcp-proxy",
"otel_exporter_otlp_endpoint": null,
"otel_capture_content": false
}
```
Key settings:
- `tool_budget` — max tools exposed at once (default 50, matches Antigravity limit)
- `auth_enabled` — JWT RS256 + HMAC API key auth for production use
- `guardrails_enabled` — prompt-injection scanning + result size caps
- `receipts_enabled` — write JSONL receipts to `audit_log_path` with `run_id`, `span_id`, caller, status, latency, server/tool names, and privacy-safe argument summaries
- `otel_enabled` — optionally mirror receipt events into OpenTelemetry when `opentelemetry-*` packages are installed; JSONL receipts continue to work without those optional packages
- `otel_capture_content` — defaults to `false`; do not capture raw prompts, customer content, API keys, or tool payloads in traces by default
## Receipts and Traceability
DynamicMCPProxy is the right place to collect agent receipts because every server activation and child MCP tool call passes through it. Receipts are enabled by default as local JSONL in `audit.log` and are designed for security-first debugging:
- Every record includes a `run_id` and `span_id`; related operations can pass a parent span where available.
- HMAC/JWT callers are identified by auth type, not by secret value. For example, sidecar HMAC calls record `service:hmac`.
- Tool arguments are summarized as keys, types, lengths, and SHA-256 fingerprints. Raw strings, nested payloads, passwords, tokens, cookies, and API keys are not logged.
- OpenTelemetry export is optional and dependency-optional. If enabled without the OTel packages installed, the proxy logs one warning to stderr and continues with JSONL receipts.
Example JSONL record shape:
```json
{
"ts": "2026-07-04T12:00:00+00:00",
"event_type": "mcp.tool.call",
"run_id": "run_...",
"span_id": "...",
"tool": "github_search",
"caller": "service:hmac",
"outcome": "ok",
"latency_ms": 42.1,
"server": "github",
"argument_keys": ["query"],
"redacted_args": {
"query": {"type": "str", "length": 12, "sha256": "..."}
}
}
```
## Security
When `auth_enabled = true`:
- JWT (RS256) — set `jwt_public_key_path` to your RSA public key PEM
- HMAC API key — set `hmac_api_key` (passed via `X-API-Key` header)
- Guardrails — 8 prompt-injection pattern checks on all tool descriptions
- Audit log / receipts — every tool call logged to `audit.log` (JSON lines) with privacy-safe argument fingerprints, not raw secrets or payloads
- Rate limiting — configurable RPM per caller
## Hot-Plug Plugins
Drop any executable MCP server script into `./plugins/`. The proxy detects it via watchdog and registers it live — no restart needed.
## Autonomous Tooling & Hot-Swap
The real power of this proxy lies in **Runtime Mutation**. Unlike static MCP configurations, this proxy allows an agent to "evolve" its toolbox as the task progresses:
1. **Just-in-Time Activation**: A2A (Agent-to-Agent) workflows that run for hours can activate `sequential-thinking` only when hitting a complex logic gate, then swap it for `docker` or `terraform` during the execution phase.
2. **Autonomous Bootstrap**: An agent can research a new tool, configure its environment via `proxy_add_custom_proxy`, and begin using it immediately without human intervention.
3. **Self-Correction**: If a tool is missing, an agent can literally write a new MCP server to `./plugins/` and the proxy will hot-plug it instantly.
4. **Token Sustainability**: By keeping the active toolset lean (via LRU eviction), long-running agents avoid context-window saturation and maintain peak focus on the task.
## Update the Public Catalogue
```bash
uv run python scripts/sync_catalogue.py
```
## Run Tests
```bash
uv run pytest tests/ -v
```
## Optional HTTP Endpoint
Disabled by default. Enable with `ENABLE_HTTP_SIDECAR=1`:
```bash
curl -X POST http://localhost:8765/handshake \
-H "Content-Type: application/json" \
-d '{"tech_stack": ["python", "fastapi"], "task_description": "Building a REST API"}'
```
## Long-Term Memory
This project uses the Anti-Gravity LTM protocol. The repo ships canonical memory in `docs/memories/`. Per-developer context lives in `.antigravity/memories/` (gitignored):
- `patterns_and_lessons.md` — solved problems, failure post-mortems
- `codebase_insights/` — module-level hidden knowledge
- `architectural_decisions/` — design tradeoffs and rationale
Bootstrap your local LTM by following [BOOTSTRAP.md](https://github.com/SPhillips1337/AntigravityAgentsPromptProtocol/blob/main/BOOTSTRAP.md) from the protocol repo. See `AGENTS.md` for the full agent protocol.
## Repository Documentation
- `README.md` — user overview and getting started
- `CONTEXT.md` — concise operating manual (stack, rules, decisions, guidance)
- `AGENTS.md` — agent behavior, workflow rules, and LTM protocol (HERMES equivalent)
- `docs/memories/` — living memory: patterns, insights, architectural decisions
- `research/` — external references, comparisons, and notes (not source of truth)
## Related Work & Problem Context
[anthropics/claude-code#7336](https://github.com/anthropics/claude-code/issues/7336) documented a real problem: loading all MCP servers at session startup can consume **54 % of the available context window** (~108k of 200k tokens) before a single message is sent. Several approaches have been proposed or built:
| Project | Approach | Limitation |
|---|---|---|
| [machjesusmoto/claude-lazy-loading](https://github.com/machjesusmoto/claude-lazy-loading) | Offline registry generator — produces a lightweight token index from your MCP config | No runtime injection; explicitly lists *"Automatic lazy loading at runtime"* as needing Claude Code support |
| [block-town/mcp-gateway](https://github.com/block-town/mcp-gateway) | Replaces all tools with 3–4 generic `gw(service, tool, args)` shim tools; dispatches at call time | Hard-coded, requires fork-and-edit per stack; the AI loses full tool type-safety and discovery |
| **This project** | Smart proxy that activates only the servers relevant to the current project context via `proxy_handshake()`, enforces a tool budget via LRU eviction, and is fully dynamic at runtime | Works with *any* MCP client today — no IDE changes required |
### Why the MCP layer is the right place to solve this
1. **Client-agnostic** — the proxy handles lazy loading transparently for any MCP client (Claude Code, Windsurf, Antigravity, opencode, Claude Desktop…), not just one IDE.
2. **`proxy_handshake()` already delivers the "After" UX from the issue** — the feature request's ideal example shows `> Auto-loading: context7, magic [+3.5k tokens]` after detecting keywords in user input. That is exactly what `proxy_handshake({ tech_stack, task_description })` does today.
3. **No fork required** — add servers to `catalogue.json` or `user.catalogue.json`; the matcher and budget enforcement are automatic.
## Inspiration
- [FastMCP dynamic proxy pattern](https://dev.to/amartyadev/building-a-dynamic-mcp-proxy-server-in-python-16jf)
- [mcp-scan guardrail design](https://github.com/invariantlabs-ai/mcp-scan)
- [Anti-Gravity Agents Prompt Protocol](https://github.com/SPhillips1337/AntigravityAgentsPromptProtocol)
TDQS
Scored across 13 tools
Each tool has a clearly distinct purpose: handshake, listing servers, searching, activation/deactivation, spec generation, metrics, usage tracking, etc. No two tools appear to overlap in functionality.
All tools follow the 'proxy_verb_noun' pattern with consistent snake_case. Verbs like 'list', 'activate', 'get', 'reset' are used uniformly, making the naming predictable.
13 tools is well-scoped for a proxy management server, covering setup, discovery, activation, inspection, and usage tracking without being overwhelming or too sparse.
The tool surface covers the core workflows (handshake, list, search, activate, deactivate, add custom, metrics, usage). A minor gap is the lack of a tool to get detailed server info (beyond counts), but overall it's quite complete.