Reverse
Reverse transports for MCP (Model Context Protocol) β WebSocket & SSE engines. Allows internal MCP servers behind NAT/firewall to connect out to public MCP clients.
Ask AI about Reverse
Powered by Claude Β· Grounded in docs
I know everything about Reverse. Ask me about installation, configuration, usage, or troubleshooting.
0/500
Reviews
Documentation
mcp-reverse
Reverse transports for MCP β WebSocket & SSE engines.
Let internal MCP servers behind NAT/firewall connect OUT to your public client.
The Problem
Standard MCP transports are client-initiated: the Client must reach the Server's address.
Client (public) βββconnectβββ> Server (public) β
works
Client (public) βββconnectβββX Server (NAT) β unreachable
mcp-reverse flips the direction at the transport layer. The internal MCP Server initiates the connection; the public MCP Client accepts it. The MCP protocol then runs normally over the established channel.
Public Client (chat-ai) <βββincomingββββ Internal Server (behind NAT) β
works
Two Transport Engines
| Engine | Protocol | Best for |
|---|---|---|
| WebSocket | ws:// / wss:// | Native Node.js, low-latency, high-throughput |
| SSE | HTTP + Server-Sent Events | Next.js, Express, Deno, serverless, any HTTP framework |
Which engine should I use?
- WebSocket β When you control the server (Electron, Docker, bare-metal Node.js) and want the lowest latency.
- SSE β When deploying to a web platform (Next.js App Router, Vercel, Express) that already has an HTTP server. No extra port needed.
Both engines support the full MCP feature set: tools, resources, prompts, notifications, logging, etc.
Features
| Feature | WebSocket | SSE |
|---|---|---|
| NAT traversal | β | β |
| Authentication (token + custom handler) | β | β |
| Keepalive / heartbeat | β Ping/Pong | β SSE comments |
| Auto-reconnect (exponential backoff + jitter) | β | β |
| TLS / HTTPS | β WSS | β HTTPS |
| Tools / Resources / Prompts | β | β |
| Notifications (both directions) | β | β |
| Single-port deployment | β | β |
| Next.js / Vercel / serverless | β | β |
Install
npm install mcp-reverse
Requires @modelcontextprotocol/sdk as peer dependency and ws (for WebSocket engine).
Quick Start β SSE (Recommended for Web Platforms)
Public Side (chat-ai / Next.js App Router)
import { SSEAcceptor } from 'mcp-reverse';
// or: import { SSEAcceptor } from 'mcp-reverse/sse';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const acceptor = new SSEAcceptor({
authTokens: { 'office-server': 'secret123' },
});
acceptor.onConnection(async ({ transport, metadata }) => {
console.log(`Server connected: ${metadata.serverName}`);
const client = new Client(
{ name: 'chat-ai', version: '1.0.0' },
{ capabilities: {} }
);
await client.connect(transport);
// Use client normally
const tools = await client.listTools();
const result = await client.callTool({
name: 'exec',
arguments: { cmd: 'ls' },
});
});
acceptor.onDisconnection((serverName) => console.log(`Disconnected: ${serverName}`));
// βββ Next.js App Router integration βββ
// GET /api/mcp-reverse/sse
export async function GET(req: NextRequest) {
return acceptor.handleSSE(req);
}
// POST /api/mcp-reverse/message
export async function POST(req: NextRequest) {
return acceptor.handleMessage(req);
}
Or run standalone (creates its own HTTP server):
const acceptor = new SSEAcceptor({ port: 3400, authTokens: { ... } });
await acceptor.start(); // listens on http://0.0.0.0:3400/mcp-reverse
Internal Side (behind NAT)
import { SSEReverseClientTransport } from 'mcp-reverse';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const transport = new SSEReverseClientTransport({
url: 'https://public-chatai.example.com:3000/mcp-reverse',
serverName: 'office-server',
authToken: 'secret123',
reconnect: { enabled: true },
});
const server = new McpServer({
name: 'office-server',
version: '1.0.0'
});
// Add tools, resources, and prompts
server.tool('greet',
'Greet someone',
{ name: z.string() },
async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] })
);
await server.connect(transport);
Quick Start β WebSocket
Public Side
import { WebSocketAcceptor } from 'mcp-reverse';
// or: import { WebSocketAcceptor } from 'mcp-reverse/websocket';
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
const acceptor = new WebSocketAcceptor({
port: 9090,
authTokens: { 'office-server': 'secret123' },
});
acceptor.onConnection(async ({ transport, metadata }) => {
const client = new Client(
{ name: 'chat-ai', version: '1.0.0' },
{ capabilities: {} }
);
await client.connect(transport);
});
acceptor.onDisconnection((name) => console.log(`Disconnected: ${name}`));
await acceptor.start();
Internal Side
import { ReverseClientTransport } from 'mcp-reverse';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { z } from 'zod';
const transport = new ReverseClientTransport({
url: 'wss://public-chatai.example.com:9090/ws',
serverName: 'office-server',
authToken: 'secret123',
reconnect: { enabled: true },
heartbeat: { enabled: true },
});
const server = new McpServer({
name: 'office-server',
version: '1.0.0'
});
// Add tools, resources, and prompts
server.tool('greet',
'Greet someone',
{ name: z.string() },
async ({ name }) => ({ content: [{ type: 'text', text: `Hello, ${name}!` }] })
);
await server.connect(transport);
API Reference
SSEAcceptor (Public Side β SSE)
new SSEAcceptor(options: SSEAcceptorOptions | SSEAcceptorStandaloneOptions)
| Option | Type | Default | Notes |
|---|---|---|---|
authTokens | Record<string, string> | β | serverName β token map |
authHandler | async (meta) => boolean | β | Custom auth logic |
heartbeat | SSEHeartbeatOptions | {enabled:true} | Keepalive |
maxMessageSize | number | 4MB | POST body limit |
sessionTimeout | number | 60000 | Inactivity timeout (ms) |
pathPrefix | string | '/mcp-reverse' | URL prefix |
port | number | β | Standalone only: listen port |
Framework mode methods (Next.js / Express):
handleSSE(req)β Returns HTTP response for GET SSE connectionshandleMessage(req)β Returns HTTP response for POST messages
Standalone mode methods (when port is provided):
start()/close()β LifecycleisRunning()β Whether the server is runninggetAddress()β{ host, port, pathPrefix }
Event handlers (both modes):
onConnection(fn)β New server connected. Receives{ transport, metadata, sessionId }onDisconnection(fn)β Server disconnectedonError(fn)β Error occurred
SSEReverseClientTransport (Internal Side β SSE)
new SSEReverseClientTransport(options: SSEReverseClientTransportOptions)
| Option | Type | Default | Notes |
|---|---|---|---|
url | string | required | Base URL (appends /sse and /message) |
serverName | string | required | Server identifier |
authToken | string | β | Bearer token |
reconnect | ReconnectOptions | {enabled:true} | Auto-reconnect |
heartbeat | SSEHeartbeatOptions | {enabled:true} | Keepalive |
headers | Record<string, string> | β | Extra headers |
insecureTls | boolean | false | Skip TLS verify |
queryParams | Record<string, string> | β | Extra query params |
Properties: state, sessionId, reconnectAttempts
WebSocketAcceptor (Public Side β WebSocket)
new WebSocketAcceptor(options: WebSocketAcceptorOptions)
| Option | Type | Default | Notes |
|---|---|---|---|
port | number | required | Listening port |
host | string | '0.0.0.0' | Bind address |
path | string | '/ws' | Upgrade path |
authTokens | Record<string, string> | β | serverName β token map |
authHandler | async (meta) => boolean | β | Custom auth |
heartbeat | HeartbeatOptions | {enabled:false} | Ping/Pong |
tls | {cert, key} | β | WSS cert/key files |
maxMessageSize | number | 4MB | Message limit |
Methods: start(), close(), onConnection(fn), onDisconnection(fn), onError(fn), getAddress()
ReverseClientTransport (Internal Side β WebSocket)
new ReverseClientTransport(options: ReverseClientTransportOptions)
| Option | Type | Default | Notes |
|---|---|---|---|
url | string | required | WebSocket URL |
serverName | string | required | Server identifier |
authToken | string | β | Bearer token |
reconnect | ReconnectOptions | {enabled:false} | Auto-reconnect |
heartbeat | HeartbeatOptions | {enabled:false} | Ping/Pong |
headers | Record<string, string> | β | Extra headers |
insecureTls | boolean | false | Skip TLS verify |
Properties: state, sessionId, reconnectAttempts
Reconnect Options
{
enabled?: boolean; // WebSocket: default false, SSE: default true
initialDelay?: number; // default: 1000ms
maxDelay?: number; // default: 30000ms
multiplier?: number; // default: 2
jitter?: boolean; // default: true
maxRetries?: number; // default: 0 (infinite)
}
All MCP Features β Confirmed Working
| MCP Feature | WebSocket | SSE |
|---|---|---|
tools/list | β | β |
tools/call | β | β |
notifications/tools/list_changed | β | β |
resources/list | β | β |
resources/read | β | β |
resources/subscribe | β | β |
notifications/resources/list_changed | β | β |
notifications/resources/updated | β | β |
prompts/list | β | β |
prompts/get | β | β |
notifications/prompts/list_changed | β | β |
roots/list | β | β |
sampling/createMessage | β | β |
logging | β | β |
ping | β | β |
| Instructions | β | β |
Key insight: The Transport is a transparent JSON-RPC pipe. Any message that flows through
transport.send()/transport.onmessageworks automatically.
Project Structure
src/
βββ common/ # Shared utilities
β βββ types.ts # All type definitions
β βββ heartbeat.ts # WebSocket Ping/Pong heartbeat
β βββ reconnect.ts # Exponential backoff reconnection
βββ websocket/ # WebSocket engine
β βββ acceptor.ts # WebSocketAcceptor (public side)
β βββ reverse-client.ts # ReverseClientTransport (internal side)
β βββ transport.ts # SingleConnectionTransport wrapper
βββ sse/ # SSE engine
β βββ acceptor.ts # SSEAcceptor (public side)
β βββ reverse-client.ts # SSEReverseClientTransport (internal side)
β βββ connection-transport.ts # SSEConnectionTransport wrapper
β βββ util.ts # SSE parsing/formatting utilities
βββ client/ # (deprecated) Re-exports from websocket/
βββ server/ # (deprecated) Re-exports from websocket/
βββ index.ts # Main entry point β exports all engines
Testing
npm test # All 66 tests (unit + integration)
npm run build # TypeScript compilation
Credits
- CleverChatty β first
reverse-websocketMCP implementation (Go) - Supergateway β MCP WS transport bridge
License
MIT
