Spaces:
Running
Running
Jeremiah Lowin commited on
Commit ·
0fa02ae
1
Parent(s): 8e52c19
Add http as an alias for streamable http
Browse files- README.md +1 -1
- src/fastmcp/cli/cli.py +1 -1
- src/fastmcp/cli/run.py +1 -3
- src/fastmcp/server/server.py +9 -8
- src/fastmcp/utilities/mcp_config.py +4 -3
- tests/deprecated/test_deprecated.py +1 -1
README.md
CHANGED
|
@@ -349,7 +349,7 @@ mcp.run(transport="stdio") # Default, so transport argument is optional
|
|
| 349 |
**Streamable HTTP**: Recommended for web deployments.
|
| 350 |
|
| 351 |
```python
|
| 352 |
-
mcp.run(transport="
|
| 353 |
```
|
| 354 |
|
| 355 |
**SSE**: For compatibility with existing SSE clients.
|
|
|
|
| 349 |
**Streamable HTTP**: Recommended for web deployments.
|
| 350 |
|
| 351 |
```python
|
| 352 |
+
mcp.run(transport="http", host="127.0.0.1", port=8000, path="/mcp")
|
| 353 |
```
|
| 354 |
|
| 355 |
**SSE**: For compatibility with existing SSE clients.
|
src/fastmcp/cli/cli.py
CHANGED
|
@@ -235,7 +235,7 @@ def run(
|
|
| 235 |
typer.Option(
|
| 236 |
"--transport",
|
| 237 |
"-t",
|
| 238 |
-
help="Transport protocol to use (stdio,
|
| 239 |
),
|
| 240 |
] = None,
|
| 241 |
host: Annotated[
|
|
|
|
| 235 |
typer.Option(
|
| 236 |
"--transport",
|
| 237 |
"-t",
|
| 238 |
+
help="Transport protocol to use (stdio, http, or sse)",
|
| 239 |
),
|
| 240 |
] = None,
|
| 241 |
host: Annotated[
|
src/fastmcp/cli/run.py
CHANGED
|
@@ -4,14 +4,12 @@ import importlib.util
|
|
| 4 |
import re
|
| 5 |
import sys
|
| 6 |
from pathlib import Path
|
| 7 |
-
from typing import Any
|
| 8 |
|
| 9 |
from fastmcp.utilities.logging import get_logger
|
| 10 |
|
| 11 |
logger = get_logger("cli.run")
|
| 12 |
|
| 13 |
-
TransportType = Literal["stdio", "streamable-http", "sse"]
|
| 14 |
-
|
| 15 |
|
| 16 |
def is_url(path: str) -> bool:
|
| 17 |
"""Check if a string is a URL."""
|
|
|
|
| 4 |
import re
|
| 5 |
import sys
|
| 6 |
from pathlib import Path
|
| 7 |
+
from typing import Any
|
| 8 |
|
| 9 |
from fastmcp.utilities.logging import get_logger
|
| 10 |
|
| 11 |
logger = get_logger("cli.run")
|
| 12 |
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def is_url(path: str) -> bool:
|
| 15 |
"""Check if a string is a URL."""
|
src/fastmcp/server/server.py
CHANGED
|
@@ -74,6 +74,7 @@ if TYPE_CHECKING:
|
|
| 74 |
logger = get_logger(__name__)
|
| 75 |
|
| 76 |
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
|
|
|
|
| 77 |
|
| 78 |
# Compiled URI parsing regex to split a URI into protocol and path components
|
| 79 |
URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
|
|
@@ -280,7 +281,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 280 |
|
| 281 |
async def run_async(
|
| 282 |
self,
|
| 283 |
-
transport:
|
| 284 |
**transport_kwargs: Any,
|
| 285 |
) -> None:
|
| 286 |
"""Run the FastMCP server asynchronously.
|
|
@@ -290,19 +291,19 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 290 |
"""
|
| 291 |
if transport is None:
|
| 292 |
transport = "stdio"
|
| 293 |
-
if transport not in {"stdio", "
|
| 294 |
raise ValueError(f"Unknown transport: {transport}")
|
| 295 |
|
| 296 |
if transport == "stdio":
|
| 297 |
await self.run_stdio_async(**transport_kwargs)
|
| 298 |
-
elif transport in {"
|
| 299 |
await self.run_http_async(transport=transport, **transport_kwargs)
|
| 300 |
else:
|
| 301 |
raise ValueError(f"Unknown transport: {transport}")
|
| 302 |
|
| 303 |
def run(
|
| 304 |
self,
|
| 305 |
-
transport:
|
| 306 |
**transport_kwargs: Any,
|
| 307 |
) -> None:
|
| 308 |
"""Run the FastMCP server. Note this is a synchronous function.
|
|
@@ -1253,7 +1254,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1253 |
|
| 1254 |
async def run_http_async(
|
| 1255 |
self,
|
| 1256 |
-
transport: Literal["streamable-http", "sse"] = "
|
| 1257 |
host: str | None = None,
|
| 1258 |
port: int | None = None,
|
| 1259 |
log_level: str | None = None,
|
|
@@ -1384,7 +1385,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1384 |
middleware: list[ASGIMiddleware] | None = None,
|
| 1385 |
json_response: bool | None = None,
|
| 1386 |
stateless_http: bool | None = None,
|
| 1387 |
-
transport: Literal["streamable-http", "sse"] = "
|
| 1388 |
) -> StarletteWithLifespan:
|
| 1389 |
"""Create a Starlette app using the specified HTTP transport.
|
| 1390 |
|
|
@@ -1397,7 +1398,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1397 |
A Starlette application configured with the specified transport
|
| 1398 |
"""
|
| 1399 |
|
| 1400 |
-
if transport
|
| 1401 |
return create_streamable_http_app(
|
| 1402 |
server=self,
|
| 1403 |
streamable_http_path=path
|
|
@@ -1444,7 +1445,7 @@ class FastMCP(Generic[LifespanResultT]):
|
|
| 1444 |
stacklevel=2,
|
| 1445 |
)
|
| 1446 |
await self.run_http_async(
|
| 1447 |
-
transport="
|
| 1448 |
host=host,
|
| 1449 |
port=port,
|
| 1450 |
log_level=log_level,
|
|
|
|
| 74 |
logger = get_logger(__name__)
|
| 75 |
|
| 76 |
DuplicateBehavior = Literal["warn", "error", "replace", "ignore"]
|
| 77 |
+
Transport = Literal["stdio", "http", "sse", "streamable-http"]
|
| 78 |
|
| 79 |
# Compiled URI parsing regex to split a URI into protocol and path components
|
| 80 |
URI_PATTERN = re.compile(r"^([^:]+://)(.*?)$")
|
|
|
|
| 281 |
|
| 282 |
async def run_async(
|
| 283 |
self,
|
| 284 |
+
transport: Transport | None = None,
|
| 285 |
**transport_kwargs: Any,
|
| 286 |
) -> None:
|
| 287 |
"""Run the FastMCP server asynchronously.
|
|
|
|
| 291 |
"""
|
| 292 |
if transport is None:
|
| 293 |
transport = "stdio"
|
| 294 |
+
if transport not in {"stdio", "http", "sse", "streamable-http"}:
|
| 295 |
raise ValueError(f"Unknown transport: {transport}")
|
| 296 |
|
| 297 |
if transport == "stdio":
|
| 298 |
await self.run_stdio_async(**transport_kwargs)
|
| 299 |
+
elif transport in {"http", "sse", "streamable-http"}:
|
| 300 |
await self.run_http_async(transport=transport, **transport_kwargs)
|
| 301 |
else:
|
| 302 |
raise ValueError(f"Unknown transport: {transport}")
|
| 303 |
|
| 304 |
def run(
|
| 305 |
self,
|
| 306 |
+
transport: Transport | None = None,
|
| 307 |
**transport_kwargs: Any,
|
| 308 |
) -> None:
|
| 309 |
"""Run the FastMCP server. Note this is a synchronous function.
|
|
|
|
| 1254 |
|
| 1255 |
async def run_http_async(
|
| 1256 |
self,
|
| 1257 |
+
transport: Literal["http", "streamable-http", "sse"] = "http",
|
| 1258 |
host: str | None = None,
|
| 1259 |
port: int | None = None,
|
| 1260 |
log_level: str | None = None,
|
|
|
|
| 1385 |
middleware: list[ASGIMiddleware] | None = None,
|
| 1386 |
json_response: bool | None = None,
|
| 1387 |
stateless_http: bool | None = None,
|
| 1388 |
+
transport: Literal["http", "streamable-http", "sse"] = "http",
|
| 1389 |
) -> StarletteWithLifespan:
|
| 1390 |
"""Create a Starlette app using the specified HTTP transport.
|
| 1391 |
|
|
|
|
| 1398 |
A Starlette application configured with the specified transport
|
| 1399 |
"""
|
| 1400 |
|
| 1401 |
+
if transport in ("streamable-http", "http"):
|
| 1402 |
return create_streamable_http_app(
|
| 1403 |
server=self,
|
| 1404 |
streamable_http_path=path
|
|
|
|
| 1445 |
stacklevel=2,
|
| 1446 |
)
|
| 1447 |
await self.run_http_async(
|
| 1448 |
+
transport="http",
|
| 1449 |
host=host,
|
| 1450 |
port=port,
|
| 1451 |
log_level=log_level,
|
src/fastmcp/utilities/mcp_config.py
CHANGED
|
@@ -19,7 +19,7 @@ if TYPE_CHECKING:
|
|
| 19 |
|
| 20 |
def infer_transport_type_from_url(
|
| 21 |
url: str | AnyUrl,
|
| 22 |
-
) -> Literal["
|
| 23 |
"""
|
| 24 |
Infer the appropriate transport type from the given URL.
|
| 25 |
"""
|
|
@@ -34,7 +34,7 @@ def infer_transport_type_from_url(
|
|
| 34 |
if re.search(r"/sse(/|\?|&|$)", path):
|
| 35 |
return "sse"
|
| 36 |
else:
|
| 37 |
-
return "
|
| 38 |
|
| 39 |
|
| 40 |
class StdioMCPServer(FastMCPBaseModel):
|
|
@@ -58,7 +58,7 @@ class StdioMCPServer(FastMCPBaseModel):
|
|
| 58 |
class RemoteMCPServer(FastMCPBaseModel):
|
| 59 |
url: str
|
| 60 |
headers: dict[str, str] = Field(default_factory=dict)
|
| 61 |
-
transport: Literal["streamable-http", "sse"] | None = None
|
| 62 |
auth: Annotated[
|
| 63 |
str | Literal["oauth"] | httpx.Auth | None,
|
| 64 |
Field(
|
|
@@ -79,6 +79,7 @@ class RemoteMCPServer(FastMCPBaseModel):
|
|
| 79 |
if transport == "sse":
|
| 80 |
return SSETransport(self.url, headers=self.headers, auth=self.auth)
|
| 81 |
else:
|
|
|
|
| 82 |
return StreamableHttpTransport(
|
| 83 |
self.url, headers=self.headers, auth=self.auth
|
| 84 |
)
|
|
|
|
| 19 |
|
| 20 |
def infer_transport_type_from_url(
|
| 21 |
url: str | AnyUrl,
|
| 22 |
+
) -> Literal["http", "sse"]:
|
| 23 |
"""
|
| 24 |
Infer the appropriate transport type from the given URL.
|
| 25 |
"""
|
|
|
|
| 34 |
if re.search(r"/sse(/|\?|&|$)", path):
|
| 35 |
return "sse"
|
| 36 |
else:
|
| 37 |
+
return "http"
|
| 38 |
|
| 39 |
|
| 40 |
class StdioMCPServer(FastMCPBaseModel):
|
|
|
|
| 58 |
class RemoteMCPServer(FastMCPBaseModel):
|
| 59 |
url: str
|
| 60 |
headers: dict[str, str] = Field(default_factory=dict)
|
| 61 |
+
transport: Literal["http", "streamable-http", "sse"] | None = None
|
| 62 |
auth: Annotated[
|
| 63 |
str | Literal["oauth"] | httpx.Auth | None,
|
| 64 |
Field(
|
|
|
|
| 79 |
if transport == "sse":
|
| 80 |
return SSETransport(self.url, headers=self.headers, auth=self.auth)
|
| 81 |
else:
|
| 82 |
+
# Both "http" and "streamable-http" map to StreamableHttpTransport
|
| 83 |
return StreamableHttpTransport(
|
| 84 |
self.url, headers=self.headers, auth=self.auth
|
| 85 |
)
|
tests/deprecated/test_deprecated.py
CHANGED
|
@@ -85,7 +85,7 @@ async def test_run_streamable_http_async_deprecation_warning():
|
|
| 85 |
# Verify the mock was called with the right transport
|
| 86 |
mock_run.assert_called_once()
|
| 87 |
call_kwargs = mock_run.call_args.kwargs
|
| 88 |
-
assert call_kwargs.get("transport") == "
|
| 89 |
|
| 90 |
|
| 91 |
def test_http_app_with_sse_transport():
|
|
|
|
| 85 |
# Verify the mock was called with the right transport
|
| 86 |
mock_run.assert_called_once()
|
| 87 |
call_kwargs = mock_run.call_args.kwargs
|
| 88 |
+
assert call_kwargs.get("transport") == "http"
|
| 89 |
|
| 90 |
|
| 91 |
def test_http_app_with_sse_transport():
|