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

mcp-local-tunnel

v1.1.0

Published

Expose local MCP servers to remote clients without opening ports.

Downloads

307

Readme

mcp-local-tunnel

Expose local MCP servers to remote clients — without opening ports.

Want to use local tools like shell-exec-mcp, computer-use-mcp, or filesystem-mcp from a remote MCP client like Claude.ai? Normally you'd need port forwarding, a static IP, or a VPN. mcp-local-tunnel removes all of that.

You run a relay on a server with a public URL, and an agent on your local machine. The agent connects outward to the relay over WebSocket — no inbound ports needed. The relay exposes a standard streamable HTTP MCP endpoint with OAuth 2.1 auth, so any MCP client can connect to it.

MCP client (Claude.ai, Cursor, etc.)
    |
    v
relay (tunnel.example.com)            you host this
    ^
    | outbound WebSocket
    |
agent (npx mcp-local-tunnel)          your laptop
    |
    v
local MCP servers                     stdio or HTTP

Both the relay and agent are the same mcp-local-tunnel package — you just set "mode": "relay" or "mode": "agent" in the config. It also works as an upstream in mcp-aggregator — the aggregator just sees another MCP server with tools.

Usage

Relay

Set MCP_LOCAL_TUNNEL_CONFIG to a JSON config object and run:

MCP_LOCAL_TUNNEL_CONFIG='{
  "mode": "relay",
  "auth": {"issuer": "https://auth.example.com"}
}' npx -y mcp-local-tunnel

This starts an HTTP MCP server on localhost:3000. MCP clients connect to /mcp with OAuth. Agents connect to /ws with a bearer token.

The env var can also point to a file path:

MCP_LOCAL_TUNNEL_CONFIG=/path/to/config.json npx -y mcp-local-tunnel

Or create mcp-local-tunnel.config.json in the working directory — it's picked up automatically:

npx -y mcp-local-tunnel
docker run -e 'MCP_LOCAL_TUNNEL_CONFIG={"mode":"relay","auth":{"issuer":"..."}}' -p 3000:3000 ghcr.io/domdomegg/mcp-local-tunnel

Agent

MCP_LOCAL_TUNNEL_CONFIG='{
  "mode": "agent",
  "relay": "tunnel.example.com",
  "servers": {
    "shell": {"command": ["npx", "-y", "shell-exec-mcp"]},
    "computer": {"command": ["npx", "-y", "computer-use-mcp"]},
    "filesystem": {"command": ["npx", "-y", "filesystem-mcp"]}
  }
}' npx -y mcp-local-tunnel

On first run, the agent opens your browser for login (same identity provider as the relay). After authenticating, the token is cached in ~/.config/mcp-local-tunnel/ so subsequent runs connect immediately.

The agent spawns the configured local MCP servers, connects to the relay, and registers all their tools. Remote clients see tools like shell__execute and computer__computer — namespaced by server name.

When used as an MCP server in tools like Claude Code or opencode, the agent runs as a background daemon. Multiple sessions share a single relay connection — the daemon stays alive as long as at least one session exists, and shuts down automatically after the last session ends. Daemon logs are written to ~/.config/mcp-local-tunnel/logs/.

If the agent disconnects (laptop sleeps, network drops), it automatically reconnects with exponential backoff.

Config

Both modes use the same config loading: MCP_LOCAL_TUNNEL_CONFIG env var (JSON string or file path), or mcp-local-tunnel.config.json in the working directory.

Shared

| Field | Required | Description | |-------|----------|-------------| | mode | Yes | "relay" or "agent". |

Relay mode

Only mode and auth.issuer are required. Everything else has sensible defaults.

| Field | Required | Description | |-------|----------|-------------| | auth.issuer | Yes | Your login provider's URL. Must support OpenID Connect discovery. | | auth.clientId | No | Client ID registered with your login provider. Defaults to "mcp-local-tunnel". | | auth.clientSecret | No | Client secret. Omit for public clients. | | auth.scopes | No | Scopes to request during login. Defaults to ["openid"]. | | auth.userClaim | No | Which field from the login token identifies the user. Defaults to "sub". | | port | No | Port to listen on. Defaults to 3000. | | host | No | Host to bind to. Defaults to "0.0.0.0". | | issuerUrl | No | Public URL of this server. Required when behind a reverse proxy. | | secret | No | Signing key for tokens. Random if not set. Set a fixed value to survive restarts. |

A full relay example:

{
  "mode": "relay",
  "auth": {
    "issuer": "https://keycloak.example.com/realms/myrealm",
    "clientId": "mcp-local-tunnel",
    "clientSecret": "optional-secret"
  },
  "port": 3000,
  "host": "0.0.0.0",
  "issuerUrl": "https://tunnel.example.com",
  "secret": "some-persistent-secret"
}

Agent mode

Only mode, relay, and servers are required.

| Field | Required | Description | |-------|----------|-------------| | relay | Yes | Domain of the relay server (e.g. tunnel.example.com). The agent connects to wss://<relay>/ws. | | name | No | Name for this device. Defaults to the machine hostname. Used to distinguish multiple devices per user. | | servers | Yes | Map of local MCP servers to expose. Keys become tool name prefixes. | | servers.<name>.command | — | Command array to spawn a stdio MCP server (e.g. ["npx", "-y", "shell-exec-mcp"]). | | servers.<name>.url | — | URL of an already-running HTTP MCP server. Provide either command or url, not both. | | servers.<name>.env | No | Extra environment variables to set when spawning the command. |

A full agent example:

{
  "mode": "agent",
  "relay": "tunnel.example.com",
  "name": "my-laptop",
  "servers": {
    "shell": {
      "command": ["npx", "-y", "shell-exec-mcp"]
    },
    "computer": {
      "command": ["npx", "-y", "computer-use-mcp"]
    },
    "filesystem": {
      "command": ["npx", "-y", "filesystem-mcp"]
    }
  }
}

Login provider examples

{
  "mode": "relay",
  "auth": {
    "issuer": "https://accounts.google.com",
    "clientId": "...",
    "clientSecret": "..."
  }
}

Create OAuth 2.0 credentials in the Google Cloud Console. Choose "Web application", add https://<relay-host>/callback as an authorized redirect URI. To restrict access to your organization, configure the OAuth consent screen as "Internal".

{
  "mode": "relay",
  "auth": {
    "issuer": "https://login.microsoftonline.com/<tenant-id>/v2.0",
    "clientId": "...",
    "clientSecret": "..."
  }
}

Register an application in the Azure portal. Add https://<relay-host>/callback as a redirect URI under "Web". Create a client secret under "Certificates & secrets". Replace <tenant-id> with your directory (tenant) ID.

{
  "mode": "relay",
  "auth": {
    "issuer": "https://your-org.okta.com",
    "clientId": "...",
    "clientSecret": "..."
  }
}

Create a Web Application in Okta. Set the sign-in redirect URI to https://<relay-host>/callback. The issuer URL is your Okta org URL (or a custom authorization server URL if you use one).

{
  "mode": "relay",
  "auth": {
    "issuer": "https://keycloak.example.com/realms/myrealm",
    "clientSecret": "..."
  }
}

Create an OpenID Connect client in your Keycloak realm with client ID mcp-local-tunnel (or set auth.clientId to match). Set the redirect URI to https://<relay-host>/callback. Users are identified by sub (Keycloak user ID) by default. Set auth.userClaim to preferred_username to match by username instead.

{
  "mode": "relay",
  "auth": {
    "issuer": "https://your-tenant.auth0.com",
    "clientId": "...",
    "clientSecret": "..."
  }
}

Create a Regular Web Application in Auth0. Add https://<relay-host>/callback as an allowed callback URL. Set auth.clientId to the Auth0 application's client ID. The sub claim in Auth0 is typically prefixed with the connection type (e.g. auth0|abc123).

{
  "mode": "relay",
  "auth": {
    "issuer": "https://authentik.example.com/application/o/myapp/",
    "clientSecret": "...",
    "userClaim": "preferred_username"
  }
}

Create an OAuth2/OpenID Provider in Authentik with client ID mcp-local-tunnel (or set auth.clientId to match). Set the redirect URI to https://<relay-host>/callback.

Home Assistant doesn't natively support OpenID Connect. Use hass-oidc-provider to bridge the gap — it runs alongside Home Assistant and adds the missing pieces.

{
  "mode": "relay",
  "auth": {
    "issuer": "https://hass-oidc-provider.example.com"
  }
}

Point auth.issuer at your hass-oidc-provider instance (not Home Assistant directly). The sub claim is the Home Assistant user ID. No clientId or clientSecret needed.

Meta tools

The relay exposes two built-in tools:

  • status — Shows whether an agent is connected, its device name, uptime, and available tools.
  • restart — Tells connected agents to restart their local MCP server processes. Useful if a server is stuck or its configuration has changed.

These are always available, even when no agent is connected.

Multiple devices

If you run agents on multiple machines, the name field distinguishes them. Tools are prefixed with the device name when multiple devices are connected:

laptop__shell__execute
desktop__shell__execute

When only one device is connected, the device prefix is omitted.

Contributing

Pull requests are welcomed on GitHub! To get started:

  1. Install Git and Node.js
  2. Clone the repository
  3. Install dependencies with npm install
  4. Run npm run test to run tests
  5. Build with npm run build

Releases

Versions follow the semantic versioning spec.

To release:

  1. Use npm version <major | minor | patch> to bump the version
  2. Run git push --follow-tags to push with tags
  3. Wait for GitHub Actions to publish to the NPM registry and GHCR (Docker).