Files
wehub-resource-sync 49b9bb6724
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
chore: import upstream snapshot with attribution
2026-07-13 12:10:27 +08:00

66 lines
2.7 KiB
Python

"""Exported era classifier: the body-primary predicate, the built-in dual-era app, and CORS — exports `build_app()`."""
from collections.abc import Mapping
from typing import Any, Literal
from mcp_types import INVALID_PARAMS
from mcp_types.version import MODERN_PROTOCOL_VERSIONS
from starlette.applications import Starlette
from starlette.middleware.cors import CORSMiddleware
from mcp.server.mcpserver import Context, MCPServer
from mcp.shared.inbound import InboundLadderRejection, InboundModernRoute, classify_inbound_request
from stories._hosting import NO_DNS_REBIND, run_app_from_args
#: Response headers a browser-based MCP client must be able to read.
MCP_EXPOSED_HEADERS = ["Mcp-Session-Id", "WWW-Authenticate", "Last-Event-Id", "Mcp-Protocol-Version"]
#: Request headers a browser-based MCP client must be allowed to send.
MCP_ALLOWED_HEADERS = ["Authorization", "Content-Type", "Mcp-Protocol-Version", "Mcp-Session-Id", "Last-Event-Id"]
#: Streamable HTTP verbs: POST requests, the standalone GET stream, DELETE session end.
MCP_ALLOWED_METHODS = ["GET", "POST", "DELETE"]
def classify_era(
body: Mapping[str, Any], headers: Mapping[str, str]
) -> Literal["modern", "legacy"] | InboundLadderRejection:
"""Tri-state era classifier built on the exported `classify_inbound_request` predicate.
Compose this in your own ASGI/ingress layer when the two eras need different
backends. Only a rung-1 ``INVALID_PARAMS`` rejection (no envelope keys) means
"treat as legacy"; other rejections are malformed-modern and should be refused.
"""
verdict = classify_inbound_request(body, headers=headers)
if isinstance(verdict, InboundModernRoute):
return "modern"
if verdict.code == INVALID_PARAMS:
return "legacy"
return verdict
def build_app() -> Starlette:
mcp = MCPServer("legacy-routing-example")
@mcp.tool()
async def which_arm(ctx: Context) -> str:
"""Report which era the built-in router dispatched this request to."""
pv = ctx.request_context.protocol_version
return "modern" if pv in MODERN_PROTOCOL_VERSIONS else "legacy"
# One Starlette app, one /mcp route, both eras: sessionful 2025 (initialize +
# Mcp-Session-Id + GET stream) and stateless 2026 (per-request _meta envelope).
app = mcp.streamable_http_app(transport_security=NO_DNS_REBIND)
# CORS for browser-based clients. DEMO ONLY — restrict allow_origins in production.
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=MCP_ALLOWED_METHODS,
allow_headers=MCP_ALLOWED_HEADERS,
expose_headers=MCP_EXPOSED_HEADERS,
)
return app
if __name__ == "__main__":
run_app_from_args(build_app)