@@ -112,7 +112,7 @@ async def test_error_handling(lowlevel_server_simple_app: Server):
112
112
113
113
text_content = next (c for c in response .content if isinstance (c , types .TextContent ))
114
114
assert "item_id" in text_content .text .lower () or "missing" in text_content .text .lower ()
115
- assert "422 " in text_content .text , "Expected a 422 status to appear in the response text "
115
+ assert "input validation error " in text_content .text . lower () , "Expected an input validation error "
116
116
117
117
118
118
@pytest .mark .asyncio
@@ -368,3 +368,76 @@ async def test_custom_header_passthrough_to_tool_handler(fastapi_mcp_with_custom
368
368
headers_arg = mock_request .call_args [0 ][4 ] # headers are the 5th argument
369
369
assert "X-Custom-Header" in headers_arg
370
370
assert headers_arg ["X-Custom-Header" ] == "MyValue123"
371
+
372
+
373
+ @pytest .mark .asyncio
374
+ async def test_context_extraction_in_tool_handler (fastapi_mcp : FastApiMCP ):
375
+ """Test that handle_call_tool extracts HTTP request info from MCP context."""
376
+ from unittest .mock import patch , MagicMock
377
+ import mcp .types as types
378
+ from mcp .server .lowlevel .server import request_ctx
379
+
380
+ # Create a fake HTTP request object with headers
381
+ fake_http_request = MagicMock ()
382
+ fake_http_request .method = "POST"
383
+ fake_http_request .url .path = "/test"
384
+ fake_http_request .headers = {"Authorization" : "Bearer token-123" , "X-Custom" : "custom-value-123" }
385
+ fake_http_request .cookies = {}
386
+ fake_http_request .query_params = {}
387
+
388
+ # Create a fake request context containing the HTTP request
389
+ fake_request_context = MagicMock ()
390
+ fake_request_context .request = fake_http_request
391
+
392
+ # Test with authorization header extraction from context
393
+ token = request_ctx .set (fake_request_context )
394
+ try :
395
+ with patch .object (fastapi_mcp , "_execute_api_tool" ) as mock_execute :
396
+ mock_execute .return_value = [types .TextContent (type = "text" , text = "success" )]
397
+
398
+ # Create a CallToolRequest like the MCP protocol would
399
+ call_request = types .CallToolRequest (
400
+ method = "tools/call" , params = types .CallToolRequestParams (name = "get_item" , arguments = {"item_id" : 1 })
401
+ )
402
+
403
+ try :
404
+ # Call the tool handler directly like the MCP server would
405
+ await fastapi_mcp .server .request_handlers [types .CallToolRequest ](call_request )
406
+ except Exception :
407
+ pass
408
+
409
+ assert mock_execute .called , "The _execute_api_tool method was not called"
410
+
411
+ if mock_execute .called :
412
+ # Verify that HTTPRequestInfo was extracted from context and passed to _execute_api_tool
413
+ http_request_info = mock_execute .call_args .kwargs ["http_request_info" ]
414
+ assert http_request_info is not None , "HTTPRequestInfo should be extracted from context"
415
+ assert http_request_info .method == "POST"
416
+ assert http_request_info .path == "/test"
417
+ assert "Authorization" in http_request_info .headers
418
+ assert http_request_info .headers ["Authorization" ] == "Bearer token-123"
419
+ assert "X-Custom" in http_request_info .headers
420
+ assert http_request_info .headers ["X-Custom" ] == "custom-value-123"
421
+ finally :
422
+ # Clean up the context variable
423
+ request_ctx .reset (token )
424
+
425
+ # Test with missing request context (should still work but with None)
426
+ with patch .object (fastapi_mcp , "_execute_api_tool" ) as mock_execute :
427
+ mock_execute .return_value = [types .TextContent (type = "text" , text = "success" )]
428
+
429
+ call_request = types .CallToolRequest (
430
+ method = "tools/call" , params = types .CallToolRequestParams (name = "get_item" , arguments = {"item_id" : 1 })
431
+ )
432
+
433
+ try :
434
+ await fastapi_mcp .server .request_handlers [types .CallToolRequest ](call_request )
435
+ except Exception :
436
+ pass
437
+
438
+ assert mock_execute .called , "The _execute_api_tool method was not called"
439
+
440
+ if mock_execute .called :
441
+ # Verify that HTTPRequestInfo is None when context is not available
442
+ http_request_info = mock_execute .call_args .kwargs ["http_request_info" ]
443
+ assert http_request_info is None , "HTTPRequestInfo should be None when context is not available"
0 commit comments