mcp-remote is a Node.js proxy that lets any stdio-only MCP client — Claude Desktop, Cursor, Windsurf, and others — connect to remote HTTP-based MCP servers. It is the standard solution for a problem that affects the entire MCP ecosystem: many clients were built to launch a local subprocess and talk over stdin/stdout, but the growing catalog of cloud-hosted MCP servers (GitHub, Linear, Notion, Cloudflare, Pieces) speak HTTP, not stdio. mcp-remote acts as the invisible bridge between the two worlds.
- GitHub: github.com/geelen/mcp-remote
- npm: npmjs.com/package/mcp-remote
- Current stable version (Feb 2026):
0.1.38 - Security: CVE-2025-6514 affects
0.0.5–0.1.15; upgrade to0.1.16+immediately
The Model Context Protocol defines two families of transport:
| Transport | Communication model | Direction | Typical use |
|---|---|---|---|
| stdio | subprocess stdin/stdout | local only | Claude Desktop JSON config, Cursor mcp.json, any tool that spawns a process |
| Streamable HTTP (MCP 2025-03-26+) | HTTP POST + optional SSE upgrade on one endpoint | local or remote | modern cloud servers (GitHub, Notion, Cloudflare) |
| HTTP + SSE (deprecated, MCP 2024-11-05) | GET opens SSE stream; separate POST for client messages | local or remote | legacy servers still in production (PiecesOS SSE endpoint, Linear) |
The problem is a mismatch at the client level. Claude Desktop's claude_desktop_config.json, for example, only launches stdio processes. It has no built-in HTTP client for MCP. This means you cannot put a URL in that config file — the client does not know what to do with it.
mcp-remote solves this by appearing to be a stdio server to the client while internally speaking HTTP to the remote server:
Claude Desktop (stdio) ─────► mcp-remote (local process) ─────► remote MCP server (HTTPS)
▲ | ▲
| Auth/OAuth/| |
└────── JSON-RPC over stdio ────┘ └── Token storage (~/.mcp-auth/)
From the client's perspective nothing has changed. From the remote server's perspective it is receiving standard HTTP requests.
Before reaching for mcp-remote, confirm whether your client already speaks HTTP natively:
| Client | stdio | Streamable HTTP | SSE (legacy) | Needs mcp-remote? |
|---|---|---|---|---|
| Claude Desktop (JSON config) | ✅ | ❌ | ❌ | Yes — JSON config only supports stdio |
| Claude Desktop (Connectors UI, Pro+) | — | ✅ | ✅ | No |
| Claude Code | ✅ | ✅ | ✅ | No — native --transport http |
| Cursor | ✅ | ✅ | ✅ | No — supports URL directly in mcp.json |
| VS Code + Copilot | ✅ | ✅ | ✅ (fallback) | No |
| Windsurf (Cascade) | ✅ | ✅ | ✅ | No — supports serverUrl field |
| Goose | ✅ | ✅ | ✅ | No — supports type: streamable_http |
| JetBrains AI | ✅ | ✅ | ✅ | No |
| Raycast AI | ✅ | ✅ | ✅ | No |
| Zed | ✅ | Partial | Partial | Sometimes |
| Amazon Q Developer | ✅ | ❌ | ❌ | Yes |
| Continue.dev | ✅ | ✅ | ✅ | No |
| Cline | ✅ | ✅ | ✅ | No |
Rule of thumb: If your client's config takes only
"command"and"args"(a subprocess invocation), you needmcp-remote. If it takes a"url"or"serverUrl"field, you can connect directly.
-
Your client only supports stdio. Claude Desktop's JSON config is the classic example. Any tool that spawns a subprocess but cannot issue HTTP requests directly.
-
You need to inject custom auth headers. The
--headerflag injects anAuthorization: Bearer …(or any header) into every HTTP request — useful for API-key-protected servers that do not do OAuth. -
You want OAuth without modifying your client.
mcp-remoteimplements a complete OAuth 2.1 + PKCE flow with Dynamic Client Registration, token storage, and automatic refresh — all transparently. -
You are behind a corporate HTTP proxy. The
--enable-proxyflag makesmcp-remoterespectHTTP_PROXY/HTTPS_PROXY/NO_PROXYenvironment variables, something many clients don't do. -
You want tool-level filtering. The
--ignore-toolflag lets you block specific tools from a remote server before they even reach your AI client. -
Bridging local dev + ngrok for remote clients. Expose a local MCP server via ngrok, then use
mcp-remoteto connect other stdio clients to that ngrok URL — without the other clients needing HTTP support. -
Compatibility with PiecesOS. PiecesOS exposes an SSE endpoint. For clients like Claude Desktop (JSON config),
mcp-remoteis the bridge.
- Your client has a
"url"or"serverUrl"field in its MCP config — use it directly. - You are using Claude Desktop Connectors (Pro/Max/Team/Enterprise) — the Connectors UI handles HTTP natively.
- You are using Claude Code — use
claude mcp add --transport http <name> <url>.
- Node.js 18+ —
node --versionto verify npx— ships with Node.js; no separate install needed- OR: global install via
npm install -g mcp-remote
No installation required. npx downloads and caches the package on first use:
npx mcp-remote https://your-remote-server.com/sseForce the latest version every time:
npx mcp-remote@latest https://your-remote-server.com/sseSilence the "install?" prompt in scripted / CI environments:
npx -y mcp-remote https://your-remote-server.com/ssenpm install -g mcp-remoteThen invoke directly:
mcp-remote https://your-remote-server.com/ssenpx vs global:
npxgives you always-current versions and no version drift across machines. Global install is slightly faster after the first run but requires manualnpm update -g mcp-remoteto stay current.
npx mcp-remote https://example.com/mcpnpx mcp-remote https://example.com/mcp \
--header "Authorization: Bearer YOUR_TOKEN"npx mcp-remote https://example.com/sse \
--transport sse-onlynpx mcp-remote https://example.com/mcp \
--transport http-onlynpx mcp-remote http://localhost:39300/model_context_protocol/2024-11-05/sse \
--allow-httpnpx mcp-remote https://example.com/mcp --debugLogs are written to ~/.mcp-auth/{server_hash}_debug.log.
npx mcp-remote <server-url> [port] [options]
| Flag | Default | Description |
|---|---|---|
<server-url> |
(required) | Full HTTPS URL of the remote MCP server |
[port] |
3334 |
Local port for OAuth callback listener. Auto-selects a random port if unavailable. |
--transport <strategy> |
http-first |
See transport strategies below |
--header "Key: Value" |
— | Add a custom HTTP header to every request. Repeatable. Supports ${ENV_VAR} substitution. |
--host <hostname> |
localhost |
Hostname registered as the OAuth redirect URI |
--auth-timeout <seconds> |
30 |
How long to wait for the user to complete OAuth browser flow |
--allow-http |
off | Permit plain HTTP (unencrypted) connections. Only use on trusted private networks. |
--enable-proxy |
off | Honor HTTP_PROXY / HTTPS_PROXY / NO_PROXY environment variables |
--debug |
off | Enable verbose logging to ~/.mcp-auth/{hash}_debug.log |
--ignore-tool <pattern> |
— | Exclude tools matching a wildcard pattern from being surfaced. Repeatable. |
--static-oauth-client-info <json|@file> |
— | Pre-registered OAuth client credentials (skip Dynamic Client Registration) |
--static-oauth-client-metadata <json|@file> |
— | Custom OAuth client metadata (scopes, client name, etc.) |
| Strategy | Behavior |
|---|---|
http-first |
Tries Streamable HTTP (POST) first; falls back to SSE if server returns 404 |
sse-first |
Tries SSE (GET) first; falls back to Streamable HTTP if server returns 405 |
http-only |
Streamable HTTP exclusively; fails immediately if unsupported |
sse-only |
SSE exclusively; fails immediately if unsupported |
Use
http-firstfor new servers (default). Usesse-onlyfor legacy servers like PiecesOS's2024-11-05/sseendpoint. Usehttp-onlyto enforce the modern protocol.
mcp-remote implements the full OAuth 2.1 + PKCE + Dynamic Client Registration flow described in the MCP specification. Here is what happens on first connection to an OAuth-protected remote server:
-
Initial request → 401 challenge.
mcp-remoteconnects to the remote server. The server responds with401 Unauthorizedand aWWW-Authenticateheader containing aresource_metadatapointer. -
Discover authorization server.
mcp-remotefetches the Protected Resource Metadata document (at/.well-known/oauth-protected-resource) to find which authorization server to use. -
Dynamic Client Registration. If the server supports it,
mcp-remoteautomatically registers itself with the authorization server — no manual app registration required. If registration is disabled, provide credentials via--static-oauth-client-info. -
Browser-based authorization.
mcp-remoteopens the system browser to the authorization endpoint. The user logs in and grants permissions. -
Callback capture. The authorization server redirects back to
http://localhost:3334/callback(or your configured port).mcp-remotecaptures the authorization code. -
Token exchange.
mcp-remoteexchanges the authorization code + PKCE verifier for an access token and refresh token at the token endpoint. -
Secure storage. Tokens are written to
~/.mcp-auth/(or$MCP_REMOTE_CONFIG_DIR), with file permissions scoped to your user. Each remote server gets its own token file. -
Automatic refresh. On subsequent connections,
mcp-remoteloads the stored token. When the access token nears expiry, it refreshes using the stored refresh token — without any user action.
One-time setup, persistent thereafter. After the initial browser authorization, all subsequent invocations are silent.
| Location | Purpose |
|---|---|
~/.mcp-auth/ |
Default token storage directory |
$MCP_REMOTE_CONFIG_DIR |
Override the storage directory |
~/.mcp-auth/{hash}_debug.log |
Debug log (when --debug is active) |
To force re-authentication (clears all stored tokens):
rm -rf ~/.mcp-authConfig file locations:
| Platform | Path |
|---|---|
| macOS | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Windows | %APPDATA%\Claude\claude_desktop_config.json |
| Linux | ~/.config/Claude/claude_desktop_config.json |
OAuth-protected remote server (fully automatic):
{
"mcpServers": {
"github": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.githubcopilot.com/mcp"
]
}
}
}Bearer token authentication:
{
"mcpServers": {
"my-api-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.example.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer sk_your_token_here"
}
}
}
}Windows note: Some versions of Claude Desktop on Windows mishandle spaces inside the
argsarray during subprocess invocation. The workaround is to move the full"Bearer <token>"string into anenvvariable and reference it asAuthorization:${AUTH_HEADER}(no space around the colon).
PiecesOS local bridge:
{
"mcpServers": {
"pieces": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:39300/model_context_protocol/2024-11-05/sse",
"--allow-http",
"--transport",
"sse-only"
]
}
}
}PiecesOS via ngrok (remote access on any Claude Desktop plan):
{
"mcpServers": {
"pieces-remote": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://YOUR_NGROK_URL.ngrok.app/model_context_protocol/2024-11-05/sse"
]
}
}
}Corporate VPN / custom CA certificate:
{
"mcpServers": {
"internal-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.internal.company.com/mcp"
],
"env": {
"NODE_EXTRA_CA_CERTS": "/path/to/corporate-ca.pem"
}
}
}
}Restart Claude Desktop after any change to the config file — it only reads configuration at startup.
Config file locations:
| Scope | Path |
|---|---|
| Global | ~/.cursor/mcp.json |
| Project | .cursor/mcp.json (in repo root) |
Modern Cursor supports native HTTP — you can usually connect directly without mcp-remote:
{
"mcpServers": {
"github": {
"url": "https://api.githubcopilot.com/mcp"
}
}
}When you need mcp-remote in Cursor (e.g. header injection, tool filtering, or specific OAuth handling):
{
"mcpServers": {
"linear": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://mcp.linear.app/sse",
"--transport",
"sse-only"
]
}
}
}Claude Code supports HTTP natively — mcp-remote is not required. But you can still use it:
# Native HTTP (recommended)
claude mcp add --transport http github https://api.githubcopilot.com/mcp
# Via mcp-remote (if you need header injection or OAuth handling)
claude mcp add-json my-server '{
"type": "stdio",
"command": "npx",
"args": [
"mcp-remote@latest",
"https://api.example.com/mcp",
"--header",
"Authorization: Bearer ${MY_TOKEN}"
]
}'Config file: ~/.codeium/windsurf/mcp_config.json
Windsurf supports native HTTP via serverUrl. Use mcp-remote only when you need OAuth or header injection:
Native HTTP (preferred):
{
"mcpServers": {
"github": {
"serverUrl": "https://api.githubcopilot.com/mcp"
}
}
}Via mcp-remote with bearer token:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.example.com/mcp",
"--header",
"Authorization:${AUTH_HEADER}"
],
"env": {
"AUTH_HEADER": "Bearer your_token_here"
}
}
}
}VS Code MCP config goes in .vscode/mcp.json (workspace) or user settings:
{
"servers": {
"github": {
"type": "http",
"url": "https://api.githubcopilot.com/mcp"
}
}
}Use mcp-remote in VS Code only if you are on an older version without native HTTP, or need specific OAuth handling:
{
"servers": {
"my-server": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.example.com/mcp"
]
}
}
}Goose supports streamable_http natively. Use that when possible:
extensions:
github:
enabled: true
type: streamable_http
name: github
uri: https://api.githubcopilot.com/mcp
headers: {}
timeout: 300Via mcp-remote in Goose (for OAuth or header injection):
extensions:
my-server:
enabled: true
type: stdio
cmd: npx
args:
- "-y"
- mcp-remote
- https://api.example.com/mcp
- "--header"
- "Authorization:${AUTH_HEADER}"
envs:
AUTH_HEADER: "Bearer your_token"
timeout: 300Use Settings → Tools → AI Assistant → Model Context Protocol (MCP) → Add server:
- Type: Command
- Command:
npx - Arguments:
-y mcp-remote https://api.example.com/mcp
Or add to ~/.config/JetBrains/mcp.json:
{
"mcpServers": {
"my-server": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.example.com/mcp"]
}
}
}Amazon Q only supports stdio transport and requires mcp-remote to reach any remote server:
In ~/.aws/amazonq/mcp.json:
{
"mcpServers": {
"github": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.githubcopilot.com/mcp"
]
}
}
}A growing catalog of cloud-hosted MCP servers is available without any self-hosting. Connect to them via mcp-remote (or natively in clients that support HTTP):
| Service | Remote URL | Auth |
|---|---|---|
| GitHub | https://api.githubcopilot.com/mcp |
OAuth |
| Linear | https://mcp.linear.app/sse |
OAuth |
| Notion | https://mcp.notion.com/mcp |
OAuth |
| Sentry | https://mcp.sentry.dev/mcp |
OAuth |
| PayPal | https://mcp.paypal.com/mcp |
OAuth |
| Cloudflare Docs | https://docs.mcp.cloudflare.com/mcp |
OAuth |
| Cloudflare Observability | https://observability.mcp.cloudflare.com/mcp |
OAuth |
| Cloudflare Workers Bindings | https://bindings.mcp.cloudflare.com/mcp |
OAuth |
| Cloudflare Radar | https://radar.mcp.cloudflare.com/mcp |
OAuth |
| Cloudflare AI Gateway | https://ai-gateway.mcp.cloudflare.com/mcp |
OAuth |
| Make.com | https://mcp.make.com/mcp |
OAuth |
| Jina AI | https://mcp.jina.ai/sse |
API key via --header |
| Semgrep | https://mcp.semgrep.ai/mcp |
OAuth |
| Buildkite | https://mcp.buildkite.com/mcp |
OAuth |
mcp-remote and ngrok complement each other in two distinct scenarios.
You have a PiecesOS (or any local MCP server) running on one machine and want a stdio-only client on another machine (or a cloud agent) to reach it:
Cloud AI agent ──► ngrok public URL ──► your local MCP server
Use mcp-remote on the cloud side to bridge from HTTP back to whatever your local client expects. See the Connecting to PiecesOS via Ngrok guide for the full tunnel setup.
You are building a custom MCP server locally. You want teammates to test it without installing anything:
# Terminal 1: your MCP server on port 8080
node my-mcp-server.js
# Terminal 2: expose it
ngrok http 8080
# → https://abc123.ngrok.app
# Teammates configure mcp-remote to point at the ngrok URL
# npx mcp-remote https://abc123.ngrok.app/mcpEach teammate adds this to their client config with no local installation of your server required.
You can protect your ngrok-exposed server with an API key at the ngrok traffic policy level, then inject that key via mcp-remote's --header flag on the client side:
{
"mcpServers": {
"dev-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://abc123.ngrok.app/mcp",
"--header",
"X-Api-Key:${DEV_SERVER_KEY}"
],
"env": {
"DEV_SERVER_KEY": "your-ngrok-api-key"
}
}
}
}PiecesOS uses the legacy SSE transport (2024-11-05/sse). Use mcp-remote as the bridge in stdio-only clients. Discover the active port first (39300–39333):
for p in $(seq 39300 39333); do
if curl -s -o /dev/null -w "%{http_code}" --connect-timeout 1 \
"http://localhost:$p/.well-known/version" 2>/dev/null | grep -q 200; then
echo "PiecesOS on port $p"
break
fi
doneThen configure:
{
"mcpServers": {
"pieces": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"http://localhost:39300/model_context_protocol/2024-11-05/sse",
"--allow-http",
"--transport",
"sse-only"
]
}
}
}For a remote PiecesOS instance (via ngrok — useful for cloud agents or non-local machines):
{
"mcpServers": {
"pieces-remote": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://YOUR_NGROK_URL.ngrok.app/model_context_protocol/2024-11-05/sse",
"--transport",
"sse-only"
]
}
}
}For OAuth servers that require pre-registration:
{
"mcpServers": {
"enterprise-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sso.company.com/mcp",
"--static-oauth-client-info",
"@/Users/you/Library/Application Support/Claude/oauth_client.json"
]
}
}
}Where oauth_client.json contains:
{
"client_id": "your_client_id",
"client_secret": "your_client_secret"
}{
"mcpServers": {
"scoped-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.example.com/mcp",
"--static-oauth-client-metadata",
"{\"scope\": \"read write admin\"}"
]
}
}
}{
"mcpServers": {
"sso-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://sso.company.com/mcp",
"--auth-timeout",
"120"
]
}
}
}{
"mcpServers": {
"third-party-server": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://api.example.com/mcp",
"--ignore-tool",
"delete*",
"--ignore-tool",
"execute*",
"--ignore-tool",
"shell*"
]
}
}
}Filtered tools are hidden from tools/list responses and cannot be invoked.
{
"mcpServers": {
"behind-proxy": {
"command": "npx",
"args": [
"-y",
"mcp-remote",
"https://remote.example.com/mcp",
"--enable-proxy"
],
"env": {
"HTTPS_PROXY": "http://proxy.company.com:8080",
"NO_PROXY": "localhost,127.0.0.1"
}
}
}
}Each server entry runs a separate mcp-remote process with independent token storage:
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://api.githubcopilot.com/mcp"]
},
"linear": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.linear.app/sse", "--transport", "sse-only"]
},
"notion": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://mcp.notion.com/mcp"]
},
"pieces": {
"command": "npx",
"args": ["-y", "mcp-remote", "http://localhost:39300/model_context_protocol/2024-11-05/sse", "--allow-http", "--transport", "sse-only"]
}
}
}Versions 0.0.5 through 0.1.15 contain a critical remote code execution vulnerability (CVSS 9.6). A malicious or compromised MCP server could respond with a crafted OAuth authorization_endpoint URL containing shell metacharacters, causing mcp-remote to execute arbitrary commands on your machine when initiating the OAuth flow.
Action required:
- Check your version:
npx mcp-remote --version - If below
0.1.16, update immediately - Use
npx mcp-remote@latestornpm update -g mcp-remote - Only connect to remote servers you trust, over HTTPS
-
Always use HTTPS. Never use
--allow-httpover the public internet. Only use it on trusted, isolated private networks where traffic cannot be intercepted. -
Store credentials in
env, notargs. Config files may be committed to version control or visible in process lists. Use the"env"block in your client config and reference with${VAR_NAME}in args. -
Rotate tokens. Tokens stored in
~/.mcp-auth/are long-lived. Rotate API keys and OAuth credentials on a regular schedule. If you suspect compromise, runrm -rf ~/.mcp-authand re-authenticate. -
Audit remote server capabilities. Use
--ignore-toolto block tools you don't need, especially destructive ones (delete*,execute*,shell*). -
Pin your version in CI. In automated environments, use
mcp-remote@0.1.38(or whichever current version is patched) rather than@latest, to prevent unexpected behavior from version updates. -
Keep your Node.js updated.
mcp-remoterequires Node.js 18+. Keep Node.js current for upstream security patches. -
Review token file permissions.
~/.mcp-auth/should be readable only by your user. Verify withls -la ~/.mcp-auth/.
| Symptom | Likely cause | Fix |
|---|---|---|
npx: command not found |
Node.js not installed or not on PATH | Install Node.js 18+ from nodejs.org |
Error: Cannot connect to remote server |
Server URL is wrong, server is down, or HTTPS cert invalid | Verify the URL with curl; check --debug logs |
Error: EACCES on port 3334 |
Port already in use | Specify an alternate port: npx mcp-remote <url> 9696 |
| OAuth browser window does not open | Headless environment or browser misconfigured | Set the BROWSER environment variable; or use --header for API-key auth instead |
| OAuth times out | MFA/SSO is slow | Add --auth-timeout 120 |
401 Unauthorized after first auth |
Token expired or revoked | Run rm -rf ~/.mcp-auth and re-authenticate |
| Tools not appearing in client | Client not restarted after config change | Fully quit and reopen Claude Desktop / Cursor |
404 on connection |
Server uses SSE, not Streamable HTTP | Add --transport sse-only |
405 on connection |
Server uses Streamable HTTP, not SSE | Use default http-first or --transport http-only |
Could not resolve host |
DNS issue or ngrok tunnel expired | Check tunnel is running; refresh ngrok URL |
| Config change not taking effect | Windows PATH or env issues | Specify full Node.js path; use "env" block for credentials |
Spaces in --header args broken on Windows |
Shell argument parsing bug in some clients | Move token to env var, use Authorization:${HEADER} (no space around :) |
rm -rf ~/.mcp-authmcp-remote ships with a client mode for manual testing. This runs the full OAuth flow and lists available tools:
npx mcp-remote-client https://your-server.com/mcpAdd --debug to your args:
"args": ["-y", "mcp-remote", "https://your-server.com/mcp", "--debug"]Then inspect: ~/.mcp-auth/{hash}_debug.log
- mcp-remote GitHub repository (geelen/mcp-remote)
- mcp-remote on npm
- MCP Transport Specification (2025-03-26)
- MCP Authorization Flow
- MCP Security Best Practices
- CVE-2025-6514 Analysis (JFrog)
- SSE vs Streamable HTTP — Deep Dive
- Cloudflare Remote MCP Servers
- GitHub Remote MCP Server
- Connecting to PiecesOS via Ngrok
- ngrok + MCP Integration Guide (ngrok docs)
See also: Pieces MCP and LTM Tools Reference — Complete reference for all 39 MCP tools your agents can use
| ← Previous: Connecting to PiecesOS via Ngrok | Next: Agent Setups & Integrations → |