Skip to content

Commit b818c67

Browse files
committed
feat:sse support for 'mcpm run'
1 parent 2427bff commit b818c67

File tree

1 file changed

+55
-22
lines changed

1 file changed

+55
-22
lines changed

src/mcpm/commands/run.py

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,9 @@ def find_installed_server(server_name):
3232
return None, None
3333

3434

35-
async def run_server_with_fastmcp(server_config, server_name, http_mode=False, port=None, host="127.0.0.1"):
35+
async def run_server_with_fastmcp(
36+
server_config, server_name, http_mode=False, sse_mode=False, port=None, host="127.0.0.1"
37+
):
3638
"""Run server using FastMCP proxy (stdio or HTTP)."""
3739
try:
3840
# Use default port if none specified
@@ -41,11 +43,17 @@ async def run_server_with_fastmcp(server_config, server_name, http_mode=False, p
4143
# Note: Usage tracking is handled by proxy middleware
4244

4345
# Create FastMCP proxy for single server
44-
action = "run_http" if http_mode else "run"
46+
if sse_mode:
47+
action = "run_sse"
48+
elif http_mode:
49+
action = "run_http"
50+
else:
51+
action = "run"
52+
4553
proxy = await create_mcpm_proxy(
4654
servers=[server_config],
4755
name=f"mcpm-run-{server_name}",
48-
stdio_mode=not http_mode, # stdio_mode=False for HTTP
56+
stdio_mode=not (http_mode or sse_mode), # stdio_mode=False for HTTP/SSE
4957
action=action,
5058
)
5159

@@ -55,25 +63,35 @@ async def run_server_with_fastmcp(server_config, server_name, http_mode=False, p
5563
# Re-suppress library logging after FastMCP initialization
5664
ensure_dependency_logging_suppressed()
5765

58-
if http_mode:
66+
if http_mode or sse_mode:
5967
# Try to find an available port if the requested one is taken
6068
actual_port = await find_available_port(port)
6169
if actual_port != port:
6270
logger.debug(f"Port {port} is busy, using port {actual_port} instead")
6371

6472
# Display server information in a nice panel
65-
http_url = f"http://{host}:{actual_port}/mcp/"
66-
panel_content = f"[bold]Server:[/] {server_name}\n[bold]URL:[/] [cyan]{http_url}[/cyan]\n\n[dim]Press Ctrl+C to stop the server[/]"
67-
panel = Panel(
68-
panel_content, title="🌐 Local Server Running", title_align="left", border_style="green", padding=(1, 2)
69-
)
73+
if sse_mode:
74+
server_url = f"http://{host}:{actual_port}/sse/"
75+
title = "📡 SSE Server Running"
76+
else:
77+
server_url = f"http://{host}:{actual_port}/mcp/"
78+
title = "🌐 Local Server Running"
79+
80+
panel_content = f"[bold]Server:[/] {server_name}\n[bold]URL:[/] [cyan]{server_url}[/cyan]\n\n[dim]Press Ctrl+C to stop the server[/]"
81+
panel = Panel(panel_content, title=title, title_align="left", border_style="green", padding=(1, 2))
7082
console.print(panel)
7183

72-
logger.debug(f"Starting FastMCP proxy for server '{server_name}' on {host}:{actual_port}")
84+
mode = "SSE" if sse_mode else "HTTP"
85+
logger.debug(f"Starting FastMCP proxy for server '{server_name}' in {mode} mode on {host}:{actual_port}")
7386

74-
# Run FastMCP proxy in HTTP mode with uvicorn logging control
87+
# Run FastMCP proxy in HTTP/SSE mode with uvicorn logging control
88+
transport = "sse" if sse_mode else "http"
7589
await proxy.run_http_async(
76-
host=host, port=actual_port, show_banner=False, uvicorn_config={"log_level": get_uvicorn_log_level()}
90+
host=host,
91+
port=actual_port,
92+
show_banner=False,
93+
transport=transport,
94+
uvicorn_config={"log_level": get_uvicorn_log_level()},
7795
)
7896
else:
7997
# Run FastMCP proxy in stdio mode (default)
@@ -84,7 +102,7 @@ async def run_server_with_fastmcp(server_config, server_name, http_mode=False, p
84102

85103
except KeyboardInterrupt:
86104
logger.info("Server execution interrupted")
87-
if http_mode:
105+
if http_mode or sse_mode:
88106
logger.warning("\nServer execution interrupted")
89107
return 130
90108
except Exception as e:
@@ -114,19 +132,23 @@ async def find_available_port(preferred_port, max_attempts=10):
114132
@click.command()
115133
@click.argument("server_name")
116134
@click.option("--http", is_flag=True, help="Run server over HTTP instead of stdio")
117-
@click.option("--port", type=int, default=DEFAULT_PORT, help=f"Port for HTTP mode (default: {DEFAULT_PORT})")
118-
@click.option("--host", type=str, default="127.0.0.1", help="Host address for HTTP mode (default: 127.0.0.1)")
135+
@click.option("--sse", is_flag=True, help="Run server over SSE instead of stdio")
136+
@click.option("--port", type=int, default=DEFAULT_PORT, help=f"Port for HTTP / SSE mode (default: {DEFAULT_PORT})")
137+
@click.option("--host", type=str, default="127.0.0.1", help="Host address for HTTP / SSE mode (default: 127.0.0.1)")
119138
@click.help_option("-h", "--help")
120-
def run(server_name, http, port, host):
121-
"""Execute a server from global configuration over stdio or HTTP.
139+
def run(server_name, http, sse, port, host):
140+
"""Execute a server from global configuration over stdio, HTTP, or SSE.
122141
123142
Runs an installed MCP server from the global configuration. By default
124-
runs over stdio for client communication, but can run over HTTP with --http.
143+
runs over stdio for client communication, but can run over HTTP with --http
144+
or over SSE with --sse.
125145
126146
Examples:
127147
mcpm run mcp-server-browse # Run over stdio (default)
128148
mcpm run --http mcp-server-browse # Run over HTTP on 127.0.0.1:6276
149+
mcpm run --sse mcp-server-browse # Run over SSE on 127.0.0.1:6276
129150
mcpm run --http --port 9000 filesystem # Run over HTTP on 127.0.0.1:9000
151+
mcpm run --sse --port 9000 filesystem # Run over SSE on 127.0.0.1:9000
130152
mcpm run --http --host 0.0.0.0 filesystem # Run over HTTP on 0.0.0.0:6276
131153
132154
Note: stdio mode is typically used in MCP client configurations:
@@ -164,20 +186,31 @@ def run(server_name, http, port, host):
164186
if server_config.headers:
165187
logger.debug(f"Headers: {list(server_config.headers.keys())}")
166188

167-
logger.debug(f"Mode: {'HTTP' if http else 'stdio'}")
168-
if http:
189+
# Validate mutually exclusive options
190+
if http and sse:
191+
logger.error("Error: Cannot use both --http and --sse flags together")
192+
sys.exit(1)
193+
194+
mode = "SSE" if sse else "HTTP" if http else "stdio"
195+
logger.debug(f"Mode: {mode}")
196+
if http or sse:
169197
logger.debug(f"Port: {port}")
170198

171199
# Choose execution method
172200
if http:
173201
# Use FastMCP proxy for HTTP mode
174202
exit_code = asyncio.run(
175-
run_server_with_fastmcp(server_config, server_name, http_mode=True, port=port, host=host)
203+
run_server_with_fastmcp(server_config, server_name, http_mode=True, sse_mode=False, port=port, host=host)
204+
)
205+
elif sse:
206+
# Use FastMCP proxy for SSE mode
207+
exit_code = asyncio.run(
208+
run_server_with_fastmcp(server_config, server_name, http_mode=False, sse_mode=True, port=port, host=host)
176209
)
177210
else:
178211
# Use FastMCP proxy for stdio mode (enables middleware and usage tracking)
179212
exit_code = asyncio.run(
180-
run_server_with_fastmcp(server_config, server_name, http_mode=False, port=port, host=host)
213+
run_server_with_fastmcp(server_config, server_name, http_mode=False, sse_mode=False, port=port, host=host)
181214
)
182215

183216
sys.exit(exit_code)

0 commit comments

Comments
 (0)