Files
prefecthq--fastmcp/tests/server/http/test_session_owner_enforcement.py
wehub-resource-sync 60e0ffc959
Upgrade checks / Notify on failure (push) Has been cancelled
Upgrade checks / Close issue on success (push) Has been cancelled
Schema Crash Test / Real-world schema crash test (232K schemas) (push) Has been cancelled
Run static analysis / static_analysis (push) Has been cancelled
Tests / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Tests / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Tests / Tests with lowest-direct dependencies (push) Has been cancelled
Tests / MCP conformance tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
Tests / Package install smoke (push) Has been cancelled
Upgrade checks / Static analysis (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.13 on ubuntu-latest (push) Has been cancelled
Upgrade checks / Tests: Python 3.10 on windows-latest (push) Has been cancelled
Upgrade checks / Integration tests (push) Has been cancelled
Update MCPServerConfig Schema / update-config-schema (push) Has been cancelled
Update SDK Documentation / update-sdk-docs (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:59 +08:00

110 lines
3.4 KiB
Python

"""End-to-end tests for session-owner credential enforcement.
A streamable-HTTP session is bound to the credential that created it. A request
that presents a different credential for the same ``Mcp-Session-Id`` is rejected
with 404, exactly as if the session did not exist. This closes a gap where a
leaked session id was usable by any bearer.
Enforcement lives in the SDK's ``StreamableHTTPSessionManager`` (the
``_session_owners`` map). It is active on FastMCP's stateful HTTP path because
the session manager owns the server lifecycle (its ``run()`` drives our
lifespan through ``_lifespan_proxy``).
"""
from starlette.testclient import TestClient
from fastmcp.server import FastMCP
from fastmcp.server.auth.providers.jwt import StaticTokenVerifier
from fastmcp.server.http import StarletteWithLifespan, create_streamable_http_app
INITIALIZE_REQUEST = {
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"capabilities": {},
"clientInfo": {"name": "client", "version": "0.1"},
},
}
TOOLS_LIST_REQUEST = {
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list",
"params": {},
}
TOKEN_A = "token-a"
TOKEN_B = "token-b"
MCP_HEADERS = {"accept": "application/json, text/event-stream"}
def _make_app() -> StarletteWithLifespan:
verifier = StaticTokenVerifier(
tokens={
TOKEN_A: {"client_id": "client-a", "scopes": []},
TOKEN_B: {"client_id": "client-b", "scopes": []},
}
)
server = FastMCP(name="OwnerEnforcementServer", auth=verifier)
return create_streamable_http_app(
server=server,
streamable_http_path="/mcp",
auth=verifier,
)
def _initialize(client: TestClient, token: str) -> str:
"""Create a session with the given token and return its session id."""
response = client.post(
"/mcp",
headers={**MCP_HEADERS, "authorization": f"Bearer {token}"},
json=INITIALIZE_REQUEST,
)
assert response.status_code == 200
session_id = response.headers.get("mcp-session-id")
assert session_id is not None
return session_id
def test_session_reuse_with_creating_credential_succeeds():
app = _make_app()
with TestClient(app, base_url="http://127.0.0.1") as client:
session_id = _initialize(client, TOKEN_A)
response = client.post(
"/mcp",
headers={
**MCP_HEADERS,
"authorization": f"Bearer {TOKEN_A}",
"mcp-session-id": session_id,
"mcp-protocol-version": "2024-11-05",
},
json=TOOLS_LIST_REQUEST,
)
assert response.status_code == 200
def test_session_reuse_with_different_credential_returns_404():
"""A session created with credential A must not be usable with credential B."""
app = _make_app()
with TestClient(app, base_url="http://127.0.0.1") as client:
session_id = _initialize(client, TOKEN_A)
response = client.post(
"/mcp",
headers={
**MCP_HEADERS,
"authorization": f"Bearer {TOKEN_B}",
"mcp-session-id": session_id,
"mcp-protocol-version": "2024-11-05",
},
json=TOOLS_LIST_REQUEST,
)
# Responds exactly as if the session did not exist.
assert response.status_code == 404