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-secure-remote

v0.0.2

Published

Remote proxy for Model Context Protocol servers with mTLS (mutual TLS) client certificate authentication support.

Readme

mcp-secure-remote

A stdio ↔ remote bridge for the Model Context Protocol with first-class mTLS (mutual TLS) client-certificate authentication.

Works with any MCP-capable AI agent or IDE — Claude Desktop, Claude Code, Cursor, Windsurf, Cline, Continue, Zed, VS Code MCP extensions, and any custom client that speaks the MCP stdio transport.


Contents

  1. What it does
  2. How it works
  3. Prerequisites
  4. Install
  5. Generate or obtain client certificates
  6. Quick start
  7. CLI parameters
  8. Environment variables
  9. AI agent / IDE integration
  10. Testing your setup
  11. Security notes
  12. Troubleshooting
  13. Docker
  14. Development
  15. License

What it does

mcp-secure-remote spawns as a local stdio MCP server and forwards every JSON-RPC message to a remote MCP server over HTTPS. Every outbound request carries a client certificate you supply, so the remote server sees a cryptographically authenticated connection — no OAuth dance, no bearer tokens on the wire, no shared API keys.

┌──────────────┐   stdio    ┌────────────────────┐   HTTPS + mTLS   ┌───────────────┐
│ MCP client   │───────────▶│ mcp-secure-remote  │─────────────────▶│ Remote MCP    │
│ (Claude,     │            │ (this proxy)       │                  │ server        │
│  Cursor, …)  │◀───────────│                    │◀─────────────────│               │
└──────────────┘            └────────────────────┘                  └───────────────┘

How it works

  1. AI agent launches mcp-secure-remote as a local subprocess and talks to it over stdio (the transport every MCP client already supports).
  2. Proxy builds an undici HTTPS dispatcher seeded with your client cert, private key, and trusted CA bundle.
  3. Proxy opens either a Streamable HTTP or SSE transport to the remote server (configurable). TLS handshake presents the client cert; server validates it before forwarding the MCP session.
  4. JSON-RPC frames flow bidirectionally. All proxy logging goes to stderr so the stdio channel stays clean.

Prerequisites

  • Node.js ≥ 18 (for built-in fetch, undici, and native TLS features).
  • A client certificate + private key issued by a CA the remote MCP server trusts (or a PKCS#12 bundle containing both).
  • The CA bundle used by the remote server, if it is not in your OS trust store (private/corporate CAs almost always need this).
  • The remote MCP server URL (typically https://host/mcp or https://host/sse).

Install

# global
npm install -g mcp-secure-remote

# or ephemeral (recommended for agent configs)
npx mcp-secure-remote <server-url> [options]

Generate or obtain client certificates

If your team already issues client certs, skip this section. For local testing, generate a throw-away CA + client cert pair with OpenSSL:

# CA
openssl req -x509 -newkey rsa:4096 -sha256 -days 365 -nodes \
  -keyout ca.key -out ca.crt -subj "/CN=dev-ca"

# client key + CSR
openssl req -newkey rsa:4096 -nodes \
  -keyout client.key -out client.csr -subj "/CN=dev-client"

# sign client cert with CA
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
  -out client.crt -days 365 -sha256

Configure the remote MCP server to require client certs signed by ca.crt. Point the proxy at client.crt + client.key + the server's CA bundle.

Quick start

Cert + key pair:

npx mcp-secure-remote https://mcp.example.com/mcp \
  --tls-cert ./certs/client.crt \
  --tls-key  ./certs/client.key \
  --tls-ca   ./certs/ca-bundle.pem

PKCS#12 bundle:

npx mcp-secure-remote https://mcp.example.com/mcp \
  --tls-pfx       ./certs/client.p12 \
  --tls-passphrase "$P12_PASSPHRASE" \
  --tls-ca        ./certs/ca-bundle.pem

Force SSE transport + pin minimum TLS:

npx mcp-secure-remote https://mcp.example.com/sse \
  --transport sse-only \
  --tls-min-version TLSv1.3 \
  --tls-cert ./certs/client.crt \
  --tls-key  ./certs/client.key \
  --tls-ca   ./certs/ca-bundle.pem

CLI parameters

Usage: mcp-secure-remote <server-url> [options]

<server-url> is a positional argument (required). Everything else is a named flag.

General

| Flag | Type | Default | Description | | --- | --- | --- | --- | | <server-url> | string (URL) | — | Required. Remote MCP endpoint. Must be https://… unless --allow-http is set. | | --header "Name: value" | string (repeatable) | — | Extra HTTP header on every outbound request. Repeat the flag for multiple headers. | | --transport <strategy> | enum | http-first | Transport negotiation. One of http-first, sse-first, http-only, sse-only. -first variants try the preferred transport then fall back; -only variants never fall back. | | --allow-http | boolean | false | Permit plain http:// URLs. Off by default; mTLS is meaningless over HTTP. | | --debug | boolean | false | Verbose logging to stderr (parsed args, per-message trace, transport selection). | | -h, --help | boolean | — | Print usage and exit. |

mTLS / TLS

| Flag | Type | Default | Description | | --- | --- | --- | --- | | --tls-cert <path> | path | — | PEM client certificate (leaf, optionally followed by chain intermediates). | | --tls-key <path> | path | — | PEM private key matching --tls-cert. Must be supplied together with --tls-cert. | | --tls-ca <path> | path | — | PEM CA bundle used to verify the remote server. Required for private CAs not in the OS trust store. | | --tls-pfx <path> | path | — | PKCS#12 (.pfx / .p12) bundle. Mutually exclusive with --tls-cert/--tls-key. | | --tls-passphrase <value> | string | — | Passphrase protecting the private key or PFX bundle. Prefer the env var to keep secrets off the command line. | | --tls-servername <name> | string | URL hostname | SNI override. Use when the server cert's SAN differs from the URL host (e.g. IP literal, internal DNS). | | --tls-min-version <ver> | enum | Node default | Minimum TLS version: TLSv1.2 or TLSv1.3. | | --tls-insecure-skip-verify, --tls-no-verify | boolean | false | Disable server certificate validation. Dev only. Proxy prints a warning when enabled. |

Parameter rules

  • --tls-cert and --tls-key must appear together.
  • --tls-pfx cannot combine with --tls-cert/--tls-key.
  • --allow-http is required for any http:// URL. Supplying mTLS flags with http:// triggers a warning (cert is not sent over plain HTTP).
  • Unknown --flags cause parse failure with exit code 2.
  • Argument errors exit with code 2; runtime errors exit with code 1.
  • URLs with embedded credentials, such as https://user:[email protected]/mcp, are rejected. Use --header or environment configuration for credentials.

Environment variables

Every TLS flag has an env-var fallback so secrets can stay out of shell history and MCP client configs.

| Variable | Equivalent flag | Values | | --- | --- | --- | | MCP_REMOTE_TLS_CERT | --tls-cert | path | | MCP_REMOTE_TLS_KEY | --tls-key | path | | MCP_REMOTE_TLS_CA | --tls-ca | path | | MCP_REMOTE_TLS_PFX | --tls-pfx | path | | MCP_REMOTE_TLS_PASSPHRASE | --tls-passphrase | string | | MCP_REMOTE_TLS_SERVERNAME | --tls-servername | string | | MCP_REMOTE_TLS_MIN_VERSION | --tls-min-version | TLSv1.2 | TLSv1.3 | | MCP_REMOTE_TLS_INSECURE | --tls-insecure-skip-verify | 1 / true / yes to disable verify |

Precedence: explicit CLI flag overrides env var.

AI agent / IDE integration

Every agent below launches the proxy as a local stdio MCP server. Pattern is identical — only the config file format differs. Use absolute paths; agents do not inherit your shell's working directory.

Claude Desktop

File: ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or %APPDATA%\Claude\claude_desktop_config.json (Windows).

{
  "mcpServers": {
    "example": {
      "command": "npx",
      "args": [
        "mcp-secure-remote",
        "https://mcp.example.com/mcp",
        "--tls-cert", "/absolute/path/client.crt",
        "--tls-key",  "/absolute/path/client.key",
        "--tls-ca",   "/absolute/path/ca-bundle.pem"
      ]
    }
  }
}

Restart Claude Desktop after editing.

Claude Code (CLI)

Add a server via the claude mcp add command or edit ~/.claude.json / project .mcp.json:

claude mcp add example npx -- mcp-secure-remote \
  https://mcp.example.com/mcp \
  --tls-cert /absolute/path/client.crt \
  --tls-key  /absolute/path/client.key \
  --tls-ca   /absolute/path/ca-bundle.pem

Or in .mcp.json:

{
  "mcpServers": {
    "example": {
      "command": "npx",
      "args": [
        "mcp-secure-remote",
        "https://mcp.example.com/mcp",
        "--tls-cert", "/absolute/path/client.crt",
        "--tls-key",  "/absolute/path/client.key",
        "--tls-ca",   "/absolute/path/ca-bundle.pem"
      ]
    }
  }
}

Cursor

File: ~/.cursor/mcp.json (global) or .cursor/mcp.json (per project).

{
  "mcpServers": {
    "example": {
      "command": "npx",
      "args": [
        "mcp-secure-remote",
        "https://mcp.example.com/mcp",
        "--tls-cert", "/absolute/path/client.crt",
        "--tls-key",  "/absolute/path/client.key",
        "--tls-ca",   "/absolute/path/ca-bundle.pem"
      ],
      "env": {
        "MCP_REMOTE_TLS_PASSPHRASE": "…optional…"
      }
    }
  }
}

Windsurf

File: ~/.codeium/windsurf/mcp_config.json.

{
  "mcpServers": {
    "example": {
      "command": "npx",
      "args": [
        "mcp-secure-remote",
        "https://mcp.example.com/mcp",
        "--tls-cert", "/absolute/path/client.crt",
        "--tls-key",  "/absolute/path/client.key",
        "--tls-ca",   "/absolute/path/ca-bundle.pem"
      ]
    }
  }
}

Cline (VS Code)

Cline reads cline_mcp_settings.json from its extension storage. Open the Cline MCP panel → "Configure MCP Servers" or edit the file directly:

{
  "mcpServers": {
    "example": {
      "command": "npx",
      "args": [
        "mcp-secure-remote",
        "https://mcp.example.com/mcp",
        "--tls-cert", "/absolute/path/client.crt",
        "--tls-key",  "/absolute/path/client.key",
        "--tls-ca",   "/absolute/path/ca-bundle.pem"
      ],
      "disabled": false,
      "autoApprove": []
    }
  }
}

Continue (VS Code / JetBrains)

File: ~/.continue/config.json (or config.yaml).

{
  "experimental": {
    "modelContextProtocolServers": [
      {
        "transport": {
          "type": "stdio",
          "command": "npx",
          "args": [
            "mcp-secure-remote",
            "https://mcp.example.com/mcp",
            "--tls-cert", "/absolute/path/client.crt",
            "--tls-key",  "/absolute/path/client.key",
            "--tls-ca",   "/absolute/path/ca-bundle.pem"
          ]
        }
      }
    ]
  }
}

Zed

File: ~/.config/zed/settings.json.

{
  "context_servers": {
    "example": {
      "command": {
        "path": "npx",
        "args": [
          "mcp-secure-remote",
          "https://mcp.example.com/mcp",
          "--tls-cert", "/absolute/path/client.crt",
          "--tls-key",  "/absolute/path/client.key",
          "--tls-ca",   "/absolute/path/ca-bundle.pem"
        ]
      }
    }
  }
}

Generic MCP client

Any client that spawns stdio MCP servers works. Required pieces:

  • command: npx (or absolute path to node + dist/proxy.js).
  • args: ["mcp-secure-remote", "<server-url>", …flags].
  • Optional env block for MCP_REMOTE_TLS_* variables to keep secrets out of the args array.

Testing your setup

Bundled mcp-secure-remote-client verifies the TLS handshake and enumerates the server's capabilities — no real agent needed:

npx mcp-secure-remote-client https://mcp.example.com/mcp \
  --tls-cert ./certs/client.crt \
  --tls-key  ./certs/client.key \
  --tls-ca   ./certs/ca-bundle.pem

Output: negotiated capabilities + lists of tools, resources, prompts.

Add --debug for per-message tracing.

Security notes

  • HTTPS only by default. http:// URLs are refused unless --allow-http is explicitly set. Proxy additionally warns when mTLS flags are combined with http:// because the client cert will not be sent.
  • Redirects are rejected. Outbound transport requests do not follow HTTP redirects, which prevents a remote endpoint from bouncing the client to a different host and reusing the configured TLS credentials there.
  • Skip-verify prints a warning. --tls-insecure-skip-verify disables server certificate validation; intended for local dev loops only.
  • Prefer env vars for passphrases. Anything on the CLI may leak into process listings, shell history, or agent logs.
  • Debug logging redacts secrets. The bundled client and proxy avoid printing TLS passphrases or header values in --debug output. Proxy message tracing logs only JSON-RPC metadata, not full tool arguments or results.
  • Embedded URL credentials are refused. Userinfo in the remote URL is not accepted because it can leak through process lists and logs.
  • Proxy logs to stderr. stdout is reserved for the MCP JSON-RPC stream.
  • Client output is terminal-sanitized. Tool names, descriptions, resources, and prompts received from the remote server are escaped before being written to the terminal.
  • No credential persistence. Proxy does not write certs, keys, or tokens to disk.
  • Pin TLS 1.3 (--tls-min-version TLSv1.3) when the server supports it, to avoid downgrade-prone 1.2 cipher suites.

Troubleshooting

self signed certificate in certificate chain / unable to verify the first certificate Point --tls-ca at the PEM bundle that signed the remote server's cert. OS trust store alone is not enough for private CAs.

Hostname/IP does not match certificate's altnames Set --tls-servername to the SAN the server cert presents.

error:0909006C:PEM routines:get_name:no start line Private key file malformed or encrypted. If encrypted, supply --tls-passphrase (or MCP_REMOTE_TLS_PASSPHRASE).

ERR_SSL_SSLV3_ALERT_HANDSHAKE_FAILURE / alert bad certificate Server rejected your client cert. Check:

  • Cert signed by a CA the server trusts.
  • Key matches cert: openssl x509 -noout -modulus -in client.crt | openssl md5 vs. openssl rsa -noout -modulus -in client.key | openssl md5.
  • Intermediate chain present in --tls-cert.

Agent shows "failed to start server" with no detail. Run the exact same command in a terminal to see stderr. Agents hide subprocess stderr by default.

Remote transport hangs. Try --transport sse-only or --transport http-only to isolate which transport the server actually implements. Add --debug.

already started error in mcp-secure-remote-client. Upgrade — prior versions double-started the transport. Fixed in current release.

Docker

A multi-stage Dockerfile is included. The build stage compiles TypeScript; the runtime stage contains only production dependencies and the compiled dist/ output — no dev tooling in the final image.

Build the image:

docker build -t mcp-secure-remote .

Run the proxy (no mTLS — plain HTTPS server):

docker run -i mcp-secure-remote https://mcp.example.com/mcp

The -i flag is required because the proxy communicates over stdin/stdout.

Run the proxy with mTLS using Docker secrets (recommended for production):

docker run -i \
  -e MCP_REMOTE_TLS_CERT=/run/secrets/client.crt \
  -e MCP_REMOTE_TLS_KEY=/run/secrets/client.key \
  -e MCP_REMOTE_TLS_CA=/run/secrets/ca-bundle.pem \
  --mount type=secret,id=client.crt \
  --mount type=secret,id=client.key \
  --mount type=secret,id=ca-bundle.pem \
  mcp-secure-remote https://mcp.example.com/mcp

Run the proxy with mTLS by bind-mounting a local cert directory:

docker run -i \
  -v /path/to/local/certs:/certs:ro \
  -e MCP_REMOTE_TLS_CERT=/certs/client.crt \
  -e MCP_REMOTE_TLS_KEY=/certs/client.key \
  -e MCP_REMOTE_TLS_CA=/certs/ca-bundle.pem \
  mcp-secure-remote https://mcp.example.com/mcp

All TLS configuration is supplied via MCP_REMOTE_TLS_* environment variables (see Environment variables). No certs are baked into the image.

Run the client (verify handshake / enumerate server capabilities):

docker run --rm \
  -v /path/to/local/certs:/certs:ro \
  -e MCP_REMOTE_TLS_CERT=/certs/client.crt \
  -e MCP_REMOTE_TLS_KEY=/certs/client.key \
  -e MCP_REMOTE_TLS_CA=/certs/ca-bundle.pem \
  --entrypoint node mcp-secure-remote dist/client.js \
  https://mcp.example.com/mcp

Pass extra CLI flags by appending them after the image name:

docker run -i mcp-secure-remote \
  https://mcp.example.com/mcp \
  --transport sse-only \
  --tls-min-version TLSv1.3 \
  --debug

Development

npm install
npm run typecheck
npm run test
npm run test:coverage
npm run build
npm pack --dry-run

Build artifacts land in dist/. dist/proxy.js and dist/client.js are the two bin entrypoints. npm pack and npm publish rebuild dist/ first via the package lifecycle scripts.

Test script summary:

  • npm run test — run all tests once.
  • npm run test:watch — run tests in watch mode.
  • npm run test:coverage — run tests and generate coverage output.
  • npm run test:unit — run tests under test/unit.

License

MIT — see LICENSE.