44This file contains tests for request validation in the StreamableHTTP transport.
55"""
66
7+ import contextlib
78import multiprocessing
89import socket
910import time
10- from collections .abc import AsyncGenerator , Generator
11+ from collections .abc import Generator
1112from http import HTTPStatus
1213from uuid import uuid4
13- import contextlib
14+
1415import anyio
1516import pytest
1617import requests
1920from starlette .applications import Starlette
2021from starlette .requests import Request
2122from starlette .responses import Response
22- from starlette .routing import Mount , Route
23+ from starlette .routing import Mount
2324
2425from mcp .server import Server
2526from mcp .server .streamableHttp import (
2930)
3031from mcp .shared .exceptions import McpError
3132from mcp .types import (
32- EmptyResult ,
3333 ErrorData ,
34- JSONRPCMessage ,
3534 TextContent ,
36- TextResourceContents ,
3735 Tool ,
3836)
3937
@@ -106,11 +104,9 @@ def create_app(session_id=None) -> Starlette:
106104 # Create server instance
107105 server = ServerTest ()
108106
109- # Store the server instances between requests for session management
110107 server_instances = {}
111108 # Lock to prevent race conditions when creating new sessions
112109 session_creation_lock = anyio .Lock ()
113- # Task group for running server instances
114110 task_group = None
115111
116112 @contextlib .asynccontextmanager
@@ -130,7 +126,6 @@ async def lifespan(app):
130126 task_group = None
131127 print ("Resources cleaned up successfully." )
132128
133- # ASGI handler for streamable HTTP connections
134129 async def handle_streamable_http (scope , receive , send ):
135130 request = Request (scope , receive )
136131 request_mcp_session_id = request .headers .get (MCP_SESSION_ID_HEADER )
@@ -141,12 +136,11 @@ async def handle_streamable_http(scope, receive, send):
141136 and request_mcp_session_id in server_instances
142137 ):
143138 transport = server_instances [request_mcp_session_id ]
144- print ( "Session already exists, handling request directly" )
139+
145140 await transport .handle_request (scope , receive , send )
146- elif session_id is None or request_mcp_session_id is None :
141+ elif request_mcp_session_id is None :
147142 async with session_creation_lock :
148- # For tests with fixed session ID
149- new_session_id = session_id if session_id else uuid4 ().hex
143+ new_session_id = uuid4 ().hex
150144
151145 http_transport = StreamableHTTPServerTransport (
152146 mcp_session_id = new_session_id ,
@@ -156,11 +150,14 @@ async def handle_streamable_http(scope, receive, send):
156150 read_stream , write_stream = streams
157151
158152 async def run_server ():
159- await server .run (
160- read_stream ,
161- write_stream ,
162- server .create_initialization_options (),
163- )
153+ try :
154+ await server .run (
155+ read_stream ,
156+ write_stream ,
157+ server .create_initialization_options (),
158+ )
159+ except Exception as e :
160+ print (f"Server exception: { e } " )
164161
165162 if task_group is None :
166163 response = Response (
@@ -533,3 +530,34 @@ def test_session_termination(session_server, server_url):
533530 )
534531 assert response .status_code == 404
535532 assert "Session has been terminated" in response .text
533+
534+
535+ def test_response (basic_server , server_url ):
536+ """Test response handling for a valid request."""
537+ mcp_url = f"{ server_url } /mcp"
538+ response = requests .post (
539+ mcp_url ,
540+ headers = {
541+ "Accept" : "application/json, text/event-stream" ,
542+ "Content-Type" : "application/json" ,
543+ },
544+ json = INIT_REQUEST ,
545+ )
546+ assert response .status_code == 200
547+
548+ # Now terminate the session
549+ session_id = response .headers .get (MCP_SESSION_ID_HEADER )
550+
551+ # Try to use the terminated session
552+ tools_response = requests .post (
553+ mcp_url ,
554+ headers = {
555+ "Accept" : "application/json, text/event-stream" ,
556+ "Content-Type" : "application/json" ,
557+ MCP_SESSION_ID_HEADER : session_id , # Use the session ID we got earlier
558+ },
559+ json = {"jsonrpc" : "2.0" , "method" : "tools/list" , "id" : "tools-1" },
560+ stream = True , # Important for SSE
561+ )
562+ assert tools_response .status_code == 200
563+ assert tools_response .headers .get ("Content-Type" ) == "text/event-stream"
0 commit comments