@@ -21,18 +21,39 @@ def parse_version(v):
2121
2222 from scout_apm .fastmcp import ScoutMiddleware
2323except (ImportError , TypeError ):
24- # fastmcp has compatibility issues with version <2.13.0
25- # This is due to us using internal methods to test the middleware hooks
26- # These internal methods were renamed in 2.13.0
2724 fastmcp_version = "0.0.0"
28- pass
25+
26+ _fastmcp_version = parse_version (fastmcp_version )
2927
3028pytestmark = pytest .mark .skipif (
31- parse_version ( fastmcp_version ) < (2 , 13 , 0 ) or sys .version_info < (3 , 10 ),
29+ _fastmcp_version < (2 , 13 , 0 ) or sys .version_info < (3 , 10 ),
3230 reason = "These tests require fastMCP 2.13.0+ and Python 3.10+" ,
3331)
3432
3533
34+ async def _call_tool (mcp , name , arguments ):
35+ """
36+ Call a tool on a FastMCP server, compatible with both 2.x and 3.x.
37+
38+ Returns (content_blocks, metadata) for uniform access.
39+ """
40+ if _fastmcp_version >= (3 ,):
41+ result = await mcp .call_tool (name , arguments )
42+ return result .content , result .meta
43+ else :
44+ return await mcp ._call_tool_mcp (name , arguments )
45+
46+
47+ async def _list_tools (mcp ):
48+ """
49+ List tools on a FastMCP server, compatible with both 2.x and 3.x.
50+ """
51+ if _fastmcp_version >= (3 ,):
52+ return await mcp .list_tools ()
53+ else :
54+ return await mcp ._list_tools_mcp ()
55+
56+
3657@contextmanager
3758def server_with_scout (scout_config = None ):
3859 """
@@ -70,14 +91,14 @@ def add_numbers(a: int, b: int) -> int:
7091 return a + b
7192
7293 # Verify tool is registered
73- tools_list = await mcp . _list_tools_mcp ( )
94+ tools_list = await _list_tools ( mcp )
7495 assert len (tools_list ) == 1
7596 assert tools_list [0 ].name == "add_numbers"
7697
7798 # Simulate tool execution using the MCP protocol method
78- result = await mcp . _call_tool_mcp ( "add_numbers" , { "a" : 5 , "b" : 3 })
79- # result is a tuple: (content_blocks, metadata)
80- content_blocks , metadata = result
99+ content_blocks , metadata = await _call_tool (
100+ mcp , "add_numbers" , { "a" : 5 , "b" : 3 }
101+ )
81102 assert len (content_blocks ) == 1
82103 assert content_blocks [0 ].text == "8"
83104
@@ -100,8 +121,10 @@ async def async_multiply(a: int, b: int) -> int:
100121 return a * b
101122
102123 # Simulate tool execution
103- result , metadata = await mcp ._call_tool_mcp ("async_multiply" , {"a" : 4 , "b" : 7 })
104- assert result [0 ].text == "28"
124+ content_blocks , metadata = await _call_tool (
125+ mcp , "async_multiply" , {"a" : 4 , "b" : 7 }
126+ )
127+ assert content_blocks [0 ].text == "28"
105128
106129 # Verify tracking
107130 assert len (tracked_requests ) == 1
@@ -130,8 +153,8 @@ def search_database(query: str) -> list:
130153 return [{"id" : 1 , "name" : "result" }]
131154
132155 # Execute tool
133- result , metadata = await mcp . _call_tool_mcp ( "search_db" , {"query" : "test" })
134- assert len (result ) == 1
156+ content_blocks , metadata = await _call_tool ( mcp , "search_db" , {"query" : "test" })
157+ assert len (content_blocks ) == 1
135158
136159 # Verify metadata tags
137160 assert len (tracked_requests ) == 1
@@ -158,13 +181,15 @@ def process_data(data: str, password: str, count: int) -> dict:
158181 return {"processed" : True , "length" : len (data )}
159182
160183 # Execute tool with sensitive parameter
161- result , metadata = await mcp ._call_tool_mcp (
162- "process_data" , {"data" : "test data" , "password" : "secret123" , "count" : 5 }
184+ content_blocks , metadata = await _call_tool (
185+ mcp ,
186+ "process_data" ,
187+ {"data" : "test data" , "password" : "secret123" , "count" : 5 },
163188 )
164189 # FastMCP returns list of ContentBlock, need to parse the JSON
165190 import json
166191
167- result_data = json .loads (result [0 ].text )
192+ result_data = json .loads (content_blocks [0 ].text )
168193 assert result_data ["processed" ] is True
169194
170195 # Verify arguments are tagged
@@ -195,7 +220,7 @@ def divide_numbers(a: float, b: float) -> float:
195220
196221 # Execute tool that raises an error
197222 with pytest .raises (ToolError , match = "Division by zero" ):
198- await mcp . _call_tool_mcp ( "divide_numbers" , {"a" : 10 , "b" : 0 })
223+ await _call_tool ( mcp , "divide_numbers" , {"a" : 10 , "b" : 0 })
199224
200225 # Verify error tracking
201226 assert len (tracked_requests ) == 1
@@ -214,9 +239,9 @@ def echo(message: str) -> str:
214239 return message
215240
216241 # Execute multiple times
217- await mcp . _call_tool_mcp ( "echo" , {"message" : "first" })
218- await mcp . _call_tool_mcp ( "echo" , {"message" : "second" })
219- await mcp . _call_tool_mcp ( "echo" , {"message" : "third" })
242+ await _call_tool ( mcp , "echo" , {"message" : "first" })
243+ await _call_tool ( mcp , "echo" , {"message" : "second" })
244+ await _call_tool ( mcp , "echo" , {"message" : "third" })
220245
221246 # Should have 3 separate tracked requests
222247 assert len (tracked_requests ) == 3
@@ -234,8 +259,8 @@ def monitored_tool() -> str:
234259 """This should not be tracked."""
235260 return "result"
236261
237- result , metadata = await mcp . _call_tool_mcp ( "monitored_tool" , {})
238- assert result [0 ].text == "result"
262+ content_blocks , metadata = await _call_tool ( mcp , "monitored_tool" , {})
263+ assert content_blocks [0 ].text == "result"
239264
240265 # Should not track when monitor is disabled
241266 assert len (tracked_requests ) == 0
0 commit comments