Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
89 changes: 74 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@

## About

Connect to MCP servers that run on SSE transport using the MCP Proxy server.
Connect to MCP servers that run on SSE transport, or expose stdio servers as an SSE server using the MCP Proxy server.

## stdio to SSE

```mermaid
graph LR
Expand All @@ -25,6 +27,19 @@ graph LR
> [!TIP]
> As of now, Claude Desktop does not support MCP servers that run on SSE transport. This server is a workaround to enable the support.

## SSE to stdio

```mermaid
graph LR
A["LLM Client"] <--> B["mcp-proxy"]
B <--> 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
```


## Installation

The stable version of the package is available on the PyPI repository. You can install it using the following command:
Expand Down Expand Up @@ -60,25 +75,69 @@ Configure Claude Desktop to recognize the MCP server.

2. Add the server configuration

```json
{
"mcpServers": {
"mcp-proxy": {
"command": "mcp-proxy",
"env": {
"SSE_URL": "http://example.io/sse"
}
```json
{
"mcpServers": {
"mcp-proxy": {
"command": "mcp-proxy",
"env": {
"SSE_URL": "http://example.io/sse"
}
}
}
}
}

```

## Detailed Configuration

The MCP Proxy server can support two different approaches for proxying:
- stdio to SSE: To allow clients like Claude Desktop to run this proxy directly. The proxy is started by the LLM Client as a server that proxies to a remote server over SSE.
- SSE to stdio: To allow a client that supports remote SSE servers to access a local stdio server. This proxy opens
a port to listen for SSE requests, then spawns a local stdio server that handles MCP requests.

### stdio to SSE

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

## Advanced Configuration
Arguments

### Environment Variables
| Name | Description |
| ------------------ | ---------------------------------------------------------------------------------- |
| `--sse-url` | Required. The MCP server SSE endpoint to connect to e.g. http://example.io/sse same as environment variable `SSE_URL` |

Environment Variables

| Name | Description |
| ---------------- | ---------------------------------------------------------------------------------- |
| SSE_URL | The MCP server SSE endpoint to connect to e.g. http://example.io/sse |
| API_ACCESS_TOKEN | Added in the `Authorization` header of the HTTP request as a `Bearer` access token |
| `SSE_URL` | The MCP server SSE endpoint to connect to e.g. http://example.io/sse same as `--sse-url` |
| `API_ACCESS_TOKEN` | Added in the `Authorization` header of the HTTP request as a `Bearer` access token |


Example usage:

```bash
uv run mcp-proxy --sse-url=http://example.io/sse
```


### SSE to stdio

Run a proxy server exposing an SSE server that connects to a local stdio server. This allows remote connections to the stdio server.

Arguments

| Name | Description |
| ------------------ | ---------------------------------------------------------------------------------- |
| `--sse-port` | Required. The SSE server port to listen to e.g. `8080` |
| `--sse-host` | Optional. The host IP address that the SSE server will listen on e.g. `0.0.0.0`. By default only listens on localhost. |
| command | Required. The path for the MCP stdio server command line. |
| arg1 arg2 ... | Optional. Additional arguments to the MCP stdio server command line program. |

Example usage:

```bash
uv run mcp-proxy --sse-port=8080 -e FOO=BAR -- /path/to/command arg1 arg2
```

This will start an MCP server that can be connected to at `http://127.0.0.1:8080/sse`
90 changes: 85 additions & 5 deletions src/mcp_proxy/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,104 @@

"""

import argparse
import asyncio
import logging
import os
import sys
import typing as t

from mcp.client.stdio import StdioServerParameters

from .sse_client import run_sse_client
from .sse_server import SseServerSettings, run_sse_server

logging.basicConfig(level=logging.DEBUG)
SSE_URL: t.Final[str] = os.getenv("SSE_URL", "")
SSE_URL: t.Final[str | None] = os.getenv("SSE_URL", None)
API_ACCESS_TOKEN: t.Final[str | None] = os.getenv("API_ACCESS_TOKEN", None)

if not SSE_URL:
raise ValueError("SSE_URL environment variable is not set")


def main() -> None:
"""Start the client using asyncio."""
asyncio.run(run_sse_client(SSE_URL, api_access_token=API_ACCESS_TOKEN))
parser = argparse.ArgumentParser()
parser.add_argument(
"command_or_url",
help=(
"Command or URL to connect to. When a URL, will run an SSE client "
"to connect to the server, otherwise will run the command and "
"connect as a stdio client. Can also be set as environment variable SSE_URL."
),
nargs="?", # Required below to allow for coming form env var
default=SSE_URL,
)

sse_client_group = parser.add_argument_group("SSE client options")
sse_client_group.add_argument(
"--api-access-token",
default=API_ACCESS_TOKEN,
help=(
"Access token Authorization header passed by the client to the SSE "
"server. Can also be set as environment variable API_ACCESS_TOKEN."
),
)

stdio_client_options = parser.add_argument_group("stdio client options")
stdio_client_options.add_argument(
"args",
nargs="*",
help="Arguments to the command to run to spawn the server",
)
stdio_client_options.add_argument(
"-e",
"--env",
nargs=2,
action="append",
metavar=("KEY", "VALUE"),
help="Environment variables used when spawning the server. Can be used multiple times.",
default=[],
)

sse_server_group = parser.add_argument_group("SSE server options")
sse_server_group.add_argument(
"--sse-port",
type=int,
default=None,
help="Port to expose an SSE server on",
)
sse_server_group.add_argument(
"--sse-host",
default="127.0.0.1",
help="Host to expose an SSE server on",
)

args = parser.parse_args()

if not args.command_or_url:
parser.print_help()
sys.exit(1)

if (
SSE_URL
or args.command_or_url.startswith("http://")
or args.command_or_url.startswith("https://")
):
# Start a client connected to the SSE server, and expose as a stdio server
logging.debug("Starting SSE client and stdio server")
asyncio.run(run_sse_client(args.command_or_url, api_access_token=API_ACCESS_TOKEN))
return

# Start a client connected to the given command, and expose as an SSE server
logging.debug("Starting stdio client and SSE server")
stdio_params = StdioServerParameters(
command=args.command_or_url,
args=args.args,
env=dict(args.env),
)
sse_settings = SseServerSettings(
bind_host=args.sse_host,
port=args.sse_port,
)
asyncio.run(run_sse_server(stdio_params, sse_settings))


if __name__ == "__main__":
Expand Down
10 changes: 5 additions & 5 deletions src/mcp_proxy/sse_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@
class SseServerSettings:
"""Settings for the server."""

bind_host: str = "127.0.0.1"
port: int = 8000
bind_host: str
port: int
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO"


def create_starlette_app(mcp_server: Server, debug: bool | None = None) -> Starlette:
def create_starlette_app(mcp_server: Server, debug: bool = False) -> Starlette: # noqa: FBT001, FBT002
"""Create a Starlette application that can server the provied mcp server with SSE."""
sse = SseServerTransport("/messages/")

Expand All @@ -41,7 +41,7 @@ async def handle_sse(request: Request) -> None:
)

return Starlette(
debug=debug,
debug=debug or False,
routes=[
Route("/sse", endpoint=handle_sse),
Mount("/messages/", app=sse.handle_post_message),
Expand All @@ -64,7 +64,7 @@ async def run_sse_server(
mcp_server = await create_proxy_server(session)

# Bind SSE request handling to MCP server
starlette_app = await create_starlette_app(mcp_server, sse_settings.log_level == "DEBUG")
starlette_app = create_starlette_app(mcp_server, debug=(sse_settings.log_level == "DEBUG"))

# Configure HTTP server
config = uvicorn.Config(
Expand Down
Loading