Skip to content

Commit e5a0536

Browse files
v0.3.0: Add HTTP+SSE transport for web-based MCP clients
- New: click_to_mcp/http_server.py - Starlette+uvicorn+SSE HTTP transport - New: serve-http CLI command with --host and --port options - New: demo-http command for quick HTTP demo - New: /health endpoint for monitoring and load balancers - New: run_http() library API for programmatic HTTP serving - New: [http] optional dependency group - New: 11 HTTP integration tests (23 total, all passing) - Updated: README with HTTP transport docs and endpoint table - Bump: version 0.2.1 to 0.3.0
1 parent 7b69759 commit e5a0536

6 files changed

Lines changed: 624 additions & 17 deletions

File tree

README.md

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,28 @@ Install directly from GitHub (PyPI coming soon):
1919
pip install git+https://github.com/Coding-Dev-Tools/click-to-mcp.git
2020
```
2121

22+
For HTTP+SSE transport (web-based MCP clients), install with the `http` extra:
23+
24+
```bash
25+
pip install "click-to-mcp[http] @ git+https://github.com/Coding-Dev-Tools/click-to-mcp.git"
26+
```
27+
2228
```bash
2329
# Discover all Click/typer CLIs installed in your environment
2430
click-to-mcp discover
2531

26-
# Serve a specific CLI as an MCP server
32+
# Serve a specific CLI as an MCP server (stdio transport)
2733
click-to-mcp serve api-contract-guardian
2834

35+
# Serve over HTTP+SSE (for web-based MCP clients)
36+
click-to-mcp serve-http api-contract-guardian --port 8000
37+
2938
# Or serve the built-in demo
30-
click-to-mcp demo
39+
click-to-mcp demo # stdio
40+
click-to-mcp demo-http # HTTP+SSE on port 8000
3141
```
3242

33-
Then configure your MCP client to run `click-to-mcp serve <name>` as a stdio transport.
43+
Then configure your MCP client to connect via stdio or HTTP.
3444

3545
## How It Works
3646

@@ -189,8 +199,12 @@ click-to-mcp discover
189199
# Serve a specific CLI as an MCP server over stdio
190200
click-to-mcp serve <name>
191201

202+
# Serve over HTTP+SSE (requires pip install "click-to-mcp[http]")
203+
click-to-mcp serve-http <name> --port 8000
204+
192205
# Serve the built-in demo
193-
click-to-mcp demo
206+
click-to-mcp demo # stdio
207+
click-to-mcp demo-http # HTTP+SSE
194208

195209
# Version info
196210
click-to-mcp --version
@@ -233,10 +247,52 @@ run(app, prefix="my-cli")
233247

234248
- **Auto-discovery**: `click-to-mcp discover` scans `console_scripts` entry points for Click/typer CLIs
235249
- **Serve any CLI**: `click-to-mcp serve <name>` wraps any discovered CLI as an MCP server
250+
- **HTTP+SSE transport**: `click-to-mcp serve-http <name>` serves over HTTP for web-based clients (v0.3.0+)
236251
- **Supports both Click and Typer**: Full compatibility with both frameworks
237252
- **Nested command groups**: Handles subcommand groups recursively with prefixed tool names
238253
- **Parameter introspection**: Correctly maps Click options, arguments, types, enums, defaults, and help text to JSON Schema
239-
- **Full MCP protocol**: Implements `initialize`, `tools/list`, `tools/call` over stdio
254+
- **Full MCP protocol**: Implements `initialize`, `tools/list`, `tools/call` over stdio and HTTP+SSE
255+
- **Health endpoint**: HTTP servers expose `/health` for monitoring and load balancers
256+
257+
## Transports
258+
259+
### Stdio (default)
260+
261+
Best for local CLI-based MCP clients (Claude Code, Cursor, Cline). No extra dependencies needed.
262+
263+
```bash
264+
click-to-mcp serve <name>
265+
```
266+
267+
### HTTP+SSE (v0.3.0+)
268+
269+
Best for web-based MCP clients, remote access, and multi-user setups. Requires the `[http]` extra.
270+
271+
```bash
272+
# Install HTTP dependencies
273+
pip install "click-to-mcp[http]"
274+
275+
# Start an HTTP+SSE server
276+
click-to-mcp serve-http <name> --host 127.0.0.1 --port 8000
277+
```
278+
279+
Endpoints:
280+
| Endpoint | Method | Description |
281+
|---|---|---|
282+
| `/sse` | GET | SSE stream (server-to-client events) |
283+
| `/messages` | POST | JSON-RPC message endpoint |
284+
| `/health` | GET | Health check (JSON status) |
285+
286+
Configure your MCP client with the SSE URL:
287+
```json
288+
{
289+
"mcpServers": {
290+
"my-cli": {
291+
"url": "http://127.0.0.1:8000/sse"
292+
}
293+
}
294+
}
295+
```
240296

241297
## MCP Protocol
242298

@@ -271,9 +327,10 @@ Then agents can use it as: `your-cli mcp`
271327
```bash
272328
git clone https://github.com/Coding-Dev-Tools/click-to-mcp
273329
cd click-to-mcp
274-
pip install -e ".[dev]"
275-
python -m pytest tests/ -v # 12 tests
276-
click-to-mcp demo # starts MCP server for demo CLI
330+
pip install -e ".[dev,http]"
331+
python -m pytest tests/ -v # 23 tests (12 stdio + 11 HTTP)
332+
click-to-mcp demo # starts MCP stdio server for demo CLI
333+
click-to-mcp demo-http # starts MCP HTTP+SSE server on port 8000
277334
```
278335

279336
## Pricing

click_to_mcp/__init__.py

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
from .server import serve_stdio
2323
from .discover import scan_entry_points, load_cli, find_our_clis, DiscoveredCLI
2424

25-
__version__ = "0.2.1"
25+
__version__ = "0.3.0"
2626

2727

2828
def run(app: Any, prefix: str = "", name: str = "") -> None:
@@ -58,7 +58,44 @@ def run(app: Any, prefix: str = "", name: str = "") -> None:
5858
)
5959

6060

61+
def run_http(app: Any, prefix: str = "", name: str = "",
62+
host: str = "127.0.0.1", port: int = 8000) -> None:
63+
"""High-level entry point: serve a Click/typer app as an MCP server over HTTP+SSE.
64+
65+
Requires optional dependencies: pip install 'click-to-mcp[http]'
66+
67+
Args:
68+
app: A click.Group or typer.Typer instance.
69+
prefix: Optional tool name prefix.
70+
name: Optional server name (defaults to app name or 'cli').
71+
host: Host to bind (default: 127.0.0.1).
72+
port: Port to bind (default: 8000).
73+
"""
74+
from .http_server import serve_http
75+
76+
if not name:
77+
name = getattr(app, "name", None) or "cli"
78+
desc = ""
79+
if hasattr(app, "info"):
80+
info_desc = getattr(app.info, "help", None)
81+
if info_desc and "DefaultPlaceholder" not in str(type(info_desc)):
82+
desc = str(info_desc)
83+
if not desc:
84+
desc = getattr(app, "help", None) or ""
85+
if not isinstance(desc, str):
86+
desc = str(desc) if desc else ""
87+
88+
serve_http(
89+
app,
90+
name=name,
91+
description=desc,
92+
prefix=prefix,
93+
host=host,
94+
port=port,
95+
)
96+
97+
6198
__all__ = [
62-
"cli_to_mcp_tools", "CliToolDef", "serve_stdio", "run",
99+
"cli_to_mcp_tools", "CliToolDef", "serve_stdio", "run", "run_http",
63100
"scan_entry_points", "load_cli", "find_our_clis", "DiscoveredCLI",
64101
]

click_to_mcp/cli.py

Lines changed: 69 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
33
Usage:
44
click-to-mcp discover # List all discoverable CLIs
5-
click-to-mcp serve <name> # Serve a specific CLI
6-
click-to-mcp serve --all # Serve all discoverable CLIs
7-
click-to-mcp demo # Run the demo CLI as MCP server
5+
click-to-mcp serve <name> # Serve a specific CLI (stdio)
6+
click-to-mcp serve --all # Serve all discoverable CLIs (stdio)
7+
click-to-mcp serve-http <name> # Serve a CLI over HTTP+SSE
8+
click-to-mcp demo # Run the demo CLI as MCP server (stdio)
9+
click-to-mcp demo-http # Run the demo CLI as HTTP+SSE server
810
"""
911

1012
from __future__ import annotations
@@ -87,14 +89,77 @@ def serve(name: Optional[str], serve_all: bool):
8789
serve_stdio(target_cli, name=name)
8890

8991

92+
@cli.command()
93+
@click.argument("name", required=False, default=None)
94+
@click.option("--host", default="127.0.0.1", help="Host to bind (default: 127.0.0.1)")
95+
@click.option("--port", default=8000, type=int, help="Port to bind (default: 8000)")
96+
@click.option("--all", "serve_all", is_flag=True, help="Serve all discoverable CLIs")
97+
def serve_http(name: Optional[str], host: str, port: int, serve_all: bool):
98+
"""Serve a CLI as an MCP server over HTTP+SSE.
99+
100+
Specify a CLI NAME from 'click-to-mcp discover', or use --all.
101+
Requires optional HTTP dependencies: pip install 'click-to-mcp[http]'
102+
"""
103+
if not name and not serve_all:
104+
click.echo("Error: Specify a CLI name or --all", err=True)
105+
sys.exit(1)
106+
107+
# Try to import HTTP deps early for a clear error message
108+
try:
109+
from .http_server import serve_http as _serve_http
110+
except ImportError as e:
111+
click.echo(f"Error: {e}", err=True)
112+
sys.exit(1)
113+
114+
if serve_all:
115+
clis = scan_entry_points()
116+
if not clis:
117+
click.echo("No CLIs found to serve.", err=True)
118+
sys.exit(1)
119+
click.echo(f"Multiple CLIs found. Serving the first one: {clis[0].name}", err=True)
120+
target_cli = load_cli(clis[0].name)
121+
if target_cli is None:
122+
click.echo(f"Error: Could not load CLI '{clis[0].name}'", err=True)
123+
sys.exit(1)
124+
_serve_http(target_cli, name=clis[0].name, host=host, port=port)
125+
return
126+
127+
target_cli = load_cli(name)
128+
if target_cli is None:
129+
click.echo(f"Error: CLI '{name}' not found.", err=True)
130+
click.echo("Run 'click-to-mcp discover' to see available CLIs.", err=True)
131+
sys.exit(1)
132+
133+
_serve_http(target_cli, name=name, host=host, port=port)
134+
135+
90136
@cli.command()
91137
def demo():
92-
"""Run the built-in demo CLI as an MCP server."""
138+
"""Run the built-in demo CLI as an MCP server (stdio)."""
93139
from .demo import cli as demo_cli
94140

95141
serve_stdio(demo_cli, name="click-to-mcp-demo")
96142

97143

144+
@cli.command("demo-http")
145+
@click.option("--host", default="127.0.0.1", help="Host to bind (default: 127.0.0.1)")
146+
@click.option("--port", default=8000, type=int, help="Port to bind (default: 8000)")
147+
def demo_http(host: str, port: int):
148+
"""Run the built-in demo CLI as an HTTP+SSE MCP server.
149+
150+
Requires optional HTTP dependencies: pip install 'click-to-mcp[http]'
151+
"""
152+
try:
153+
from .http_server import serve_http as _serve_http
154+
except ImportError as e:
155+
click.echo(f"Error: {e}", err=True)
156+
sys.exit(1)
157+
158+
from .demo import cli as demo_cli
159+
160+
_serve_http(demo_cli, name="click-to-mcp-demo", host=host, port=port)
161+
162+
98163
def main():
99164
"""Entry point for the click-to-mcp CLI."""
100165
cli()

0 commit comments

Comments
 (0)