refactor: move transport-specific parameters from FastMCP constructor to run() (#1898)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
5d80f4efc8
commit
8adb5bdce8
12 changed files with 339 additions and 231 deletions
|
|
@ -83,8 +83,6 @@ event_store = InMemoryEventStore()
|
|||
|
||||
mcp = FastMCP(
|
||||
name="mcp-conformance-test-server",
|
||||
event_store=event_store,
|
||||
retry_interval=100, # 100ms retry interval for SSE polling
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -448,8 +446,12 @@ def main(port: int, log_level: str) -> int:
|
|||
logger.info(f"Starting MCP Everything Server on port {port}")
|
||||
logger.info(f"Endpoint will be: http://localhost:{port}/mcp")
|
||||
|
||||
mcp.settings.port = port
|
||||
mcp.run(transport="streamable-http")
|
||||
mcp.run(
|
||||
transport="streamable-http",
|
||||
port=port,
|
||||
event_store=event_store,
|
||||
retry_interval=100, # 100ms retry interval for SSE polling
|
||||
)
|
||||
|
||||
return 0
|
||||
|
||||
|
|
|
|||
|
|
@ -66,11 +66,11 @@ def create_simple_mcp_server(server_settings: ServerSettings, auth_settings: Sim
|
|||
name="Simple Auth MCP Server",
|
||||
instructions="A simple MCP server with simple credential authentication",
|
||||
auth_server_provider=oauth_provider,
|
||||
host=server_settings.host,
|
||||
port=server_settings.port,
|
||||
debug=True,
|
||||
auth=mcp_auth_settings,
|
||||
)
|
||||
# Store server settings for later use in run()
|
||||
app._server_settings = server_settings # type: ignore[attr-defined]
|
||||
|
||||
@app.custom_route("/login", methods=["GET"])
|
||||
async def login_page_handler(request: Request) -> Response:
|
||||
|
|
@ -131,7 +131,7 @@ def main(port: int, transport: Literal["sse", "streamable-http"]) -> int:
|
|||
|
||||
mcp_server = create_simple_mcp_server(server_settings, auth_settings)
|
||||
logger.info(f"🚀 MCP Legacy Server running on {server_url}")
|
||||
mcp_server.run(transport=transport)
|
||||
mcp_server.run(transport=transport, host=host, port=port)
|
||||
return 0
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -66,8 +66,6 @@ def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
|
|||
app = FastMCP(
|
||||
name="MCP Resource Server",
|
||||
instructions="Resource Server that validates tokens via Authorization Server introspection",
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
debug=True,
|
||||
# Auth configuration for RS mode
|
||||
token_verifier=token_verifier,
|
||||
|
|
@ -77,6 +75,8 @@ def create_resource_server(settings: ResourceServerSettings) -> FastMCP:
|
|||
resource_server_url=settings.server_url,
|
||||
),
|
||||
)
|
||||
# Store settings for later use in run()
|
||||
app._resource_server_settings = settings # type: ignore[attr-defined]
|
||||
|
||||
@app.tool()
|
||||
async def get_time() -> dict[str, Any]:
|
||||
|
|
@ -153,7 +153,7 @@ def main(port: int, auth_server: str, transport: Literal["sse", "streamable-http
|
|||
logger.info(f"🔑 Using Authorization Server: {settings.auth_server_url}")
|
||||
|
||||
# Run the server - this should block and keep running
|
||||
mcp_server.run(transport=transport)
|
||||
mcp_server.run(transport=transport, host=host, port=port)
|
||||
logger.info("Server stopped")
|
||||
return 0
|
||||
except Exception:
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from typing import Any
|
|||
import anyio
|
||||
import click
|
||||
import mcp.types as types
|
||||
import uvicorn
|
||||
from mcp.server.lowlevel import Server
|
||||
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
|
||||
from starlette.applications import Starlette
|
||||
|
|
@ -33,7 +34,7 @@ def main(
|
|||
port: int,
|
||||
log_level: str,
|
||||
json_response: bool,
|
||||
) -> int:
|
||||
) -> None:
|
||||
# Configure logging
|
||||
logging.basicConfig(
|
||||
level=getattr(logging, log_level.upper()),
|
||||
|
|
@ -118,9 +119,7 @@ def main(
|
|||
# Create an ASGI application using the transport
|
||||
starlette_app = Starlette(
|
||||
debug=True,
|
||||
routes=[
|
||||
Mount("/mcp", app=handle_streamable_http),
|
||||
],
|
||||
routes=[Mount("/mcp", app=handle_streamable_http)],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
|
|
@ -133,8 +132,4 @@ def main(
|
|||
expose_headers=["Mcp-Session-Id"],
|
||||
)
|
||||
|
||||
import uvicorn
|
||||
|
||||
uvicorn.run(starlette_app, host="127.0.0.1", port=port)
|
||||
|
||||
return 0
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ Run from the repository root:
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create an MCP server
|
||||
mcp = FastMCP("Demo", json_response=True)
|
||||
mcp = FastMCP("Demo")
|
||||
|
||||
|
||||
# Add an addition tool
|
||||
|
|
@ -40,4 +40,4 @@ def greet_user(name: str, style: str = "friendly") -> str:
|
|||
|
||||
# Run with streamable HTTP transport
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
mcp.run(transport="streamable-http", json_response=True)
|
||||
|
|
|
|||
|
|
@ -20,7 +20,6 @@ class SimpleTokenVerifier(TokenVerifier):
|
|||
# Create FastMCP instance as a Resource Server
|
||||
mcp = FastMCP(
|
||||
"Weather Service",
|
||||
json_response=True,
|
||||
# Token verifier for authentication
|
||||
token_verifier=SimpleTokenVerifier(),
|
||||
# Auth settings for RFC 9728 Protected Resource Metadata
|
||||
|
|
@ -44,4 +43,4 @@ async def get_weather(city: str = "London") -> dict[str, str]:
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
mcp.run(transport="streamable-http", json_response=True)
|
||||
|
|
|
|||
|
|
@ -5,15 +5,7 @@ Run from the repository root:
|
|||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Stateless server with JSON responses (recommended)
|
||||
mcp = FastMCP("StatelessServer", stateless_http=True, json_response=True)
|
||||
|
||||
# Other configuration options:
|
||||
# Stateless server with SSE streaming responses
|
||||
# mcp = FastMCP("StatelessServer", stateless_http=True)
|
||||
|
||||
# Stateful server with session persistence
|
||||
# mcp = FastMCP("StatefulServer")
|
||||
mcp = FastMCP("StatelessServer")
|
||||
|
||||
|
||||
# Add a simple tool to demonstrate the server
|
||||
|
|
@ -24,5 +16,14 @@ def greet(name: str = "World") -> str:
|
|||
|
||||
|
||||
# Run server with streamable_http transport
|
||||
# Transport-specific options (stateless_http, json_response) are passed to run()
|
||||
if __name__ == "__main__":
|
||||
mcp.run(transport="streamable-http")
|
||||
# Stateless server with JSON responses (recommended)
|
||||
mcp.run(transport="streamable-http", stateless_http=True, json_response=True)
|
||||
|
||||
# Other configuration options:
|
||||
# Stateless server with SSE streaming responses
|
||||
# mcp.run(transport="streamable-http", stateless_http=True)
|
||||
|
||||
# Stateful server with session persistence
|
||||
# mcp.run(transport="streamable-http")
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from starlette.routing import Mount
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create MCP server
|
||||
mcp = FastMCP("My App", json_response=True)
|
||||
mcp = FastMCP("My App")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
|
@ -30,9 +30,10 @@ async def lifespan(app: Starlette):
|
|||
|
||||
|
||||
# Mount the StreamableHTTP server to the existing ASGI server
|
||||
# Transport-specific options are passed to streamable_http_app()
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Mount("/", app=mcp.streamable_http_app()),
|
||||
Mount("/", app=mcp.streamable_http_app(json_response=True)),
|
||||
],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ from starlette.routing import Host
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create MCP server
|
||||
mcp = FastMCP("MCP Host App", json_response=True)
|
||||
mcp = FastMCP("MCP Host App")
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
|
|
@ -30,9 +30,10 @@ async def lifespan(app: Starlette):
|
|||
|
||||
|
||||
# Mount using Host-based routing
|
||||
# Transport-specific options are passed to streamable_http_app()
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Host("mcp.acme.corp", app=mcp.streamable_http_app()),
|
||||
Host("mcp.acme.corp", app=mcp.streamable_http_app(json_response=True)),
|
||||
],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ from starlette.routing import Mount
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create multiple MCP servers
|
||||
api_mcp = FastMCP("API Server", json_response=True)
|
||||
chat_mcp = FastMCP("Chat Server", json_response=True)
|
||||
api_mcp = FastMCP("API Server")
|
||||
chat_mcp = FastMCP("Chat Server")
|
||||
|
||||
|
||||
@api_mcp.tool()
|
||||
|
|
@ -29,12 +29,6 @@ def send_message(message: str) -> str:
|
|||
return f"Message sent: {message}"
|
||||
|
||||
|
||||
# Configure servers to mount at the root of each path
|
||||
# This means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp
|
||||
api_mcp.settings.streamable_http_path = "/"
|
||||
chat_mcp.settings.streamable_http_path = "/"
|
||||
|
||||
|
||||
# Create a combined lifespan to manage both session managers
|
||||
@contextlib.asynccontextmanager
|
||||
async def lifespan(app: Starlette):
|
||||
|
|
@ -44,11 +38,12 @@ async def lifespan(app: Starlette):
|
|||
yield
|
||||
|
||||
|
||||
# Mount the servers
|
||||
# Mount the servers with transport-specific options passed to streamable_http_app()
|
||||
# streamable_http_path="/" means endpoints will be at /api and /chat instead of /api/mcp and /chat/mcp
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Mount("/api", app=api_mcp.streamable_http_app()),
|
||||
Mount("/chat", app=chat_mcp.streamable_http_app()),
|
||||
Mount("/api", app=api_mcp.streamable_http_app(json_response=True, streamable_http_path="/")),
|
||||
Mount("/chat", app=chat_mcp.streamable_http_app(json_response=True, streamable_http_path="/")),
|
||||
],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
"""
|
||||
Example showing path configuration during FastMCP initialization.
|
||||
Example showing path configuration when mounting FastMCP.
|
||||
|
||||
Run from the repository root:
|
||||
uvicorn examples.snippets.servers.streamable_http_path_config:app --reload
|
||||
|
|
@ -10,13 +10,8 @@ from starlette.routing import Mount
|
|||
|
||||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Configure streamable_http_path during initialization
|
||||
# This server will mount at the root of wherever it's mounted
|
||||
mcp_at_root = FastMCP(
|
||||
"My Server",
|
||||
json_response=True,
|
||||
streamable_http_path="/",
|
||||
)
|
||||
# Create a simple FastMCP server
|
||||
mcp_at_root = FastMCP("My Server")
|
||||
|
||||
|
||||
@mcp_at_root.tool()
|
||||
|
|
@ -25,9 +20,13 @@ def process_data(data: str) -> str:
|
|||
return f"Processed: {data}"
|
||||
|
||||
|
||||
# Mount at /process - endpoints will be at /process instead of /process/mcp
|
||||
# Mount at /process with streamable_http_path="/" so the endpoint is /process (not /process/mcp)
|
||||
# Transport-specific options like json_response are passed to streamable_http_app()
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Mount("/process", app=mcp_at_root.streamable_http_app()),
|
||||
Mount(
|
||||
"/process",
|
||||
app=mcp_at_root.streamable_http_app(json_response=True, streamable_http_path="/"),
|
||||
),
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from starlette.routing import Mount
|
|||
from mcp.server.fastmcp import FastMCP
|
||||
|
||||
# Create the Echo server
|
||||
echo_mcp = FastMCP(name="EchoServer", stateless_http=True, json_response=True)
|
||||
echo_mcp = FastMCP(name="EchoServer")
|
||||
|
||||
|
||||
@echo_mcp.tool()
|
||||
|
|
@ -21,7 +21,7 @@ def echo(message: str) -> str:
|
|||
|
||||
|
||||
# Create the Math server
|
||||
math_mcp = FastMCP(name="MathServer", stateless_http=True, json_response=True)
|
||||
math_mcp = FastMCP(name="MathServer")
|
||||
|
||||
|
||||
@math_mcp.tool()
|
||||
|
|
@ -42,13 +42,13 @@ async def lifespan(app: Starlette):
|
|||
# Create the Starlette app and mount the MCP servers
|
||||
app = Starlette(
|
||||
routes=[
|
||||
Mount("/echo", echo_mcp.streamable_http_app()),
|
||||
Mount("/math", math_mcp.streamable_http_app()),
|
||||
Mount("/echo", echo_mcp.streamable_http_app(stateless_http=True, json_response=True)),
|
||||
Mount("/math", math_mcp.streamable_http_app(stateless_http=True, json_response=True)),
|
||||
],
|
||||
lifespan=lifespan,
|
||||
)
|
||||
|
||||
# Note: Clients connect to http://localhost:8000/echo/mcp and http://localhost:8000/math/mcp
|
||||
# To mount at the root of each path (e.g., /echo instead of /echo/mcp):
|
||||
# echo_mcp.settings.streamable_http_path = "/"
|
||||
# math_mcp.settings.streamable_http_path = "/"
|
||||
# echo_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
|
||||
# math_mcp.streamable_http_app(streamable_http_path="/", stateless_http=True, json_response=True)
|
||||
|
|
|
|||
Loading…
Reference in a new issue