npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

immorterm-mcp-gateway

v0.1.0

Published

Singleton MCP gateway — run all stdio MCP servers as shared processes, expose via Streamable HTTP

Downloads

127

Readme

immorterm-mcp-gateway

The third ImmorTerm service — persistent, shared MCP servers for Claude Code.

ImmorTerm makes your development environment survive crashes. The terminal service (C binary) keeps your shell sessions alive. The memory service (Docker stack) keeps your AI's memory alive. The MCP gateway keeps your MCP servers alive — and shared across all Claude sessions, eliminating the massive per-session process duplication.

Result: 96% memory reduction (11.7 GB → 56 MB), 97% process reduction (339 → 10).

In Simple Words

The problem you didn't know you had: Every time you open a Claude Code session, it quietly launches a copy of every MCP tool you have — search, code analysis, browser, etc. Open 20 sessions over a workday and you now have 300+ invisible processes eating 12 GB of RAM. Your laptop gets slow. Things start crashing. You blame Chrome.

The simple fix: Instead of 20 copies of each tool, run one copy and let everyone share it. That's what this gateway does — it sits in the middle, takes requests from all your Claude sessions, and routes them to a single set of shared tools.

What you get: Your 300 processes become 10. Your 12 GB becomes 56 MB. Your laptop stops sweating. And you didn't have to change how you use Claude at all — it's completely invisible.

Why This Exists

Claude Code spawns every MCP server as a stdio child process — per session. Open 21 Claude sessions with 7 configured MCP servers, and you get:

| Component | Count | Memory | |-----------|-------|--------| | npm exec wrappers | 148 | 6.1 GB (41 MB each, sitting idle) | | Node.js MCP servers | 147 | 4.9 GB | | Python/uvx tools | 42 | 0.4 GB | | Total | 339 | 11.7 GB |

The npm exec wrappers are the worst offender — each is a full Node.js process (41 MB) that just spawns the actual binary and waits. That's 6.1 GB of pure waste.

We discovered this while investigating OOM crashes that were killing VS Code entirely. The system hit 52 GB usage with 10 GB swap. MCP servers were the single largest contributor.

Existing tools like supergateway can't solve this — it broadcasts MCP responses to ALL connected clients instead of routing to the originating client, breaking any shared process scenario.

How It Works

The gateway is a transparent HTTP-to-stdio JSON-RPC proxy. It is not an MCP server itself — the child processes are the real MCP servers. The gateway just routes traffic.

Claude Session 1 ─┐
Claude Session 2 ─┤  POST /:server/mcp
Claude Session 3 ─┤─────────────────────→ Gateway (port 9100)
Claude Session N ─┘                           │
                                              ├─→ [shared] context7 (1 child)
                                              ├─→ [shared] tavily (1 child)
                                              ├─→ [shared] magic (1 child)
                                              ├─→ [stateful] serena (N children)
                                              └─→ [stateful] playwright (N children)

On startup:

  1. Reads ~/.claude.json and finds all type: "stdio" MCP server entries
  2. Creates an atomic backup at ~/.immorterm/mcp-gateway/config-backup.json
  3. Rewrites each stdio entry to type: "http" pointing at http://localhost:9100/<name>/mcp
  4. Writes a _mcp_gateway_ts timestamp to trigger Claude Code's config reload
  5. Watches the config file for hot-reload (new servers added via claude mcp add are wrapped automatically)

On shutdown (or crash recovery), the original stdio configs are restored from backup.

After the gateway is running:

| Component | Count | Memory | |-----------|-------|--------| | Gateway process | 1 | ~20 MB | | Shared child processes | 9 | ~36 MB | | Total | 10 | ~56 MB |

Stateless vs Stateful

Not all MCP servers can be shared. The gateway classifies them:

  • Stateless (default): One shared child process for all sessions. Requests are multiplexed via JSON-RPC IDs. Safe for servers that don't maintain conversational state (search APIs, code lookup, etc.)
  • Stateful: One child process per MCP session. Required for servers that track state across requests (sequential-thinking, browser automation, etc.)

Built-in stateful servers: sequential-thinking, serena, playwright, chrome-devtools, puppeteer.

You can override classification in ~/.immorterm/mcp-gateway/classification.json:

{
  "my-custom-server": { "mode": "stateful" },
  "another-server": { "mode": "stateless", "requestTimeout": 30000 }
}

npx Resolution

The biggest memory win. When Claude configures:

{ "command": "npx", "args": ["-y", "@upstash/context7-mcp"] }

Each session spawns an npx process (41 MB) that spawns the actual server (34 MB). The gateway resolves the binary path once, caches it (7-day TTL), and spawns the binary directly — eliminating the npm exec wrapper entirely. This alone saves 6.1 GB across 21 sessions.

The ImmorTerm Service Pattern

Like the other ImmorTerm services, the gateway follows a key design principle: the extension orchestrates, but the service is independent.

  • Runs as a detached Node.js process — survives VS Code and Extension Host crashes
  • Managed via PID file locking — prevents duplicate instances
  • Auto-recovery with exponential backoff (30s → 60s → 120s cap) if the process dies
  • Extension does NOT stop the gateway on deactivate() — it persists deliberately
  • Crash recovery restores ~/.claude.json from backup if the gateway dies uncleanly

This mirrors how the immorterm terminal binary (C/screen fork) survives VS Code restarts and how the memory Docker stack persists independently of the Extension Host.

Installation

cd immorterm-mcp-gateway
npm install
npm run build

The gateway is designed to be managed by the ImmorTerm VS Code extension. Enable it per-project via the ImmorTerm setup wizard (Step 3: MCP Gateway). It can also be run standalone.

CLI

immorterm-mcp-gateway start              # Start (detached)
immorterm-mcp-gateway start --foreground  # Start in foreground (debugging)
immorterm-mcp-gateway start --port 9200   # Custom port (default: 9100)
immorterm-mcp-gateway start --config PATH # Custom claude.json path
immorterm-mcp-gateway stop                # Stop gracefully (restores config)
immorterm-mcp-gateway status              # Show status
immorterm-mcp-gateway doctor              # Full diagnostic check

Doctor

Runs 7 health checks with detailed output:

immorterm-mcp-gateway doctor

  ✓ Gateway running (PID 12345, port 9100)
  ✓ Health endpoint OK — 9 servers, 11 children, 56 MB
    ○ context7 (stateless, 1 child, 142 reqs)
    ○ tavily (stateless, idle, 0 reqs)
    ◆ serena (stateful, 2 children, 89 reqs)
    ◆ playwright (stateful, 1 child, 23 reqs)
  ✓ Config backup valid (9 servers backed up)
  ✓ Claude config: 9 via gateway, 0 stdio, 2 other
  ✓ npx cache: 5 resolved path(s)
  ○ No classification overrides (using defaults)
  ✓ Log file: 0.3 MB

6 passed, 0 issue(s)

= stateful (per-session children) | = stateless (shared child)

Modules

| Module | Purpose | |--------|---------| | server.ts | Hono HTTP server, JSON-RPC proxy routes, session management | | child-pool.ts | Child process lifecycle, request routing, stdout parsing | | config-rewriter.ts | Atomic backup/rewrite/restore of ~/.claude.json, hot-reload via fs.watch | | npx-resolver.ts | Resolves npx/uvx commands to direct binary paths | | classification.ts | Stateless/stateful classification with user overrides | | health.ts | /health endpoint with server stats and memory usage | | index.ts | CLI entry point and lifecycle management | | types.ts | TypeScript interfaces |

State Directory

~/.immorterm/mcp-gateway/
  state.json              # PID, port, uptime (exists while running)
  config-backup.json      # Original ~/.claude.json stdio entries
  npx-cache.json          # Resolved binary paths (7-day TTL)
  classification.json     # User overrides (optional)
  gateway.log             # Log file (auto-rotated at 5 MB)
  project-backups/        # Per-project .mcp.json backups

VS Code Extension Integration

The ImmorTerm extension manages the gateway lifecycle via GatewayManager:

  • Status bar: Shows 🛜 when gateway is active, with hover tooltip (server count, children, memory)
  • Auto-start: Starts gateway when Claude sync detects it's enabled
  • Auto-recovery: Restarts with exponential backoff on process death
  • Project enablement: Opt-in via setup wizard (Step 3), stored in .vscode/immorterm-config.json
  • Process cleanup: On first enablement, kills old per-session MCP processes to force reconnection through gateway

Session Lifecycle & Cleanup

Why cleanup is needed

Without the gateway, MCP servers are stdio children of Claude. When Claude dies, they get SIGPIPE and die automatically. With the gateway, MCP servers are children of the gateway process — when Claude dies, the children survive as orphans because the gateway (their parent) is still alive. This is why the gateway needs explicit cleanup.

How sessions are tracked

The gateway tracks which Claude process owns each MCP session via:

  • X-Client-Pid HTTP header — set by the config rewriter in ~/.claude.json
  • clientInfo.pid in the MCP initialize request

Both are stored in the sessionClientPid map. Activity is tracked at the session level — any request to any server in a session updates the session's last-activity timestamp. This prevents killing one server just because a different server in the same session is being used.

Three cleanup layers

| Layer | Trigger | Latency | How it works | |-------|---------|---------|-------------| | Extension-driven | Terminal close / stale reaper | Immediate | Extension calls DELETE /sessions/by-pid/:pid before SIGTERMing Claude | | Gateway Phase 2 | Client PID no longer exists | ~60s | Reaper checks process.kill(pid, 0) every 60s; ESRCH = dead client → kill all children | | Gateway Phase 3 | No requests for 30 min | 30 min | Idle timeout fallback for sessions without a known PID |

The extension layer is the fastest but optional — the gateway's own reaper (Phases 2 and 3) ensures cleanup even without the extension.

HTTP endpoints

DELETE /sessions/by-pid/:pid    Kill ALL stateful children owned by a client PID
DELETE /sessions/:sessionId     Kill all stateful children for a specific session
DELETE /:serverName/mcp         Kill a specific server's child for a session

Reaper details

The reaper runs every 60 seconds with three phases per cycle:

  1. Phase 1 — Clean up dead children (process exited but still in pool)
  2. Phase 2 — Check sessionClientPid map; if PID is dead (ESRCH), kill session immediately
  3. Phase 3 — For sessions without a known PID, apply 30-minute idle timeout

Independence Guarantee

The gateway and extension are independent components — users can have either without the other.

Gateway without extension: The gateway reads ~/.claude.json directly, has its own CLI, and handles all cleanup autonomously via the reaper (PID detection + idle timeout). It has no extension imports and exposes only an HTTP interface.

Extension without gateway: isGatewayEnabled() returns false, causing all gateway code paths to skip silently. cleanupGatewaySessionByPid() is a safe no-op — it guards on isGatewayEnabled() && state.healthy before doing anything. The status bar hides the gateway indicator. No gateway process is spawned.

Design principle: Each side guards against the other's absence and never assumes co-existence.

Dependencies

Minimal by design — the whole point is reducing memory:

  • hono (14 KB) — HTTP routing
  • @hono/node-server — Node.js adapter
  • Node.js >= 18 — Built-ins only (child_process, fs, crypto, http)

License

MIT