Skip to content

feat(mcp-proxy): singleton mode to prevent multiple instances#43

Merged
ricardoraposo merged 6 commits into
mainfrom
ricardo-/-mcp-proxy-singleton-mode
Apr 14, 2026
Merged

feat(mcp-proxy): singleton mode to prevent multiple instances#43
ricardoraposo merged 6 commits into
mainfrom
ricardo-/-mcp-proxy-singleton-mode

Conversation

@ricardoraposo

@ricardoraposo ricardoraposo commented Apr 14, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Adds singleton mode to mcp-proxy so only one instance runs at a time, even when multiple agents (Kiro, OpenCode, Cursor) spawn it simultaneously
  • First process becomes PRIMARY (stdio + HTTP on port 9200); subsequent spawns become BRIDGE (stdio → HTTP forwarding to primary)
  • Uses atomic lockfile (/tmp/mcp-proxy.lock with fs.open('wx')) with PID liveness check to handle stale locks

How it works

  1. On startup, tries to acquire lock atomically
  2. If acquired → PRIMARY: runs normally via stdio, also exposes StreamableHTTP on port 9200 for bridges
  3. If lock exists and PID alive → BRIDGE: thin stdio server that forwards mcp_search/mcp_call/mcp_schema to primary via HTTP
  4. If lock exists but PID dead → cleans stale lock, becomes PRIMARY

New files

  • singleton.ts — lock file management (acquire, release, stale detection)
  • bridge.ts — stdio↔HTTP bridge for secondary instances

Modified files

  • server.ts — added startHttpTransport() for bridge connections
  • index.ts — startup decision logic (PRIMARY vs BRIDGE)

Config

  • MCP_PROXY_SINGLETON_PORT (default: 9200)
  • MCP_PROXY_LOCK_DIR (default: /tmp)

No changes needed in agent MCP configs — interface remains stdio.

Summary by CodeRabbit

Release Notes

  • New Features
    • Added singleton-based process coordination, enabling PRIMARY and BRIDGE modes for multi-process execution
    • Implemented HTTP transport support for the MCP proxy server
    • Added graceful shutdown handling with automatic resource cleanup and signal management

Use a lockfile + HTTP bridge so only one mcp-proxy process runs at a time.
Subsequent spawns forward calls to the primary via StreamableHTTP.
@coderabbitai

coderabbitai Bot commented Apr 14, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@ricardoraposo has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 41 minutes and 30 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 41 minutes and 30 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4a92effe-cf54-42cd-b547-681657feb0a5

📥 Commits

Reviewing files that changed from the base of the PR and between 0864bd3 and 7c7a6b7.

📒 Files selected for processing (6)
  • packages/mcp-proxy/package.json
  • packages/mcp-proxy/src/bridge.ts
  • packages/mcp-proxy/src/dashboard.ts
  • packages/mcp-proxy/src/index.ts
  • packages/mcp-proxy/src/server.ts
  • packages/mcp-proxy/src/singleton.ts
📝 Walkthrough

Walkthrough

Introduces a multi-instance coordination system for MCP proxy using filesystem-based locking. A primary instance starts an HTTP server on a singleton port, while bridge instances proxy requests through the primary. New modules provide lock management and bridge server functionality with fallback transport support.

Changes

Cohort / File(s) Summary
Bridge Server Implementation
packages/mcp-proxy/src/bridge.ts
New BridgeServer class that proxies three MCP tools (mcp_search, mcp_call, mcp_schema) to an upstream server. Implements lazy client initialization with transport fallback (HTTP then SSE), request forwarding with error handling and response normalization, and graceful shutdown support.
Singleton Locking System
packages/mcp-proxy/src/singleton.ts
New module providing filesystem-based singleton lock coordination. Exports tryAcquireLock(), readLock(), and releaseLock() functions that manage a lock file containing process PID and port. Handles stale lock detection and recovery via process liveness checks.
HTTP Server & Session Management
packages/mcp-proxy/src/server.ts
Extended McpProxyServer with HTTP transport support. Adds startHttpTransport(port) method that creates an HTTP server, manages per-session StreamableHTTPServerTransport instances keyed by mcp-session-id header, and implements session lifecycle with automatic cleanup. Updated cleanup() to shut down HTTP server and release all active sessions.
Process Coordination & Initialization
packages/mcp-proxy/src/index.ts
Modified startup logic to support dual-mode operation. Computes singleton port (default 9200, configurable via MCP_PROXY_SINGLETON_PORT), attempts lock acquisition, and branches: PRIMARY mode starts McpProxyServer with HTTP transport, BRIDGE mode starts BridgeServer connecting to primary's port. Added exports for BridgeServer and lock helper functions.

Sequence Diagram(s)

sequenceDiagram
    participant App as Application Process
    participant Lock as Lock File
    participant Primary as Primary Server<br/>(McpProxyServer + HTTP)
    participant Bridge as Bridge Server
    participant Upstream as Upstream MCP<br/>Server

    App->>Lock: tryAcquireLock(port)
    
    alt Lock Acquired (PRIMARY)
        Lock->>App: true
        App->>Primary: new McpProxyServer()
        App->>Primary: startHttpTransport(9200)
        Primary->>Primary: Create HTTP server on port 9200
        
        Note over Primary: Listening for client requests
        
        App->>App: setupGracefulShutdown()
        
    else Lock Exists (BRIDGE)
        Lock->>App: false
        App->>Lock: readLock()
        Lock->>App: {pid, port: 9200}
        App->>Bridge: new BridgeServer(9200)
        App->>Bridge: start()
        Bridge->>Bridge: ensureClient() to Primary:9200
        
        Note over Bridge: Ready to proxy requests
    end
    
    rect rgba(100, 150, 200, 0.5)
    Note over Primary,Bridge: Request Flow
    
    Client->>Primary: POST /mcp (primary mode)
    Primary->>Upstream: callTool()
    Upstream->>Primary: result
    Primary->>Client: {content: [...]}
    
    Client->>Bridge: Tool call (bridge mode)
    Bridge->>Primary: callTool() via HTTP
    Primary->>Upstream: callTool()
    Upstream->>Primary: result
    Primary->>Bridge: {content: [...]}
    Bridge->>Client: {content: [...]}
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 A rabbit's tale of locks and bridges fair,
Where singleton ports coordinate with care,
Primary stands guard, while bridges proxy through,
Fallback transports catch when paths are few,
Process coordination, gracefully designed! 🔐✨

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding singleton mode to prevent multiple mcp-proxy instances from running simultaneously.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch ricardo-/-mcp-proxy-singleton-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/mcp-proxy/src/bridge.ts`:
- Around line 79-103: The ensureClient method currently assigns this.client
before its connect() completes, causing races; change it to memoize the
in-flight Promise (e.g., this.clientPromise) instead of publishing this.client
early: on first call create the Client instance and set this.clientPromise =
(async () => { try connect using StreamableHTTPClientTransport(url) catch try
SSEClientTransport and re-create client; } )(), await that promise in concurrent
callers, only assign this.client after a successful connection, and ensure the
promise is cleared or rejected on failure so future attempts can retry; update
references in ensureClient, connect, StreamableHTTPClientTransport,
SSEClientTransport and primaryUrl usage accordingly.

In `@packages/mcp-proxy/src/index.ts`:
- Around line 24-28: The current wrapper calls releaseLock() before awaiting
originalCleanup(), allowing a replacement process to acquire PRIMARY while the
old server is still closing; change server.cleanup to await originalCleanup()
first and then call releaseLock() (or call releaseLock() from a finally block
after awaiting originalCleanup()) so the singleton lock is only freed after
originalCleanup() completes; update the wrapper around
originalCleanup/server.cleanup to reflect this ordering and preserve error
propagation.

In `@packages/mcp-proxy/src/server.ts`:
- Around line 499-540: The createServer request handler currently awaits
this.server.connect(transport) and transport.handleRequest(req, res) without
local error handling; wrap both async blocks (the POST session creation path
that calls this.server.connect and the branch that calls
transport.handleRequest) in a try/catch so any rejection is caught, log the
error (including transport.sessionId or sessionId when available), and send an
HTTP 5xx response (e.g., 500) with a JSON error message instead of letting the
rejection bubble to the process-level handler; ensure you still clean up the
transport in the catch if it was created and avoid swallowing errors silently.

In `@packages/mcp-proxy/src/singleton.ts`:
- Around line 29-43: The race occurs because the code creates the lock file with
openSync(lockPath, "wx") then calls writeFileSync(lockPath, ...) which can leave
a window where readLock() sees an empty/unreadable file; instead write the JSON
payload to the already-open file descriptor so the file is atomically populated
before close. Modify tryAcquireLock to replace writeFileSync(lockPath,
JSON.stringify(info)) with writing to the fd (e.g., use writeSync(fd,
JSON.stringify(info)) and optionally fsync before closeSync(fd)), keeping the
rest of the flow (readLock, unlinkSync, retry) unchanged and referencing the
same lockPath, LockInfo and tryAcquireLock symbols.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f13b8237-7f9e-4789-a33f-dee8677be741

📥 Commits

Reviewing files that changed from the base of the PR and between b1b7380 and 0864bd3.

📒 Files selected for processing (4)
  • packages/mcp-proxy/src/bridge.ts
  • packages/mcp-proxy/src/index.ts
  • packages/mcp-proxy/src/server.ts
  • packages/mcp-proxy/src/singleton.ts

Comment thread packages/mcp-proxy/src/bridge.ts Outdated
Comment thread packages/mcp-proxy/src/index.ts
Comment thread packages/mcp-proxy/src/server.ts
Comment thread packages/mcp-proxy/src/singleton.ts
@ricardoraposo ricardoraposo merged commit 4201729 into main Apr 14, 2026
4 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.

2 participants