Skip to main content
Glama
trym-s

Lazy MCP Broker

by trym-s

Lazy MCP Broker

A localhost MCP lifecycle broker that keeps reviewed tool schemas available while scaling expensive stdio MCP servers and browsers to zero when idle.

This repository is a deliberately narrow reference implementation for a hard operational case: several long-lived AI clients share browser-heavy MCP servers across WSL and Windows, but those servers must not stay resident, leak processes, or lose session isolation.

Status: working reference deployment, not a drop-in universal gateway. The checked-in config.json and systemd unit contain deployment-specific absolute paths. Replace them before using this on another machine.

The problem

Registering Playwright, Chrome DevTools, and similar MCP servers directly in every agent process creates several problems:

  • every client starts or owns its own expensive backend;

  • Chromium, Windows Chrome, Node, and receiver processes can survive cancellation or transport failure;

  • browser state must be isolated per MCP session while some backends must remain singleton;

  • a health endpoint can be green even when npx, the browser executable, or the real MCP call path is broken;

  • killing processes by executable name is unsafe on a workstation with unrelated Chrome, Node, Codex, or browser sessions;

  • keeping all heavy servers alive wastes memory and process slots even when only their tool schemas are needed.

Lazy MCP Broker keeps one small HTTP service alive, serves validated cached tool catalogs without starting upstreams, and starts only the backend that receives a real tools/call. After a bounded idle period, that backend and every process it owns must return to zero.

Related MCP server: wsl-mcp

Why build this when MCP gateways already exist?

There are good existing projects. Use them when their model fits:

Project

What it is good at

Why this repository still exists

Docker MCP Gateway

Broad, container-oriented MCP orchestration, isolation, credentials, policy, logging, and lifecycle management.

This deployment must supervise host processes on both sides of WSL, including Windows Chrome identified through Win32_Process. It needs exact generation ownership and workstation-safe cleanup rather than making containers the ownership boundary.

MetaMCP

A full aggregator, orchestrator, middleware, and gateway with namespaces and a management surface.

That is a broader control plane. This project is intentionally a small localhost process supervisor with fixed reviewed configuration, stable per-backend URLs, no database, and no remote multi-user surface.

mcp-proxy

Bridging stdio MCP servers to Streamable HTTP/SSE and connecting between MCP transports.

Transport conversion alone does not provide this broker's per-session/singleton scopes, bounded FIFO dispatch, idle scale-to-zero, generation invalidation, or exact cross-OS process cleanup.

RaiAnsar/mcp-gateway

Reducing schema/context cost by exposing four gateway tools and loading selected servers on demand.

Its proxy-tool model changes how the model discovers and calls tools (gateway_load_server, gateway_call_tool). This broker preserves each upstream's reviewed native tool schema and stable MCP endpoint, so normal tool routing still works without gateway-tool hints. It also shuts idle backends down and verifies their owned processes are gone.

So the claim is not “other gateways do not work.” The missing combination for this deployment was:

  1. native cached schemas at stable HTTP MCP endpoints;

  2. no heavy process on initialize or tools/list;

  3. per-session browser isolation plus singleton backends;

  4. bounded queues, calls, cancellation, shutdown, and recovery;

  5. fail-closed ownership cleanup across Linux/WSL and Windows;

  6. an installed-service probe that makes real tool calls and then proves scale-to-zero.

If you need a general enterprise gateway, start with Docker MCP Gateway or MetaMCP. If you only need transport conversion, use mcp-proxy. If replacing native tools with a small lazy gateway API is acceptable, RaiAnsar's mcp-gateway is simpler. This project is for the narrower lifecycle and process ownership problem above.

Architecture

Hermes CLI 1 ─┐
Hermes CLI 2 ─┼─ Streamable HTTP → broker :8788
Gateway ──────┘                       │
                                      ├─ Playwright MCP + Linux Chromium
                                      │    per session, lazy
                                      ├─ Chrome DevTools MCP + Windows Chrome
                                      │    per session, lazy
                                      └─ Agentation MCP
                                           singleton, lazy

The request flow is:

initialize / tools/list
  → validate and return cached catalog
  → do not start an upstream process

tools/call
  → validate the tool against the reviewed catalog
  → resolve the session or singleton backend instance
  → enqueue within a bounded FIFO
  → cold-start one upstream generation if needed
  → execute with a deadline
  → return the MCP result

idle / cancellation / timeout / crash / service shutdown
  → stop accepting work for the affected generation
  → bounded graceful MCP close
  → exact-generation POSIX cleanup
  → exact-marker Windows cleanup when configured
  → re-query ownership
  → stopped only when owned-process count is zero

Lifecycle invariants

Generation isolation

Every cold start increments a generation. Async callbacks capture the instance and generation, so stale work from generation N cannot mutate generation N+1. A timeout, cancellation, or transport failure invalidates the active generation before recovery.

Fail-closed cleanup

A backend cannot silently move to stopped when cleanup is uncertain:

graceful close
→ POSIX exact-generation cleanup
→ Windows exact-marker query/terminate/re-query
→ zero verified: stopped
→ query failure or survivors: cleanup_failed

cleanup_failed blocks new calls, previously queued calls, and replacement generations. Graceful close has a sub-budget so it cannot consume the entire stop deadline and prevent ownership cleanup.

Safe process ownership

Linux/WSL children carry exact LAZY_MCP_INSTANCE_ID and LAZY_MCP_GENERATION environment markers. Their start identity is revalidated immediately before signaling; pidfd is used when available.

Windows Chrome generations use an exact broker-generated command-line marker:

hermes-lazy-mcp\chrome-{instance_id}-{generation}

The broker queries Win32_Process, retains bounded process identity fields, revalidates PID plus creation timestamp before termination, and re-queries until zero or deadline. It never runs broad taskkill, pkill chrome, or name-matched Node cleanup.

Hard evidence deadline

The synthetic probe places blocking systemd and PowerShell evidence collection behind a dedicated process-group boundary. If the monotonic deadline expires, the whole helper group is killed and reaped. Cancelling a worker thread is not accepted as timeout proof.

Current reference backends

Backend

Scope

Idle timeout

Queue limit

Upstream

Playwright

per MCP session

120 s

32

@playwright/mcp@0.0.79

Chrome DevTools on Windows

per MCP session

120 s

32

chrome-devtools-mcp@1.8.0

Agentation

singleton

60 s

64

agentation-mcp@1.2.0

Package versions and catalogs are pinned so an upstream schema change cannot silently expose a new mutating tool.

Endpoints

  • http://127.0.0.1:8788/playwright/mcp

  • http://127.0.0.1:8788/chrome-devtools-win/mcp

  • http://127.0.0.1:8788/agentation/mcp

  • http://127.0.0.1:8788/health

  • http://127.0.0.1:8788/ready

  • http://127.0.0.1:8788/status

/health proves event-loop liveness. /ready proves validated configuration, catalogs, and running supervisors without launching a browser. Neither endpoint claims that the real upstreams work. Use the synthetic probe for that.

Requirements

The reference deployment currently assumes:

  • Python 3.11+;

  • uv or another Python environment manager;

  • Node.js and npx;

  • Linux with systemd user services;

  • WSL interop and Windows PowerShell for the Windows ownership backend;

  • installed Linux and Windows Chromium executables.

The core broker can run without Windows ownership mode, but the checked-in production configuration is specifically a WSL + Windows Chrome deployment.

Setup

1. Install Python dependencies

git clone https://github.com/trym-s/lazy-mcp-broker.git
cd lazy-mcp-broker
uv sync

2. Adapt the reference configuration

Edit config.json and replace all machine-specific paths:

  • Linux npx path;

  • Linux Chromium executable;

  • Windows home directory;

  • Windows Chromium executable;

  • catalog paths if the working directory changes.

Do not weaken the Windows ownership marker or replace it with executable-name matching.

3. Validate locally

PYTHONPATH=src uv run pytest -q
PYTHONPATH=src uv run python -m lazy_mcp_broker.server --config config.json

In another shell:

curl --fail http://127.0.0.1:8788/ready
curl --fail http://127.0.0.1:8788/status

4. Install the user service

deploy/hermes-lazy-mcp-broker.service is a reference unit with absolute paths. Copy it only after editing Environment=PYTHONPATH and ExecStart:

mkdir -p ~/.config/systemd/user
cp deploy/hermes-lazy-mcp-broker.service \
  ~/.config/systemd/user/hermes-lazy-mcp-broker.service
systemctl --user daemon-reload
systemctl --user enable --now hermes-lazy-mcp-broker.service

Then point MCP clients at the stable endpoint URLs above.

Verification

Fast suite

PYTHONPATH=src uv run pytest -q
PYTHONPATH=src uv run python -m unittest scripts/test_real_harness.py -v

Installed-service synthetic proof

PYTHONPATH=src uv run python scripts/synthetic_probe.py \
  --base http://127.0.0.1:8788 \
  --output runtime-evidence/synthetic-latest.json

The probe:

  1. requires a clean baseline;

  2. makes harmless real Playwright, Windows Chrome DevTools, and Agentation calls;

  3. observes the exact Chrome generation marker while active;

  4. waits only the configured idle-plus-stop bound;

  5. requires zero active and queued calls;

  6. requires zero non-idle backend instances;

  7. requires zero heavy broker-owned Linux processes;

  8. requires zero Windows processes for every recorded marker;

  9. requires unchanged broker PID and restart count.

The most recent controlled implementation run passed 47 tests, the harness suite, an exact-marker Windows decoy test, all three real backend calls, and final scale-to-zero. Raw runtime evidence is intentionally ignored by Git because it is machine-specific. See docs/REAL_WORLD_PROOF.md for the recorded acceptance evidence and its limitations.

Refreshing cached catalogs

After intentionally upgrading an upstream MCP package:

PYTHONPATH=src uv run python scripts/refresh_real_catalogs.py

Review every catalog diff before restarting the broker. A catalog includes the package name, pinned version, generation timestamp, schema digest, enabled tools, and explicitly excluded tools. Startup rejects catalog tampering or mismatch.

Security boundaries

  • The server is loopback-only and rejects unexpected Host/Origin values.

  • Backend commands, arguments, environment additions, and deadlines come from reviewed configuration, never client input.

  • Browser profiles are headless and isolated.

  • Chrome DevTools usage statistics and CrUX requests are disabled.

  • Network headers returned by Chrome DevTools are redacted.

  • Status and lifecycle logs omit raw arguments, credentials, URLs, page content, backend output, and full process command lines.

  • Queues are bounded; overload is explicit rather than unbounded memory growth.

  • Cleanup targets exact generation ownership, never global executable names.

This is not a remotely exposed multi-user gateway. There is no authentication layer because the broker is designed to bind only to 127.0.0.1. Do not expose port 8788 to a LAN or the internet without adding a real authentication and authorization boundary.

Known limitations

  • config.json and the systemd unit are deployment-specific references, not an installer-generated portable configuration.

  • The synthetic probe is currently specialized for the three reference backends.

  • Cached catalogs must be refreshed deliberately after upstream upgrades.

  • A literal Windows/WSL reboot acceptance run has not yet been recorded; service restart and user-service enablement have been verified.

  • A Hermes Agent one-shot shutdown path can emit _watch_stdio_children was never awaited; it is upstream of this broker and remains unresolved here.

Documentation

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

No tool schema history has been recorded yet.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    MCP server for SSH and local terminal access. Supports interactive commands, long-running processes, and TUI apps like tmux/zellij
    6
    3
    MIT
  • A
    license
    Not graded
    quality
    D
    maintenance
    A MCP terminal management server with persistent sessions and terminal polling, optimized for Windows + WSL environments.
    15
    1
    MIT
  • A
    license
    Not graded
    quality
    B
    maintenance
    Enables multiple Claude Code sessions to share a single HTTP MCP endpoint, lazily spawning stdio MCP servers only when their tools are called, reducing idle processes and memory usage.
    MIT

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/trym-s/lazy-mcp-broker'

If you have feedback or need assistance with the MCP directory API, please join our Discord server