mirror of
https://github.com/open-webui/mcpo
synced 2025-06-26 18:26:58 +00:00
commit
3b5f4fe17e
@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [0.0.11] - 2025-04-12
|
||||
|
||||
### Added
|
||||
|
||||
- 🌊 **SSE-Based MCP Server Support**: mcpo now supports SSE (Server-Sent Events) MCP servers out of the box—just pass 'mcpo --server-type "sse" -- http://127.0.0.1:8001/sse' when launching or use the standard "url" field in your config for seamless real-time integration with streaming MCP endpoints; see the README for full examples and enhanced workflows with live progress, event pushes, and interactive updates.
|
||||
|
||||
## [0.0.10] - 2025-04-10
|
||||
|
||||
### Added
|
||||
|
||||
13
README.md
13
README.md
@ -40,6 +40,12 @@ pip install mcpo
|
||||
mcpo --port 8000 --api-key "top-secret" -- your_mcp_server_command
|
||||
```
|
||||
|
||||
To use an SSE-compatible MCP server, simply specify the server type and endpoint:
|
||||
|
||||
```bash
|
||||
mcpo --port 8000 --api-key "top-secret" --server-type "sse" -- http://127.0.0.1:8001/sse
|
||||
```
|
||||
|
||||
You can also run mcpo via Docker with no installation:
|
||||
|
||||
```bash
|
||||
@ -78,7 +84,10 @@ Example config.json:
|
||||
"time": {
|
||||
"command": "uvx",
|
||||
"args": ["mcp-server-time", "--local-timezone=America/New_York"]
|
||||
}
|
||||
},
|
||||
"mcp_sse": {
|
||||
"url": "http://127.0.0.1:8001/sse"
|
||||
} // SSE MCP Server
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -110,7 +119,7 @@ To contribute or run tests locally:
|
||||
|
||||
2. **Run tests:**
|
||||
```bash
|
||||
pytest
|
||||
uv run pytest
|
||||
```
|
||||
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
[project]
|
||||
name = "mcpo"
|
||||
version = "0.0.10"
|
||||
version = "0.0.11"
|
||||
description = "A simple, secure MCP-to-OpenAPI proxy server"
|
||||
authors = [
|
||||
{ name = "Timothy Jaeryang Baek", email = "tim@openwebui.com" }
|
||||
|
||||
@ -33,7 +33,10 @@ def main(
|
||||
Optional[str],
|
||||
typer.Option("--env-path", help="Path to environment variables file"),
|
||||
] = None,
|
||||
config: Annotated[
|
||||
server_type: Annotated[
|
||||
Optional[str], typer.Option("--type", "--server-type", help="Server type")
|
||||
] = "stdio",
|
||||
config_path: Annotated[
|
||||
Optional[str], typer.Option("--config", "-c", help="Config file path")
|
||||
] = None,
|
||||
name: Annotated[
|
||||
@ -56,7 +59,7 @@ def main(
|
||||
] = None,
|
||||
):
|
||||
server_command = None
|
||||
if not config:
|
||||
if not config_path:
|
||||
# Find the position of "--"
|
||||
if "--" not in sys.argv:
|
||||
typer.echo("Usage: mcpo --host 0.0.0.0 --port 8000 -- your_mcp_command")
|
||||
@ -71,8 +74,8 @@ def main(
|
||||
|
||||
from mcpo.main import run
|
||||
|
||||
if config:
|
||||
print("Starting MCP OpenAPI Proxy with config file:", config)
|
||||
if config_path:
|
||||
print("Starting MCP OpenAPI Proxy with config file:", config_path)
|
||||
else:
|
||||
print(
|
||||
f"Starting MCP OpenAPI Proxy on {host}:{port} with command: {' '.join(server_command)}"
|
||||
@ -114,7 +117,8 @@ def main(
|
||||
port,
|
||||
api_key=api_key,
|
||||
cors_allow_origins=cors_allow_origins,
|
||||
config=config,
|
||||
server_type=server_type,
|
||||
config_path=config_path,
|
||||
name=name,
|
||||
description=description,
|
||||
version=version,
|
||||
|
||||
@ -11,7 +11,7 @@ from starlette.routing import Mount
|
||||
|
||||
from mcp import ClientSession, StdioServerParameters
|
||||
from mcp.client.stdio import stdio_client
|
||||
|
||||
from mcp.client.sse import sse_client
|
||||
|
||||
from mcpo.utils.main import get_model_fields, get_tool_handler
|
||||
from mcpo.utils.auth import get_verify_api_key
|
||||
@ -65,13 +65,18 @@ async def create_dynamic_endpoints(app: FastAPI, api_dependency=None):
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI):
|
||||
server_type = getattr(app.state, "server_type", "stdio")
|
||||
command = getattr(app.state, "command", None)
|
||||
args = getattr(app.state, "args", [])
|
||||
env = getattr(app.state, "env", {})
|
||||
|
||||
args = args if isinstance(args, list) else [args]
|
||||
api_dependency = getattr(app.state, "api_dependency", None)
|
||||
|
||||
if not command:
|
||||
if (server_type == "stdio" and not command) or (
|
||||
server_type == "sse" and not args[0]
|
||||
):
|
||||
# Main app lifespan (when config_path is provided)
|
||||
async with AsyncExitStack() as stack:
|
||||
for route in app.routes:
|
||||
if isinstance(route, Mount) and isinstance(route.app, FastAPI):
|
||||
@ -79,19 +84,25 @@ async def lifespan(app: FastAPI):
|
||||
route.app.router.lifespan_context(route.app), # noqa
|
||||
)
|
||||
yield
|
||||
|
||||
else:
|
||||
server_params = StdioServerParameters(
|
||||
command=command,
|
||||
args=args,
|
||||
env={**env},
|
||||
)
|
||||
if server_type == "stdio":
|
||||
server_params = StdioServerParameters(
|
||||
command=command,
|
||||
args=args,
|
||||
env={**env},
|
||||
)
|
||||
|
||||
async with stdio_client(server_params) as (reader, writer):
|
||||
async with ClientSession(reader, writer) as session:
|
||||
app.state.session = session
|
||||
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
||||
yield
|
||||
async with stdio_client(server_params) as (reader, writer):
|
||||
async with ClientSession(reader, writer) as session:
|
||||
app.state.session = session
|
||||
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
||||
yield
|
||||
if server_type == "sse":
|
||||
async with sse_client(url=args[0]) as (reader, writer):
|
||||
async with ClientSession(reader, writer) as session:
|
||||
app.state.session = session
|
||||
await create_dynamic_endpoints(app, api_dependency=api_dependency)
|
||||
yield
|
||||
|
||||
|
||||
async def run(
|
||||
@ -104,10 +115,14 @@ async def run(
|
||||
# Server API Key
|
||||
api_dependency = get_verify_api_key(api_key) if api_key else None
|
||||
|
||||
# MCP Config
|
||||
config_path = kwargs.get("config")
|
||||
# MCP Server
|
||||
server_type = kwargs.get("server_type") # "stdio" or "sse" or "http"
|
||||
server_command = kwargs.get("server_command")
|
||||
|
||||
# MCP Config
|
||||
config_path = kwargs.get("config_path")
|
||||
|
||||
# mcpo server
|
||||
name = kwargs.get("name") or "MCP OpenAPI Proxy"
|
||||
description = (
|
||||
kwargs.get("description") or "Automatically generated API from MCP Tool Schemas"
|
||||
@ -135,12 +150,18 @@ async def run(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
if server_command:
|
||||
if server_type == "sse":
|
||||
main_app.state.server_type = "sse"
|
||||
main_app.state.args = server_command[0]
|
||||
main_app.state.api_dependency = api_dependency
|
||||
|
||||
elif server_command:
|
||||
main_app.state.command = server_command[0]
|
||||
main_app.state.args = server_command[1:]
|
||||
main_app.state.env = os.environ.copy()
|
||||
|
||||
main_app.state.api_dependency = api_dependency
|
||||
|
||||
elif config_path:
|
||||
with open(config_path, "r") as f:
|
||||
config_data = json.load(f)
|
||||
@ -166,9 +187,15 @@ async def run(
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
sub_app.state.command = server_cfg["command"]
|
||||
sub_app.state.args = server_cfg.get("args", [])
|
||||
sub_app.state.env = {**os.environ, **server_cfg.get("env", {})}
|
||||
if server_cfg.get("command"):
|
||||
# stdio
|
||||
sub_app.state.command = server_cfg["command"]
|
||||
sub_app.state.args = server_cfg.get("args", [])
|
||||
sub_app.state.env = {**os.environ, **server_cfg.get("env", {})}
|
||||
if server_cfg.get("url"):
|
||||
# SSE
|
||||
sub_app.state.server_type = "sse"
|
||||
sub_app.state.args = server_cfg["url"]
|
||||
|
||||
sub_app.state.api_dependency = api_dependency
|
||||
|
||||
|
||||
@ -2,7 +2,7 @@ import pytest
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Any, List, Dict
|
||||
|
||||
from src.mcpo.utils.main import _process_schema_property
|
||||
from mcpo.utils.main import _process_schema_property
|
||||
|
||||
|
||||
_model_cache = {}
|
||||
Loading…
Reference in New Issue
Block a user