91 lines
2.8 KiB
Python
91 lines
2.8 KiB
Python
from __future__ import annotations
|
|
|
|
from typing import Any, cast
|
|
|
|
from fastapi import FastAPI, Request
|
|
from fastapi.responses import JSONResponse
|
|
from fastapi.testclient import TestClient
|
|
|
|
from services.dataplane_router import build_router
|
|
|
|
|
|
class SessionBoundaryProbe:
|
|
def __init__(self) -> None:
|
|
self.seen_headers: dict[str, str] = {}
|
|
|
|
async def handle(
|
|
self, request: Request, project_slug: str, environment: str
|
|
) -> JSONResponse:
|
|
self.seen_headers = dict(request.headers)
|
|
return JSONResponse(
|
|
{
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"result": {"project": project_slug, "environment": environment},
|
|
},
|
|
headers={
|
|
"Mcp-Session-Id": "contextforge-session",
|
|
"Mcp-Protocol-Version": request.headers.get(
|
|
"mcp-protocol-version", "missing"
|
|
),
|
|
},
|
|
)
|
|
|
|
|
|
def _client(probe: SessionBoundaryProbe) -> TestClient:
|
|
app = FastAPI()
|
|
app.include_router(build_router(cast(Any, probe)))
|
|
return TestClient(app)
|
|
|
|
|
|
def _assert_stateless_boundary(protocol_version: str) -> None:
|
|
probe = SessionBoundaryProbe()
|
|
|
|
with _client(probe) as client:
|
|
response = client.post(
|
|
"/mcp/gitea-all/prod",
|
|
headers={
|
|
"Mcp-Session-Id": "stale-client-session",
|
|
"Mcp-Protocol-Version": protocol_version,
|
|
},
|
|
json={"jsonrpc": "2.0", "id": 1, "method": "tools/list"},
|
|
)
|
|
|
|
assert response.status_code == 200
|
|
assert "mcp-session-id" not in probe.seen_headers
|
|
assert probe.seen_headers["mcp-protocol-version"] == protocol_version
|
|
assert "mcp-session-id" not in response.headers
|
|
assert response.headers["mcp-protocol-version"] == protocol_version
|
|
|
|
|
|
def test_modern_public_route_does_not_bridge_mcp_session_state() -> None:
|
|
_assert_stateless_boundary("2026-07-28")
|
|
|
|
|
|
def test_legacy_public_route_is_also_deliberately_stateless() -> None:
|
|
# 2025-11-25 permits a server to omit Mcp-Session-Id. The Hub must do so
|
|
# because no public feature depends on transport-scoped server state.
|
|
_assert_stateless_boundary("2025-11-25")
|
|
|
|
|
|
def test_initialize_without_protocol_header_never_mints_public_session() -> None:
|
|
probe = SessionBoundaryProbe()
|
|
|
|
with _client(probe) as client:
|
|
response = client.post(
|
|
"/mcp/gitea-all/prod",
|
|
json={
|
|
"jsonrpc": "2.0",
|
|
"id": 1,
|
|
"method": "initialize",
|
|
"params": {
|
|
"protocolVersion": "2025-11-25",
|
|
"capabilities": {},
|
|
"clientInfo": {"name": "test", "version": "1"},
|
|
},
|
|
},
|
|
)
|
|
|
|
assert "mcp-session-id" not in probe.seen_headers
|
|
assert "mcp-session-id" not in response.headers
|