{"id":49859255,"url":"https://github.com/piotrminkina/mcp-standby-proxy","last_synced_at":"2026-05-14T21:08:08.279Z","repository":{"id":353299059,"uuid":"1218288441","full_name":"piotrminkina/mcp-standby-proxy","owner":"piotrminkina","description":"Lazy-start stdio proxy for MCP servers — serves cached tool schemas instantly, starts backends only on first tools/call.","archived":false,"fork":false,"pushed_at":"2026-04-23T08:59:38.000Z","size":220,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-04-23T10:35:02.685Z","etag":null,"topics":["asyncio","mcp","model-context-protocol","proxy","python","stdio"],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"gpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/piotrminkina.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-04-22T18:13:23.000Z","updated_at":"2026-04-23T08:59:42.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/piotrminkina/mcp-standby-proxy","commit_stats":null,"previous_names":["piotrminkina/mcp-standby-proxy"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/piotrminkina/mcp-standby-proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrminkina%2Fmcp-standby-proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrminkina%2Fmcp-standby-proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrminkina%2Fmcp-standby-proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrminkina%2Fmcp-standby-proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/piotrminkina","download_url":"https://codeload.github.com/piotrminkina/mcp-standby-proxy/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/piotrminkina%2Fmcp-standby-proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33043343,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-13T13:14:54.681Z","status":"online","status_checked_at":"2026-05-14T02:00:06.663Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["asyncio","mcp","model-context-protocol","proxy","python","stdio"],"created_at":"2026-05-14T21:08:06.194Z","updated_at":"2026-05-14T21:08:08.264Z","avatar_url":"https://github.com/piotrminkina.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# mcp-standby-proxy\n\nLightweight stdio proxy for MCP (Model Context Protocol) servers that eliminates\nunnecessary backend startup when using AI agents.\n\nEach proxy instance sits between an MCP client and a real MCP server backend. It\nserves cached tool schemas instantly on startup and only starts the backend\ninfrastructure when the agent makes a real `tools/call` request. Backend lifecycle\nis controlled via configurable shell commands — the proxy is agnostic to the\nunderlying runtime (containers, service managers, bare processes).\n\n## Why\n\nMCP clients connect to **all** registered MCP servers at startup to fetch\n`tools/list`. This triggers every backend stack to start immediately — even\nwhen the user has no intention of using those tools in the current session.\n\n| Session start (before) | Session start (after) |\n|------------------------|-----------------------|\n| 15+ processes, 1.5–3 GB RAM, 30–120 s delay | 0 backend processes, ~25 MB per proxy, instant |\n| All backends running regardless of need | Backends start only when the agent calls a tool |\n\n## How it works\n\n```\nMCP Client ←stdio→ mcp-standby-proxy ←SSE/HTTP/stdio→ Real MCP Server\n                         │\n                   cache.json (tools/list response)\n```\n\n1. MCP client spawns proxy as a stdio subprocess\n2. Client sends `tools/list` → proxy responds from local cache (no backend started)\n3. Client sends `tools/call` → proxy starts the backend, waits for healthcheck, forwards the request\n4. Subsequent `tools/call` requests are forwarded directly (backend already running)\n5. On session end (SIGTERM / stdin EOF) → proxy stops the backend\n\n**Result:** Zero backend processes at session start. Backends start only when needed.\n\n## Getting Started\n\n### Prerequisites\n\n- [uv](https://docs.astral.sh/uv/) (manages Python versions automatically)\n- A running MCP server backend to proxy (e.g., a Docker Compose stack, a systemd user service, or an `npx` stdio package)\n\n### Installation\n\n```bash\ngit clone https://github.com/piotrminkina/mcp-standby-proxy.git\ncd mcp-standby-proxy\nuv sync\n```\n\n### Quick Start\n\n1. Copy the example config and edit it for your server:\n\n```bash\ncp examples/kroki.yaml my-server.yaml\n# Edit my-server.yaml: set server.name, backend.url, and lifecycle commands\n```\n\n2. Run the proxy:\n\n```bash\nuv run mcp-standby-proxy serve -c my-server.yaml\n```\n\n3. Register in your MCP client config (e.g., Claude Desktop, Claude Code):\n\n```json\n{\n  \"mcpServers\": {\n    \"my-server\": {\n      \"command\": \"uv\",\n      \"args\": [\n        \"run\",\n        \"--project\", \"/path/to/mcp-standby-proxy\",\n        \"mcp-standby-proxy\", \"serve\",\n        \"-c\", \"/path/to/my-server.yaml\"\n      ]\n    }\n  }\n}\n```\n\n## Configuration\n\nReference configs per transport:\n\n- [`examples/kroki.yaml`](examples/kroki.yaml) — **SSE** backend, systemd user service lifecycle\n- [`examples/firecrawl.yaml`](examples/firecrawl.yaml) — **Streamable HTTP** backend, Docker Compose lifecycle\n- [`examples/claude-context.yaml`](examples/claude-context.yaml) — **stdio** backend with a dependency service (Milvus) managed via `systemctl`\n\nEach example ships with an opt-in `logging` section enabled by default\n(required under Claude Code and other clients that do not persist\nchild-process stderr — see \"File logging\" below). Remove the section to\ndisable file logging.\n\nMinimal schema:\n\n```yaml\nversion: 1\n\nserver:\n  name: \"my-mcp-server\"          # Reported in MCP initialize response\n  version: \"1.0.0\"\n\nbackend:\n  transport: sse                  # sse | streamable_http | stdio\n  url: \"http://localhost:5090/sse\" # Required for sse / streamable_http\n  # For stdio backends:\n  # command: \"npx\"\n  # args: [\"@modelcontextprotocol/server-something\"]\n  # env:\n  #   FOO: \"bar\"\n\nlifecycle:\n  start:\n    command: \"systemctl\"\n    args: [\"--user\", \"start\", \"my-server.socket\"]\n    timeout: 30                   # Seconds before start command is killed\n  stop:\n    command: \"systemctl\"\n    args: [\"--user\", \"stop\", \"my-server.service\"]\n    timeout: 30\n  healthcheck:\n    type: http                    # http | tcp | command\n    url: \"http://localhost:5090/sse\"\n    interval: 1                   # Seconds between polls\n    max_attempts: 60              # Total attempts before giving up\n\ncache:\n  path: \"./my_server_cache.json\"  # Resolved against the config file's directory\n```\n\nFull schema and path-resolution rules: [Config Spec](docs/plans/config-spec.md).\n\n### Cold cache bootstrap\n\nOn first run (no cache file), the proxy starts the backend, fetches\n`tools/list`, `resources/list`, and `prompts/list`, saves the results to the\ncache file, and serves future `tools/list` requests from cache without starting\nthe backend.\n\n### Streamable HTTP transport\n\nFor backends that use the newer Streamable HTTP protocol instead of SSE:\n\n```yaml\nbackend:\n  transport: streamable_http\n  url: \"http://localhost:5100/mcp\"\n```\n\n### File logging (Claude Code and other agent-mode deployments)\n\nClaude Code does not persist child-process stderr to disk\n([anthropics/claude-code#29035](https://github.com/anthropics/claude-code/issues/29035)),\nso transport errors and lifecycle events are invisible without extra setup. Add a\n`logging.file` section to any config to write the same records to a local rotating\nlog file:\n\n```yaml\nlogging:\n  file:\n    path: \".logs/kroki.log\"\n    level: info              # raise to `debug` to reproduce a specific incident\n    max_size: \"10MB\"\n    backup_count: 3          # max disk usage: ~40 MB (active + 3 backups)\n```\n\n**How it works:**\n\n- Absence of the `logging` key disables file logging entirely — no files created,\n  no disk I/O. Stderr output is unchanged.\n- File level is independent of `-v`/`-vv`: you can run stderr at `WARNING` while\n  the file captures `DEBUG`.\n- The proxy creates intermediate directories automatically. If the path cannot be\n  opened (permission denied, read-only filesystem), a single warning is written to\n  stderr and the proxy continues with stderr-only logging.\n- On startup the proxy prints the resolved log path to stderr:\n  `file logging enabled: path=/abs/path/to/.logs/kroki.log`.\n- Rotation is size-based. `backup_count` must be ≥ 1 (setting it to `0` disables\n  rotation entirely — the stdlib ignores `max_size` when `backupCount=0`).\n- `max_size` format: `\u003cinteger\u003e\u003cunit\u003e` with no spaces.\n  Accepted units: `B`, `KB`, `MB`, `GB` (decimal) and `KiB`, `MiB`, `GiB` (binary).\n  Range: 1 KB – 10 GB.\n\nThe `logging` section is present in all three example configs under\n[`examples/`](examples) — delete it to disable file logging. For the known\ndeferred bug that motivated this feature (`Write stream closed` on SSE\nbackends), see [`TODO.md`](TODO.md).\n\n### Notes\n\n- `idle_timeout` and `auto_refresh` are accepted in the config but ignored in\n  the current version. They are reserved for post-MVP features.\n\n## CLI\n\n| Command | Description |\n|---------|-------------|\n| `serve -c \u003cconfig.yaml\u003e` | Run the proxy (stdio transport) |\n| `-v` | INFO logging to stderr |\n| `-vv` | DEBUG logging to stderr |\n\nLogs always go to stderr. Stdout is reserved exclusively for JSON-RPC traffic\nto the MCP client — piping or redirecting stdout will corrupt the protocol\nstream.\n\n## Security\n\nThe YAML config file is **trusted input**. `lifecycle.start` and\n`lifecycle.stop` are executed as arbitrary shell commands with the proxy\nprocess's full privileges, and `backend.env` is merged into the child\nprocess environment for stdio backends.\n\n**Do not run `mcp-standby-proxy` against a config file you did not author or\nreview.** A hostile YAML can execute any command when the proxy starts or\nstops the backend. This includes configs downloaded from third parties,\npasted from chat, or committed to repositories you don't control.\n\n## Project Status\n\n**MVP complete.** All three backend transports (SSE, Streamable HTTP, stdio)\nare implemented and tested.\n\nSee [PRD](docs/plans/prd.md) for the full capability matrix and requirements,\n[Tech Stack](docs/plans/tech-stack.md) for technology choices, and\n[Tech Spec](docs/plans/tech-spec.md) for architecture details.\n\n## Roadmap (post-MVP)\n\n- Idle timeout: automatically stop backend after inactivity\n- Cache auto-refresh: compare live vs cached on reconnect\n- `warm` / `validate` CLI subcommands\n- Client capability forwarding + server-to-client request forwarding\n- Nuitka binary builds for zero-dependency distribution\n\n## Contributing\n\nSee [`CONTRIBUTING.md`](CONTRIBUTING.md) for development environment setup,\ntesting, and contribution workflow.\n\n## License\n\nCopyright (C) 2026 Piotr Minkina\n\nThis program is free software: you can redistribute it and/or modify it\nunder the terms of the GNU General Public License as published by the\nFree Software Foundation, either version 3 of the License, or (at your\noption) any later version.\n\nThis program is distributed in the hope that it will be useful, but\nWITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General\nPublic License for more details.\n\nFull license text: [`LICENSE`](LICENSE) or \u003chttps://www.gnu.org/licenses/gpl-3.0.html\u003e.\n\n### Why GPLv3\n\n`mcp-standby-proxy` is a developer tool that sits between an MCP client and\na backend. Both talk to the proxy through arms-length boundaries (stdin/stdout\npipes, HTTP sockets) — under established FSF interpretation, using the proxy\ndoes NOT impose GPL on the MCP client, the backend, or anything else that\nmerely communicates with it. GPLv3 covers only the proxy itself and any\n**derivative works** (forks, embedded copies). The intent is reciprocity: if\nyou improve the proxy and distribute that improved version, those improvements\nstay open for everyone.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrminkina%2Fmcp-standby-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpiotrminkina%2Fmcp-standby-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpiotrminkina%2Fmcp-standby-proxy/lists"}