Skip to main content

mcp-proxy

GitHub License PyPI - Python Version PyPI - Downloads codecov

About

The mcp-proxy is a tool that lets you switch between server transports. There are two supported modes:

  1. stdio to SSE/StreamableHTTP
  2. SSE to stdio

1. stdio to SSE/StreamableHTTP

Run a proxy server from stdio that connects to a remote SSE server.

This mode allows clients like Claude Desktop to communicate to a remote server over SSE even though it is not supported natively.

graph LR
    A["Claude Desktop"] <--> |stdio| B["mcp-proxy"]
    B <--> |SSE| C["External MCP Server"]

    style A fill:#ffe6f9,stroke:#333,color:black,stroke-width:2px
    style B fill:#e6e6ff,stroke:#333,color:black,stroke-width:2px
    style C fill:#e6ffe6,stroke:#333,color:black,stroke-width:2px

1.1 Configuration

This mode requires providing the URL of the MCP Server's SSE endpoint as the program’s first argument. If the server uses Streamable HTTP transport, make sure to enforce it on the mcp-proxy side by passing --transport=streamablehttp.

When the proxy is a Streamable HTTP client, it bounds the final MCP session-termination DELETE request, including reading its response body, to five seconds. After cancellation or timeout, closing a received response gets a shielded, cooperative grace of up to one second; cleanup code that ignores cancellation can take longer. Graceful CLI teardown after SIGINT requires Python 3.11+; on Python 3.10, SIGINT may interrupt the event loop before the final DELETE is sent (asyncio runner documentation). This limit applies only to that termination operation; normal GET streams and POST tool calls retain their usual HTTP timeouts. It is specific to reverse-client mode and does not change the stateful/stateless behavior of proxied HTTP servers.

In reverse Streamable HTTP mode, an HTTP 404 on a POST for an established upstream session marks that session lost. The operation that received the 404 is never retried; its failure follows the existing proxy handling, and the server may already have applied it. A later explicit request may use a replacement session only when its complete initialization handshake matches the original. The proxy does not restore subscriptions, logging levels, or other upstream session-local state; after stateful resource or logging operations, or when the handshake changes, it refuses later requests and the MCP client must restart. Progress notifications are sent only to the current healthy session and are not buffered or replayed.

Arguments

Name Required Description Example
command_or_url Yes The MCP server SSE endpoint to connect to http://example.io/sse
--headers No Headers to use for the MCP server SSE connection Authorization 'Bearer my-secret-access-token'
--transport No Decides which transport protocol to use when connecting to an MCP server. Can be either 'sse' or 'streamablehttp' streamablehttp
--client-id No OAuth2 client ID for authentication your_client_id
--client-secret No OAuth2 client secret for authentication your_client_secret
--token-url No OAuth2 token endpoint URL for authentication https://auth.example.com/oauth/token
--username No Username for authentication user
--password No Password for authentication password
--digest No Use digest authentication

Environment Variables

Name Required Description Example
API_ACCESS_TOKEN No Can be used instead of --headers Authorization 'Bearer <API_ACCESS_TOKEN>' YOUR_TOKEN

1.2 Example usage

mcp-proxy is supposed to be started by the MCP Client, so the configuration must be done accordingly.

For Claude Desktop, the configuration entry can look like this:

{
  "mcpServers": {
    "mcp-proxy": {
      "command": "mcp-proxy",
      "args": [
        "http://example.io/sse"
      ],
      "env": {
        "API_ACCESS_TOKEN": "access-token"
      }
    }
  }
}

Closing the client's stdin disconnects the remote session and cancels connection setup, initialization, and active requests. Queued input is abandoned. Each input frame is limited to 4 MiB of UTF-8 wire payload bytes, excluding CR/LF delimiters. At most 4 MiB and 128 frames may be admitted but not yet consumed; empty and malformed frames count toward the frame limit. Exceeding either limit exits with an error instead of silently dropping input. These are admission limits, not an exact memory limit, because decoded text and protocol objects use additional memory.

The client must read or close stdout. A full, unread stdout pipe can prevent process exit. A cancelled blocking stdin read may leave one daemon thread and its private descriptor until the read returns or the process exits. The helper supports one active CLI lifespan per process.

2. SSE to stdio

Run a proxy server exposing a SSE server that connects to a local stdio server.

This allows remote connections to the local stdio server. The mcp-proxy opens a port to listen for SSE requests, spawns a local stdio server that handles MCP requests.

graph LR
    A["LLM Client"] <-->|SSE| B["mcp-proxy"]
    B <-->|stdio| C["Local MCP Server"]

    style A fill:#ffe6f9,stroke:#333,color:black,stroke-width:2px
    style B fill:#e6e6ff,stroke:#333,color:black,stroke-width:2px
    style C fill:#e6ffe6,stroke:#333,color:black,stroke-width:2px

2.1 Configuration

Configure the HTTP server with --host and --port; the defaults are 127.0.0.1 and an available port. A wildcard bind such as 0.0.0.0 requires --allowed-host entries for the Host authorities clients will use. The deprecated --sse-host and --sse-port options are ineffective; use --host and --port instead. Additional environment variables can be passed to the local stdio server using --env. Put local server arguments after the -- separator.

Arguments

Name Required Description Example
command_or_url Yes The command to spawn the MCP stdio server uvx mcp-server-fetch
--port No, random available The MCP server port to listen on 8080
--host No, 127.0.0.1 by default Host IP address to bind. Wildcard binds require explicit --allowed-host authorities. 0.0.0.0
--allowed-host No Additional trusted Host authority; repeat for each hostname/IP and port clients use. --allowed-host mcp.example.com:8080
--env No Additional environment variables to pass to the MCP stdio server. Can be used multiple times. FOO BAR
--cwd No The working directory to pass to the MCP stdio server process. /tmp
--pass-environment No Pass through all environment variables when spawning the server --no-pass-environment
--allow-origin No Allowed origins for the SSE server. Can be used multiple times. Default is no CORS allowed. --allow-origin "*"
--expose-header No Headers added to Access-Control-Expose-Headers. Can be used multiple times. Defaults to mcp-session-id. --expose-header Custom-Header
--initialization-timeout SECONDS No Deadline for each stdio server initialization; default is unlimited. Cleanup may take additional time. --initialization-timeout 30
--stateless No Enable stateless mode for streamable http transports. Default is False --no-stateless
--named-server NAME COMMAND_STRING No Defines a named stdio server. --named-server fetch 'uvx mcp-server-fetch'
--named-server-config FILE_PATH No Path to a JSON file defining named stdio servers. --named-server-config /path/to/servers.json
--sse-port (deprecated) No Ineffective; use --port instead. —
--sse-host (deprecated) No Ineffective; use --host instead. —

2.2 Example usage

To start the mcp-proxy server that listens on port 8080 and connects to the local MCP server:

# Start the MCP server behind the proxy
mcp-proxy uvx mcp-server-fetch

# Start the MCP server behind the proxy with a custom port
mcp-proxy --port=8080 uvx mcp-server-fetch

# Start the MCP server behind the proxy with a custom host and port
mcp-proxy --host=0.0.0.0 --port=8080 --allowed-host mcp.example.com:8080 uvx mcp-server-fetch
# Replace mcp.example.com:8080 with the hostname/IP and port clients send in their Host header.

# Start the MCP server behind the proxy with a custom user agent
# Note that the `--` separator is used to separate the `mcp-proxy` arguments from the `mcp-server-fetch` arguments
mcp-proxy --port=8080 -- uvx mcp-server-fetch --user-agent=YourUserAgent

# Start multiple named MCP servers behind the proxy
mcp-proxy --port=8080 --named-server fetch 'uvx mcp-server-fetch' --named-server fetch2 'uvx mcp-server-fetch'

# Start multiple named MCP servers using a configuration file
mcp-proxy --port=8080 --named-server-config ./servers.json

# Start the MCP server with CORS enabled and custom exposed headers
mcp-proxy --port=8080 --allow-origin='*' --expose-header Custom-Header uvx mcp-server-fetch

Named Servers

  • NAME is used in the URL path /servers/NAME/.
  • COMMAND_STRING is the command to start the server (e.g., 'uvx mcp-server-fetch').
    • Can be used multiple times.
    • This argument is ignored if --named-server-config is used.
  • FILE_PATH - If provided, this is the exclusive source for named servers, and --named-server CLI arguments are ignored.

If a default server is specified (the command_or_url argument without --named-server or --named-server-config), it will be accessible at the root paths (e.g., http://127.0.0.1:8080/sse).

Named servers (whether defined by --named-server or --named-server-config) will be accessible under /servers/<server-name>/ (e.g., http://127.0.0.1:8080/servers/fetch1/sse). The /status endpoint provides global status.

JSON Configuration File Format for --named-server-config:

The JSON file should follow this structure:

{
  "mcpServers": {
    "fetch": {
      "disabled": false,
      "timeout": 60,
      "command": "uvx",
      "args": [
        "mcp-server-fetch"
      ],
      "transportType": "stdio"
    },
    "github": {
      "timeout": 60,
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      },
      "transportType": "stdio"
    }
  }
}
  • mcpServers: A dictionary where each key is the server name (used in the URL path, e.g., /servers/fetch/) and the value is an object defining the server.
  • command: (Required) The command to execute for the stdio server.
  • args: (Optional) A list of arguments for the command. Defaults to an empty list.
  • enabled: (Optional) If false, this server definition will be skipped. Defaults to true.
  • timeout and transportType: These fields are present in standard MCP client configurations but are currently ignored by mcp-proxy when loading named servers. The transport type is implicitly "stdio".

Installation

Installing via PyPI

The stable version of the package is available on the PyPI repository. You can install it using the following command:

# Option 1: With uv (recommended)
uv tool install mcp-proxy

# Option 2: With pipx (alternative)
pipx install mcp-proxy

Once installed, you can run the server using the mcp-proxy command. See configuration options for each mode above.

Installing via Github repository (latest)

The latest version of the package can be installed from the git repository using the following command:

uv tool install git+https://github.com/sparfenyuk/mcp-proxy

Installing as container

Starting from version 0.3.2, it's possible to pull and run the corresponding container image. Release images are published to GHCR and Docker Hub as multi-platform manifests for linux/amd64 and linux/arm64; Docker selects the matching image for the host architecture automatically. The runtime image includes uv and uvx, so Python MCP servers can be launched without building a separate image to install these executables. Installing tools with dependencies that are not already cached still requires network access.

docker run --rm -t ghcr.io/sparfenyuk/mcp-proxy:v0.12.0 --help
docker run --rm -t sparfenyuk/mcp-proxy:v0.12.0 --help

Troubleshooting

  • Problem: Claude Desktop can't start the server: ENOENT code in the logs

    Solution: Try to use the full path to the binary. To do so, open a terminal and run the commandwhich mcp-proxy ( macOS, Linux) or where.exe mcp-proxy (Windows). Then, use the output path as a value for 'command' attribute:

      "fetch": {
        "command": "/full/path/to/bin/mcp-proxy",
        "args": [
          "http://localhost:8932/sse"
        ]
      }
    

Extending the container image

You can extend the mcp-proxy container image to include additional executables or configure the bundled uv. For example, this image restricts uv to the system Python:

# file: mcp-proxy.Dockerfile

FROM ghcr.io/sparfenyuk/mcp-proxy:latest

ENV UV_PYTHON_PREFERENCE=only-system

To check a locally built image, run the opt-in container tests:

docker build -t mcp-proxy:test .
MCP_PROXY_TEST_IMAGE=mcp-proxy:test uv run --frozen pytest -q tests/test_container_image.py

These tests disable container network access, run a local wheel through uvx, and launch an MCP server through uv to list and call a tool through the proxy. Without MCP_PROXY_TEST_IMAGE, the container tests are skipped.

Docker Compose Setup

With the custom Dockerfile, you can define a service in your Docker Compose file:

services:
  mcp-proxy-custom:
    build:
      context: .
      dockerfile: mcp-proxy.Dockerfile
    network_mode: host
    restart: unless-stopped
    ports:
      - 8096:8096
    command: "--pass-environment --host=0.0.0.0 --port=8096 --allowed-host mcp.example.com:8096 uvx mcp-server-fetch"

Command line arguments

usage: mcp-proxy [-h] [--version] [-H KEY VALUE]
                 [--transport {sse,streamablehttp}] [--verify-ssl [VALUE]]
                 [--no-verify-ssl] [-e KEY VALUE] [--cwd CWD]
                 [--client-id CLIENT_ID] [--client-secret CLIENT_SECRET] [--token-url TOKEN_URL]
                 [--pass-environment | --no-pass-environment]
                 [--log-level LEVEL] [--debug | --no-debug]
                 [--named-server NAME COMMAND_STRING]
                 [--named-server-config FILE_PATH] [--port PORT] [--host HOST]
                 [--stateless | --no-stateless] [--sse-port SSE_PORT]
                 [--sse-host SSE_HOST]
                 [--allow-origin ALLOW_ORIGIN [ALLOW_ORIGIN ...]]
                 [--allowed-host HOST]
                 [--expose-header HEADER]
                 [command_or_url] [args ...]

Start the MCP proxy in one of two possible modes: as a client or a server.

positional arguments:
  command_or_url        Command or URL to connect to. When a URL, will run an SSE/StreamableHTTP client. Otherwise, if --named-server is not used, this will be the command for the default stdio client. If --named-server is used, this argument is ignored for stdio mode unless no default server is desired. See corresponding options for more details.

options:
  -h, --help            show this help message and exit
  --version             Show the version and exit

SSE/StreamableHTTP client options:
  -H, --headers KEY VALUE
                        Headers to pass to the SSE server. Can be used multiple times.
  --transport {sse,streamablehttp}
                        The transport to use for the client. Default is SSE.
  --verify-ssl [VALUE]  Control SSL verification when acting as a client. Use without a value to force verification, pass 'false' to disable, or provide a path to a PEM bundle.
  --no-verify-ssl       Disable SSL verification (alias for --verify-ssl false).
  --client-id CLIENT_ID
                        OAuth2 client ID for authentication
  --client-secret CLIENT_SECRET
                        OAuth2 client secret for authentication
  --token-url TOKEN_URL
                        OAuth2 token URL for authentication
  --username USERNAME   Username for authentication
  --password PASSWORD   Password for authentication
  --digest              Use digest authentication

stdio client options:
  args                  Any extra arguments to the command to spawn the default server. Ignored if only named servers are defined.
  -e, --env KEY VALUE   Environment variables used when spawning the default server. Can be used multiple times. For named servers, environment is inherited or passed via --pass-environment.
  --cwd CWD             The working directory to use when spawning the default server process. Named servers inherit the proxy's CWD.
  --pass-environment, --no-pass-environment
                        Pass through all environment variables when spawning all server processes.
  --log-level LEVEL     Set the log level. Default is INFO.
  --debug, --no-debug   Enable debug mode with detailed logging output. Equivalent to --log-level DEBUG. If both --debug and --log-level are provided, --debug takes precedence.
  --named-server NAME COMMAND_STRING
                        Define a named stdio server. NAME is for the URL path /servers/NAME/. COMMAND_STRING is a single string with the command and its arguments (e.g., 'uvx mcp-server-fetch --timeout 10'). These servers inherit the proxy's CWD and environment from --pass-environment.
  --named-server-config FILE_PATH
                        Path to a JSON configuration file for named stdio servers. If provided, this will be the exclusive source for named server definitions, and any --named-server CLI arguments will be ignored.

SSE server options:
  --port PORT           Port to expose an SSE server on. Default is a random port
  --host HOST           Host to expose an SSE server on. Default is 127.0.0.1
  --initialization-timeout SECONDS
                        Maximum time to initialize each stdio server. By default, wait indefinitely.
                        A timeout fails proxy startup and does not limit tool calls. Cleanup may take additional time.
  --stateless, --no-stateless
                        Enable stateless mode for streamable http transports. Default is False
  --sse-port SSE_PORT   (deprecated and ineffective; use --port)
  --sse-host SSE_HOST   (deprecated and ineffective; use --host)
  --allow-origin ALLOW_ORIGIN [ALLOW_ORIGIN ...]
                        Allowed origins for the SSE server. Can be used multiple times. Default is no CORS allowed.
  --allowed-host HOST   Additional trusted Host authority. Can be used multiple times.
  --expose-header HEADER
                        Headers to expose via Access-Control-Expose-Headers. Defaults to 'Mcp-Session-Id'. Can be used multiple times.

HTTP server requests are checked against trusted Host authorities before reaching routes. Loopback hostnames and IPs are trusted by default; a non-wildcard `--host` also trusts that bind address. A wildcard bind does not trust arbitrary Host values. Add the public hostname or reverse-proxy Host with `--allowed-host`, repeating the option for each authority (for example, `--allowed-host mcp.example.com --allowed-host mcp.example.com:8443`). A terminal `:*` may be used for an authority that accepts any port.

Raw Uvicorn HTTP access-log lines are disabled because SSE session IDs can appear in request query strings.

Host and browser Origin are separate checks. `--allow-origin '*'` allows browser Origins but does not bypass Host validation. Native clients that omit Origin remain supported. These checks do not authenticate callers; protect publicly reachable deployments with network controls or an authenticated reverse proxy.

Examples:
  mcp-proxy http://localhost:8080/sse
  mcp-proxy --no-verify-ssl https://server.local/sse
  mcp-proxy --transport streamablehttp http://localhost:8080/mcp
  mcp-proxy --transport streamablehttp --username user --password pass https://server.local/mcp
  mcp-proxy --headers Authorization 'Bearer YOUR_TOKEN' http://localhost:8080/sse
  mcp-proxy --client-id CLIENT_ID --client-secret CLIENT_SECRET --token-url https://auth.example.com/token http://localhost:8080/sse
  mcp-proxy --port 8080 -- your-command --arg1 value1 --arg2 value2
  mcp-proxy --named-server fetch 'uvx mcp-server-fetch' --port 8080
  mcp-proxy your-command --port 8080 -e KEY VALUE -e ANOTHER_KEY ANOTHER_VALUE
  mcp-proxy your-command --port 8080 --allow-origin='*'
  mcp-proxy your-command --port 8080 --allow-origin='*' --expose-header Custom-Header

Example config file

{
  "mcpServers": {
    "fetch": {
      "enabled": true,
      "timeout": 60,
      "command": "uvx",
      "args": [
        "mcp-server-fetch"
      ],
      "transportType": "stdio"
    },
    "github": {
      "timeout": 60,
      "command": "npx",
      "args": [
        "-y",
        "@modelcontextprotocol/server-github"
      ],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      },
      "transportType": "stdio"
    }
  }
}

Testing

Check the mcp-proxy server by running it with the mcp-server-fetch server. You can use the inspector tool to test the target server.

# Run the stdio server called mcp-server-fetch behind the proxy over SSE
mcp-proxy --port=8080 uvx mcp-server-fetch &

# Connect to the SSE proxy server spawned above using another instance of mcp-proxy given the URL of the SSE server
mcp-proxy http://127.0.0.1:8080/sse

# Send CTRL+C to stop the second server

# Bring the first server to the foreground
fg

# Send CTRL+C to stop the first server

Metadata

Release files for mcp-proxy 0.14.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for mcp-proxy 0.14.0
File Size Uploaded Provenance Actions
mcp_proxy-0.14.0.tar.gz 94.6 kB With provenance Details for mcp_proxy-0.14.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for mcp-proxy 0.14.0
File Interpreter ABI Platform Actions
mcp_proxy-0.14.0-py3-none-any.whl Python 3 none any Details for mcp_proxy-0.14.0-py3-none-any.whl

Total release size: 130.6 kB

Release files / mcp_proxy-0.14.0.tar.gz

Download URL mcp_proxy-0.14.0.tar.gz
Size 94.6 kB
Tags Source
SHA-256 checksum
How to use checksums
72ea8795d3f572d22fe43878d69b088e88ff366d268f40ccc631cda13adc2e47
BLAKE2b-256 checksum
How to use checksums
f85122df0ca8541a81bcad74796c5d02c4def086af0d9c7690c53a2508398770
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Release files / mcp_proxy-0.14.0-py3-none-any.whl

Download URL mcp_proxy-0.14.0-py3-none-any.whl
Size 36.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1d0e8bc42911455ac0f38a0b00a0a0409f800837917c2092c9d4d31ac69c738c
BLAKE2b-256 checksum
How to use checksums
6a1c733dc5a0e41912af53b6729685c48fc5d5f9fad56fdedbb358f2c87c4a2d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

Security for mcp-proxy 0.14.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for mcp_proxy-0.14.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 2, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page