Skip to content

bugzy-ai/mcp-tunnel

Repository files navigation

MCP Tunnel

Tunnel HTTP requests from MCP servers through Ably Pub/Sub to access services behind firewalls

TypeScript Node.js License: MIT

Overview

MCP Tunnel enables Model Context Protocol (MCP) servers to securely access internal APIs and services behind firewalls without exposing them to the public internet. It uses Ably Pub/Sub for real-time bidirectional communication between a wrapper (npm package) and a worker (Docker container).

Core Value Proposition: Enable AI assistants to interact with internal APIs and services without exposing them to the public internet or requiring complex VPN setups.

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    AI Assistant / Client                    β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚         MCP Server (stdio, JSON-RPC 2.0)               β”‚ β”‚
β”‚  β”‚                                                        β”‚ β”‚
β”‚  β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚ β”‚
β”‚  β”‚  β”‚   Wrapper (@mcp-tunnel/wrapper)                  β”‚  β”‚ β”‚
β”‚  β”‚  β”‚   - Wraps MCP server execution                   β”‚  β”‚ β”‚
β”‚  β”‚  β”‚   - Intercepts HTTP/HTTPS requests               β”‚  β”‚ β”‚
β”‚  β”‚  β”‚   - Publishes requests to Ably                   β”‚  β”‚ β”‚
β”‚  β”‚  β”‚   - Receives responses from Ably                 β”‚  β”‚ β”‚
β”‚  β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚
                           β”‚ Ably Pub/Sub
                           β”‚ (Internet)
                           β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              Private Network / Behind Firewall              β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚   Worker (Docker Container)                            β”‚ β”‚
β”‚  β”‚   - Subscribes to Ably request channel                 β”‚ β”‚
β”‚  β”‚   - Executes HTTP requests to internal services        β”‚ β”‚
β”‚  β”‚   - Publishes responses to Ably                        β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β”‚                           β”‚                                 β”‚
β”‚                           β–Ό                                 β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚
β”‚  β”‚          Internal APIs / Services                      β”‚ β”‚
β”‚  β”‚          (not publicly accessible)                     β”‚ β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Features

  • βœ… Universal HTTP interception - Works with fetch, axios, http/https, and all Node.js HTTP clients
  • βœ… Transparent tunneling - MCP servers work without code changes
  • βœ… Firewall-friendly - No inbound ports required
  • βœ… Secure by design - Multi-tenant isolation via Ably capabilities
  • βœ… Host allow-lists - Worker enforces allowed target hosts
  • βœ… Low latency - Real-time pub/sub communication
  • βœ… TypeScript - Full type safety with strict mode
  • βœ… Production-ready - Rate limiting, request size limits, health checks
  • βœ… Debug logging - Optional file-based logging for troubleshooting
  • βœ… Smart bypassing - Automatically bypasses Ably domains to prevent loops

Quick Start

1. Set Up Ably Account

  1. Sign up for a free Ably account
  2. Create a new app in the Ably dashboard
  3. Create an API key with scoped capabilities:
    {
      "mcp-tunnel:your-tenant-id:*": ["publish", "subscribe"]
    }

2. Install Wrapper (Client Side)

npm install -g @mcp-tunnel/wrapper

3. Deploy Worker (Private Network)

# Pull the Docker image
docker pull ghcr.io/mslavov/mcp-tunnel-worker:latest

# Create .env file
cat > .env <<EOF
ABLY_API_KEY=your-ably-api-key
TENANT_ID=your-tenant-id
ALLOWED_HOSTS=api.internal.company.com,*.internal.company.com
EOF

# Run the worker
docker run -d \
  --name mcp-tunnel-worker \
  --env-file .env \
  ghcr.io/mslavov/mcp-tunnel-worker:latest

4. Use with MCP Server

# Set environment variables
export ABLY_API_KEY=your-ably-api-key
export TENANT_ID=your-tenant-id

# Test the tunnel
mcp-tunnel --test https://api.internal.company.com/health

# Or run with an MCP server
mcp-tunnel --server ./my-mcp-server.js

Claude Desktop Example: See examples/slack-mcp-tunneled.json for a real-world configuration example using the Slack MCP server.

HTTP Client Compatibility

The tunnel uses @mswjs/interceptors to intercept all HTTP requests, regardless of which library your MCP server uses:

βœ… Supported HTTP Clients:

  • Native fetch (Node.js 18+)
  • axios (used by official MCP weather server example)
  • got, node-fetch, request
  • Native http/https modules
  • Any library built on top of Node.js http/https

How it works:

// Your MCP server code - NO CHANGES NEEDED!
import axios from 'axios';

// This request is automatically intercepted and tunneled
const response = await axios.get('https://api.internal.company.com/data');

The wrapper intercepts requests at the http/https module level, so it works transparently with any HTTP client library.

Configuration

Wrapper Configuration

Environment variables:

  • ABLY_API_KEY (required) - Ably API key with publish/subscribe capabilities
  • TENANT_ID (required) - Unique tenant identifier
  • TUNNEL_TIMEOUT (optional) - Request timeout in milliseconds (default: 30000)
  • MCP_TUNNEL_DEBUG (optional) - Enable debug logging to .mcp-tunnel/wrapper.log

Worker Configuration

Environment variables:

  • ABLY_API_KEY (required) - Ably API key with publish/subscribe capabilities
  • TENANT_ID (required) - Unique tenant identifier
  • ALLOWED_HOSTS (optional) - Comma-separated list of allowed hosts (supports wildcards like *.example.com)
  • MAX_REQUEST_SIZE (optional) - Maximum request size in bytes (default: 10485760 = 10MB)
  • RATE_LIMIT_PER_TENANT (optional) - Requests per minute per tenant (default: 100)

Security

Multi-Tenant Isolation

Each tenant is isolated using Ably's capability system:

{
  "mcp-tunnel:tenant-123:*": ["publish", "subscribe"]
}

This ensures:

  • βœ… Tenants can only access their own channels
  • βœ… No cross-tenant data leakage
  • βœ… API keys are scoped to specific tenants

Worker Security

The worker enforces:

  • Host allow-lists - Only allowed hosts can be accessed
  • Request size limits - Prevents abuse
  • Rate limiting - Per-tenant request limits

Debugging

Enable detailed logging for troubleshooting:

# Enable debug logging
export MCP_TUNNEL_DEBUG=1
export ABLY_API_KEY=your-key
export TENANT_ID=your-tenant-id

# Run your MCP server with tunnel
mcp-tunnel --server ./my-mcp-server.js

# View logs in real-time
tail -f .mcp-tunnel/wrapper.log

Debug logs include:

  • Preload script initialization
  • HTTP request/response interception
  • Ably connection status
  • Request/response details
  • Error stack traces

Note: Debug logging writes to a file to avoid polluting stdio (which would break MCP protocol communication).

Development

Prerequisites

  • Node.js 18+
  • npm 8+
  • Docker 20+ (for worker)

Setup

# Clone the repository
git clone https://github.com/mslavov/mcp-tunnel.git
cd mcp-tunnel

# Install dependencies
npm install

# Build packages
npm run build

# Run linter
npm run lint

Project Structure

mcp-tunnel/
β”œβ”€β”€ packages/
β”‚   β”œβ”€β”€ wrapper/          # npm package for wrapping MCP servers
β”‚   β”‚   β”œβ”€β”€ src/
β”‚   β”‚   β”‚   β”œβ”€β”€ ably-client.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ tunnel-fetch.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ cli.ts
β”‚   β”‚   β”‚   β”œβ”€β”€ types.ts
β”‚   β”‚   β”‚   └── index.ts
β”‚   β”‚   β”œβ”€β”€ package.json
β”‚   β”‚   └── tsconfig.json
β”‚   └── worker/           # Docker container for executing requests
β”‚       β”œβ”€β”€ src/
β”‚       β”‚   β”œβ”€β”€ ably-client.ts
β”‚       β”‚   β”œβ”€β”€ request-handler.ts
β”‚       β”‚   β”œβ”€β”€ types.ts
β”‚       β”‚   └── index.ts
β”‚       β”œβ”€β”€ Dockerfile
β”‚       β”œβ”€β”€ package.json
β”‚       └── tsconfig.json
β”œβ”€β”€ .github/
β”‚   └── workflows/
β”‚       β”œβ”€β”€ test.yml
β”‚       └── publish.yml
β”œβ”€β”€ package.json
β”œβ”€β”€ tsconfig.base.json
└── README.md

Contributing

Contributions are welcome!

License

MIT License - see LICENSE for details.

Acknowledgments


Built with ❀️ for the MCP community

About

MCP tunnel for accessing services behind firewalls via Ably Pub/Sub

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors