chore: import upstream snapshot with attribution
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
Deploy Docs / deploy-docs (push) Failing after 1s
Conformance Tests / client-conformance (push) Failing after 3s
Conformance Tests / server-conformance (push) Failing after 1s
GitHub Actions Security Analysis / zizmor (push) Failing after 1s
CI / checks (push) Failing after 59m20s
CI / all-green (push) Waiting to run
This commit is contained in:
@@ -0,0 +1,87 @@
|
||||
"""Server-side hosting scaffold for story examples.
|
||||
|
||||
A story's ``server.py`` / ``server_lowlevel.py`` imports only from here. The
|
||||
marked lines touch entry-point APIs that a later release reshapes into
|
||||
free-function entries; isolating them here keeps story bodies stable.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from collections.abc import Callable
|
||||
from typing import Any, TypeAlias
|
||||
|
||||
import anyio
|
||||
import uvicorn
|
||||
from starlette.applications import Starlette
|
||||
|
||||
from mcp.server.lowlevel import Server
|
||||
from mcp.server.mcpserver import MCPServer
|
||||
from mcp.server.stdio import stdio_server
|
||||
from mcp.server.transport_security import TransportSecuritySettings
|
||||
|
||||
AnyServer: TypeAlias = "MCPServer | Server[Any]"
|
||||
ServerFactory = Callable[[], AnyServer]
|
||||
AppFactory = Callable[[], Starlette]
|
||||
|
||||
NO_DNS_REBIND = TransportSecuritySettings(enable_dns_rebinding_protection=False)
|
||||
"""Harness servers bind 127.0.0.1 and the in-process httpx client sends no Origin header."""
|
||||
|
||||
|
||||
def argv_after(flag: str, *, default: str | None = None) -> str:
|
||||
"""Return the argv token following ``flag``, or ``default`` when the flag is absent."""
|
||||
try:
|
||||
return sys.argv[sys.argv.index(flag) + 1]
|
||||
except ValueError:
|
||||
if default is None:
|
||||
raise SystemExit(f"missing required {flag}") from None
|
||||
return default
|
||||
|
||||
|
||||
def asgi_from(server: AnyServer, *, path: str = "/mcp") -> Starlette:
|
||||
"""Wrap a server instance in its streamable-HTTP ASGI app for in-process driving."""
|
||||
return server.streamable_http_app( # becomes free fn streamable_http(server, legacy=...)
|
||||
streamable_http_path=path,
|
||||
stateless_http=False, # bool folds into a legacy= enum in a later release
|
||||
transport_security=NO_DNS_REBIND,
|
||||
)
|
||||
|
||||
|
||||
def run_server_from_args(build_server: ServerFactory) -> None:
|
||||
"""Entry point for ``if __name__ == "__main__"`` in every ``server*.py``.
|
||||
|
||||
Bare argv serves over stdio; ``--http --port N [--path /mcp]`` serves over
|
||||
uvicorn on 127.0.0.1:N.
|
||||
"""
|
||||
server = build_server()
|
||||
if "--http" in sys.argv:
|
||||
port = int(argv_after("--port", default="8000"))
|
||||
path = argv_after("--path", default="/mcp")
|
||||
anyio.run(_serve_http, server, port, path)
|
||||
else:
|
||||
anyio.run(_serve_stdio, server)
|
||||
|
||||
|
||||
async def _serve_stdio(server: AnyServer) -> None:
|
||||
if isinstance(server, MCPServer):
|
||||
await server.run_stdio_async() # becomes await serve_stdio(server)
|
||||
else:
|
||||
async with stdio_server() as (read, write): # becomes await serve_stdio(server)
|
||||
await server.run(read, write, server.create_initialization_options())
|
||||
|
||||
|
||||
async def _serve_http(server: AnyServer, port: int, path: str) -> None:
|
||||
app = asgi_from(server, path=path)
|
||||
config = uvicorn.Config(app, host="127.0.0.1", port=port, log_level="error")
|
||||
await uvicorn.Server(config).serve()
|
||||
|
||||
|
||||
def run_app_from_args(build_app: AppFactory) -> None:
|
||||
"""Entry point for ``if __name__ == "__main__"`` in app-exporting ``server*.py``.
|
||||
|
||||
App-exporting stories are HTTP-only; ``--port N`` serves the Starlette app over
|
||||
uvicorn on 127.0.0.1:N (uvicorn drives the app's own lifespan). No stdio leg.
|
||||
"""
|
||||
port = int(argv_after("--port", default="8000"))
|
||||
config = uvicorn.Config(build_app(), host="127.0.0.1", port=port, log_level="error")
|
||||
anyio.run(uvicorn.Server(config).serve)
|
||||
Reference in New Issue
Block a user