Initial curated ITWorx_MCP_Hub source from 368fba691ea8d23403b4d56b53b5ec670d7c5020
This commit is contained in:
commit
cb5be898ea
663 files changed
+82674
No files matched your search
@@ -0,0 +1,70 @@
|
||||
"""FastAPI route assembly for the stable MCP dataplane."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Request, Response, status
|
||||
from fastapi.responses import JSONResponse, StreamingResponse
|
||||
|
||||
from .proxy import DataplaneProxy
|
||||
|
||||
MCP_TRANSPORT_STATE_HEADERS = frozenset({b"mcp-session-id"})
|
||||
|
||||
|
||||
def _without_transport_state(request: Request) -> Request:
|
||||
"""Return a request view that cannot carry MCP transport session state.
|
||||
|
||||
The public Hub is deliberately stateless for every supported MCP revision.
|
||||
MCP 2025-11-25 permits servers to omit ``Mcp-Session-Id`` entirely; the
|
||||
client only has to echo an ID when the server chooses to issue one. Keeping
|
||||
this boundary sessionless avoids coupling ChatGPT requests to ContextForge
|
||||
process-local or pooled transport state.
|
||||
"""
|
||||
|
||||
scope = dict(request.scope)
|
||||
scope["headers"] = [
|
||||
(key, value)
|
||||
for key, value in request.scope.get("headers", [])
|
||||
if key.lower() not in MCP_TRANSPORT_STATE_HEADERS
|
||||
]
|
||||
return Request(scope, receive=request.receive)
|
||||
|
||||
|
||||
def _strip_response_transport_state(response: StreamingResponse | JSONResponse) -> None:
|
||||
"""Never expose an upstream MCP transport session identifier publicly."""
|
||||
|
||||
if "mcp-session-id" in response.headers:
|
||||
del response.headers["mcp-session-id"]
|
||||
|
||||
|
||||
def build_router(proxy: DataplaneProxy) -> APIRouter:
|
||||
router = APIRouter()
|
||||
|
||||
@router.api_route(
|
||||
"/mcp/{project_slug}/{environment}",
|
||||
methods=["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS", "HEAD"],
|
||||
response_model=None,
|
||||
)
|
||||
async def mcp_proxy(
|
||||
request: Request, project_slug: str, environment: str
|
||||
) -> StreamingResponse | JSONResponse:
|
||||
upstream_request = _without_transport_state(request)
|
||||
response = await proxy.handle(upstream_request, project_slug, environment)
|
||||
_strip_response_transport_state(response)
|
||||
return response
|
||||
|
||||
@router.get("/healthz")
|
||||
@router.get("/health")
|
||||
async def health() -> dict[str, Any]:
|
||||
return {"status": "healthy", "component": "dataplane-router"}
|
||||
|
||||
@router.get("/ready")
|
||||
@router.get("/health/ready")
|
||||
async def ready(response: Response) -> dict[str, Any]:
|
||||
result = proxy.registry.health()
|
||||
if result["status"] != "ready":
|
||||
response.status_code = status.HTTP_503_SERVICE_UNAVAILABLE
|
||||
return result | {"component": "dataplane-router"}
|
||||
|
||||
return router
|
||||
Reference in new issue
Block a user