feat: support streamable transport in client mode (#70)
When running in client mode, use `--transport=streamablehttp` to enforce the new communication method between `mcp-proxy` and remote MCP servers.
This commit is contained in:
62
README.md
62
README.md
@@ -7,23 +7,23 @@
|
||||
[](https://smithery.ai/server/mcp-proxy)
|
||||
|
||||
- [mcp-proxy](#mcp-proxy)
|
||||
- [About](#about)
|
||||
- [1. stdio to SSE/StreamableHttp](#1-stdio-to-sse)
|
||||
- [1.1 Configuration](#11-configuration)
|
||||
- [1.2 Example usage](#12-example-usage)
|
||||
- [2. SSE to stdio](#2-sse-to-stdio)
|
||||
- [2.1 Configuration](#21-configuration)
|
||||
- [2.2 Example usage](#22-example-usage)
|
||||
- [Installation](#installation)
|
||||
- [Installing via Smithery](#installing-via-smithery)
|
||||
- [Installing via PyPI](#installing-via-pypi)
|
||||
- [Installing via Github repository (latest)](#installing-via-github-repository-latest)
|
||||
- [Installing as container](#installing-as-container)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Extending the container image](#extending-the-container-image)
|
||||
- [Docker Compose Setup](#docker-compose-setup)
|
||||
- [Command line arguments](#command-line-arguments)
|
||||
- [Testing](#testing)
|
||||
- [About](#about)
|
||||
- [1. stdio to SSE/StreamableHTTP](#1-stdio-to-ssestreamablehttp)
|
||||
- [1.1 Configuration](#11-configuration)
|
||||
- [1.2 Example usage](#12-example-usage)
|
||||
- [2. SSE to stdio](#2-sse-to-stdio)
|
||||
- [2.1 Configuration](#21-configuration)
|
||||
- [2.2 Example usage](#22-example-usage)
|
||||
- [Installation](#installation)
|
||||
- [Installing via Smithery](#installing-via-smithery)
|
||||
- [Installing via PyPI](#installing-via-pypi)
|
||||
- [Installing via Github repository (latest)](#installing-via-github-repository-latest)
|
||||
- [Installing as container](#installing-as-container)
|
||||
- [Troubleshooting](#troubleshooting)
|
||||
- [Extending the container image](#extending-the-container-image)
|
||||
- [Docker Compose Setup](#docker-compose-setup)
|
||||
- [Command line arguments](#command-line-arguments)
|
||||
- [Testing](#testing)
|
||||
|
||||
## About
|
||||
|
||||
@@ -51,19 +51,20 @@ graph LR
|
||||
|
||||
### 1.1 Configuration
|
||||
|
||||
This mode requires passing the URL to the MCP Server SSE endpoint as the first argument to the program.
|
||||
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`.
|
||||
|
||||
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' |
|
||||
| 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 |
|
||||
|
||||
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
|
||||
@@ -115,7 +116,7 @@ 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 | The host IP address that the MCP server will listen on | 0.0.0.0 |
|
||||
@@ -256,22 +257,24 @@ services:
|
||||
## Command line arguments
|
||||
|
||||
```bash
|
||||
usage: mcp-proxy [-h] [-H KEY VALUE] [-e KEY VALUE] [--cwd CWD] [--pass-environment | --no-pass-environment] [--debug | --no-debug] [--port PORT]
|
||||
[--host HOST] [--stateless | --no-stateless] [--sse-port SSE_PORT] [--sse-host SSE_HOST]
|
||||
usage: mcp-proxy [-h] [-H KEY VALUE] [--transport {sse,streamablehttp}] [-e KEY VALUE] [--cwd CWD] [--pass-environment | --no-pass-environment]
|
||||
[--debug | --no-debug] [--port PORT] [--host HOST] [--stateless | --no-stateless] [--sse-port SSE_PORT] [--sse-host SSE_HOST]
|
||||
[--allow-origin ALLOW_ORIGIN [ALLOW_ORIGIN ...]]
|
||||
[command_or_url] [args ...]
|
||||
|
||||
Start the MCP proxy in one of two possible modes: as an SSE or stdio client.
|
||||
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 client, otherwise will run the given command and connect as a stdio client. See corresponding options for more details.
|
||||
command_or_url Command or URL to connect to. When a URL, will run an SSE/StreamableHTTP client, otherwise will run the given command and connect as a stdio client. See corresponding options for more details.
|
||||
|
||||
options:
|
||||
-h, --help show this help message and exit
|
||||
|
||||
SSE client options:
|
||||
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.
|
||||
|
||||
stdio client options:
|
||||
args Any extra arguments to the command to spawn the server
|
||||
@@ -293,6 +296,7 @@ SSE server options:
|
||||
|
||||
Examples:
|
||||
mcp-proxy http://localhost:8080/sse
|
||||
mcp-proxy --transport streamablehttp http://localhost:8080/mcp
|
||||
mcp-proxy --headers Authorization 'Bearer YOUR_TOKEN' http://localhost:8080/sse
|
||||
mcp-proxy --port 8080 -- your-command --arg1 value1 --arg2 value2
|
||||
mcp-proxy your-command --port 8080 -e KEY VALUE -e ANOTHER_KEY ANOTHER_VALUE
|
||||
|
||||
@@ -25,7 +25,7 @@ classifiers = [
|
||||
"Topic :: Utilities",
|
||||
"Typing :: Typed",
|
||||
]
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
requires-python = ">=3.10"
|
||||
dependencies = ["mcp>=1.8.0,<2.0.0", "uvicorn>=0.34.0"]
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ from mcp.client.stdio import StdioServerParameters
|
||||
|
||||
from .mcp_server import MCPServerSettings, run_mcp_server
|
||||
from .sse_client import run_sse_client
|
||||
from .streamablehttp_client import run_streamablehttp_client
|
||||
|
||||
# Deprecated env var. Here for backwards compatibility.
|
||||
SSE_URL: t.Final[str | None] = os.getenv(
|
||||
@@ -28,12 +29,11 @@ SSE_URL: t.Final[str | None] = os.getenv(
|
||||
def main() -> None:
|
||||
"""Start the client using asyncio."""
|
||||
parser = argparse.ArgumentParser(
|
||||
description=(
|
||||
"Start the MCP proxy in one of two possible modes: as an SSE or stdio client."
|
||||
),
|
||||
description=("Start the MCP proxy in one of two possible modes: as a client or a server."),
|
||||
epilog=(
|
||||
"Examples:\n"
|
||||
" mcp-proxy http://localhost:8080/sse\n"
|
||||
" mcp-proxy --transport streamablehttp http://localhost:8080/mcp\n"
|
||||
" mcp-proxy --headers Authorization 'Bearer YOUR_TOKEN' http://localhost:8080/sse\n"
|
||||
" mcp-proxy --port 8080 -- your-command --arg1 value1 --arg2 value2\n"
|
||||
" mcp-proxy your-command --port 8080 -e KEY VALUE -e ANOTHER_KEY ANOTHER_VALUE\n"
|
||||
@@ -44,7 +44,7 @@ def main() -> None:
|
||||
parser.add_argument(
|
||||
"command_or_url",
|
||||
help=(
|
||||
"Command or URL to connect to. When a URL, will run an SSE client, "
|
||||
"Command or URL to connect to. When a URL, will run an SSE/StreamableHTTP client, "
|
||||
"otherwise will run the given command and connect as a stdio client. "
|
||||
"See corresponding options for more details."
|
||||
),
|
||||
@@ -52,8 +52,8 @@ def main() -> None:
|
||||
default=SSE_URL,
|
||||
)
|
||||
|
||||
sse_client_group = parser.add_argument_group("SSE client options")
|
||||
sse_client_group.add_argument(
|
||||
client_group = parser.add_argument_group("SSE/StreamableHTTP client options")
|
||||
client_group.add_argument(
|
||||
"-H",
|
||||
"--headers",
|
||||
nargs=2,
|
||||
@@ -62,6 +62,12 @@ def main() -> None:
|
||||
help="Headers to pass to the SSE server. Can be used multiple times.",
|
||||
default=[],
|
||||
)
|
||||
client_group.add_argument(
|
||||
"--transport",
|
||||
choices=["sse", "streamablehttp"],
|
||||
default="sse", # For backwards compatibility
|
||||
help="The transport to use for the client. Default is SSE.",
|
||||
)
|
||||
|
||||
stdio_client_options = parser.add_argument_group("stdio client options")
|
||||
stdio_client_options.add_argument(
|
||||
@@ -155,7 +161,10 @@ def main() -> None:
|
||||
headers = dict(args.headers)
|
||||
if api_access_token := os.getenv("API_ACCESS_TOKEN", None):
|
||||
headers["Authorization"] = f"Bearer {api_access_token}"
|
||||
asyncio.run(run_sse_client(args.command_or_url, headers=headers))
|
||||
if args.transport == "streamablehttp":
|
||||
asyncio.run(run_streamablehttp_client(args.command_or_url, headers=headers))
|
||||
else:
|
||||
asyncio.run(run_sse_client(args.command_or_url, headers=headers))
|
||||
return
|
||||
|
||||
# Start a client connected to the given command, and expose as an SSE server
|
||||
|
||||
30
src/mcp_proxy/streamablehttp_client.py
Normal file
30
src/mcp_proxy/streamablehttp_client.py
Normal file
@@ -0,0 +1,30 @@
|
||||
"""Create a local server that proxies requests to a remote server over SSE."""
|
||||
|
||||
from typing import Any
|
||||
|
||||
from mcp.client.session import ClientSession
|
||||
from mcp.client.streamable_http import streamablehttp_client
|
||||
from mcp.server.stdio import stdio_server
|
||||
|
||||
from .proxy_server import create_proxy_server
|
||||
|
||||
|
||||
async def run_streamablehttp_client(url: str, headers: dict[str, Any] | None = None) -> None:
|
||||
"""Run the SSE client.
|
||||
|
||||
Args:
|
||||
url: The URL to connect to.
|
||||
headers: Headers for connecting to MCP server.
|
||||
|
||||
"""
|
||||
async with (
|
||||
streamablehttp_client(url=url, headers=headers) as (read, write, _),
|
||||
ClientSession(read, write) as session,
|
||||
):
|
||||
app = await create_proxy_server(session)
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await app.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
app.create_initialization_options(),
|
||||
)
|
||||
8
uv.lock
generated
8
uv.lock
generated
@@ -191,7 +191,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "mcp"
|
||||
version = "1.8.0"
|
||||
version = "1.9.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "anyio" },
|
||||
@@ -204,14 +204,14 @@ dependencies = [
|
||||
{ name = "starlette" },
|
||||
{ name = "uvicorn", marker = "sys_platform != 'emscripten'" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/ff/97/0a3e08559557b0ac5799f9fb535fbe5a4e4dcdd66ce9d32e7a74b4d0534d/mcp-1.8.0.tar.gz", hash = "sha256:263dfb700540b726c093f0c3e043f66aded0730d0b51f04eb0a3eb90055fe49b", size = 264641 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/e7/bc/54aec2c334698cc575ca3b3481eed627125fb66544152fa1af927b1a495c/mcp-1.9.1.tar.gz", hash = "sha256:19879cd6dde3d763297617242888c2f695a95dfa854386a6a68676a646ce75e4", size = 316247 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/b2/b2/4ac3bd17b1fdd65658f18de4eb0c703517ee0b483dc5f56467802a9197e0/mcp-1.8.0-py3-none-any.whl", hash = "sha256:889d9d3b4f12b7da59e7a3933a0acadae1fce498bfcd220defb590aa291a1334", size = 119544 },
|
||||
{ url = "https://files.pythonhosted.org/packages/a6/c0/4ac795585a22a0a2d09cd2b1187b0252d2afcdebd01e10a68bbac4d34890/mcp-1.9.1-py3-none-any.whl", hash = "sha256:2900ded8ffafc3c8a7bfcfe8bc5204037e988e753ec398f371663e6a06ecd9a9", size = 130261 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mcp-proxy"
|
||||
version = "0.6.0"
|
||||
version = "0.7.0"
|
||||
source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "mcp" },
|
||||
|
||||
Reference in New Issue
Block a user