1414from inline_snapshot import snapshot
1515
1616from mcp .server import Server , ServerRequestContext
17- from mcp .types import JSONRPCResponse , ListToolsResult , PaginatedRequestParams , Tool
17+ from mcp .types import (
18+ INVALID_REQUEST ,
19+ CallToolRequestParams ,
20+ CallToolResult ,
21+ JSONRPCError ,
22+ JSONRPCResponse ,
23+ ListToolsResult ,
24+ PaginatedRequestParams ,
25+ TextContent ,
26+ Tool ,
27+ )
1828from tests .interaction ._connect import (
1929 base_headers ,
2030 client_via_http ,
@@ -32,9 +42,25 @@ def _server() -> Server:
3242 """A minimal low-level server with one tool, so subsequent-request routing can be observed."""
3343
3444 async def list_tools (ctx : ServerRequestContext , params : PaginatedRequestParams | None ) -> ListToolsResult :
35- return ListToolsResult (tools = [Tool (name = "noop" , description = "Does nothing." , input_schema = {"type" : "object" })])
45+ return ListToolsResult (
46+ tools = [
47+ Tool (name = "noop" , description = "Does nothing." , input_schema = {"type" : "object" }),
48+ Tool (
49+ name = "client-name" ,
50+ description = "Reports the initialized client name." ,
51+ input_schema = {"type" : "object" },
52+ ),
53+ ]
54+ )
3655
37- return Server ("hosted" , on_list_tools = list_tools )
56+ async def call_tool (ctx : ServerRequestContext , params : CallToolRequestParams ) -> CallToolResult :
57+ client_params = ctx .session .client_params
58+ assert client_params is not None
59+ assert params .name == "client-name"
60+ client_name = client_params .client_info .name
61+ return CallToolResult (content = [TextContent (text = client_name )], structured_content = {"clientName" : client_name })
62+
63+ return Server ("hosted" , on_list_tools = list_tools , on_call_tool = call_tool )
3864
3965
4066@requirement ("hosting:session:create" )
@@ -142,20 +168,43 @@ async def test_terminating_one_session_leaves_others_working() -> None:
142168
143169
144170@requirement ("hosting:session:reinitialize" )
145- async def test_second_initialize_on_an_existing_session_is_accepted () -> None :
146- """A second initialize POST carrying an existing session ID is processed rather than rejected.
147-
148- See the divergence on the requirement: the entry expects a rejection, but the SDK forwards the
149- second initialize to the running server, which answers it as a fresh handshake.
150- """
171+ async def test_second_initialize_on_an_existing_session_is_rejected () -> None :
172+ """A second initialize POST carrying an existing session ID is rejected without changing client params."""
151173 async with mounted_app (_server ()) as (http , manager ):
152174 session_id = await initialize_via_http (http )
153- response , messages = await post_jsonrpc (http , initialize_body (request_id = 2 ), session_id = session_id )
175+ call_body = {"jsonrpc" : "2.0" , "id" : 2 , "method" : "tools/call" , "params" : {"name" : "client-name" }}
176+ first_call_response , first_call_messages = await post_jsonrpc (http , call_body , session_id = session_id )
177+
178+ reinitialize_body = initialize_body (request_id = 3 )
179+ reinitialize_params = reinitialize_body ["params" ]
180+ assert isinstance (reinitialize_params , dict )
181+ reinitialize_client_info = reinitialize_params ["clientInfo" ]
182+ assert isinstance (reinitialize_client_info , dict )
183+ reinitialize_client_info ["name" ] = "reinitializer"
184+
185+ response , messages = await post_jsonrpc (http , reinitialize_body , session_id = session_id )
186+ second_call_response , second_call_messages = await post_jsonrpc (
187+ http ,
188+ {"jsonrpc" : "2.0" , "id" : 4 , "method" : "tools/call" , "params" : {"name" : "client-name" }},
189+ session_id = session_id ,
190+ )
154191 assert len (manager ._server_instances ) == 1
155192
193+ assert first_call_response .status_code == 200
194+ assert isinstance (first_call_messages [0 ], JSONRPCResponse )
195+ first_call_result = CallToolResult .model_validate (first_call_messages [0 ].result )
196+ assert first_call_result .structured_content == {"clientName" : "raw" }
197+
156198 assert response .status_code == snapshot (200 )
157- assert isinstance (messages [0 ], JSONRPCResponse )
158- assert messages [0 ].id == 2
199+ assert isinstance (messages [0 ], JSONRPCError )
200+ assert messages [0 ].id == 3
201+ assert messages [0 ].error .code == INVALID_REQUEST
202+ assert messages [0 ].error .message == "Server is already initialized"
203+
204+ assert second_call_response .status_code == 200
205+ assert isinstance (second_call_messages [0 ], JSONRPCResponse )
206+ second_call_result = CallToolResult .model_validate (second_call_messages [0 ].result )
207+ assert second_call_result .structured_content == {"clientName" : "raw" }
159208
160209
161210@requirement ("hosting:stateless:no-session-id" )
0 commit comments