@@ -86,7 +86,7 @@ def call_tool_through_mcp(mcp_instance, tool_name, arguments):
8686 arguments: Dictionary of arguments to pass to the tool
8787
8888 Returns:
89- The tool result (extracted from CallToolResult)
89+ The tool result normalized to {"result": value} format
9090 """
9191 import asyncio
9292 import json
@@ -102,14 +102,29 @@ def call_tool_through_mcp(mcp_instance, tool_name, arguments):
102102 if hasattr (result , "root" ):
103103 result = result .root
104104 if hasattr (result , "structuredContent" ) and result .structuredContent :
105- return result .structuredContent
106- if hasattr (result , "content" ) and result .content :
107- text = result .content [0 ].text
108- try :
109- return json .loads (text )
110- except (json .JSONDecodeError , TypeError ):
111- return text
112- return result
105+ result = result .structuredContent
106+ elif hasattr (result , "content" ):
107+ if result .content :
108+ text = result .content [0 ].text
109+ try :
110+ result = json .loads (text )
111+ except (json .JSONDecodeError , TypeError ):
112+ result = text
113+ else :
114+ # Empty content means None return
115+ result = None
116+
117+ # Normalize return value to consistent format
118+ # If already a dict, return as-is (tool functions return dicts directly)
119+ if isinstance (result , dict ):
120+ return result
121+
122+ # Handle string "None" or "null" as actual None
123+ if isinstance (result , str ) and result in ("None" , "null" ):
124+ result = None
125+
126+ # Wrap primitive values (int, str, bool, None) in dict format for consistency
127+ return {"result" : result }
113128
114129
115130async def call_tool_through_mcp_async (mcp_instance , tool_name , arguments ):
@@ -127,14 +142,29 @@ async def call_tool_through_mcp_async(mcp_instance, tool_name, arguments):
127142 if hasattr (result , "root" ):
128143 result = result .root
129144 if hasattr (result , "structuredContent" ) and result .structuredContent :
130- return result .structuredContent
131- if hasattr (result , "content" ) and result .content :
132- text = result .content [0 ].text
133- try :
134- return json .loads (text )
135- except (json .JSONDecodeError , TypeError ):
136- return text
137- return result
145+ result = result .structuredContent
146+ elif hasattr (result , "content" ):
147+ if result .content :
148+ text = result .content [0 ].text
149+ try :
150+ result = json .loads (text )
151+ except (json .JSONDecodeError , TypeError ):
152+ result = text
153+ else :
154+ # Empty content means None return
155+ result = None
156+
157+ # Normalize return value to consistent format
158+ # If already a dict, return as-is (tool functions return dicts directly)
159+ if isinstance (result , dict ):
160+ return result
161+
162+ # Handle string "None" or "null" as actual None
163+ if isinstance (result , str ) and result in ("None" , "null" ):
164+ result = None
165+
166+ # Wrap primitive values (int, str, bool, None) in dict format for consistency
167+ return {"result" : result }
138168
139169
140170def call_prompt_through_mcp (mcp_instance , prompt_name , arguments = None ):
@@ -710,7 +740,15 @@ def read_file(path: str):
710740 return "file contents"
711741
712742 with start_transaction (name = "fastmcp tx" ):
713- result = call_resource_through_mcp (mcp , "file:///test.txt" )
743+ try :
744+ result = call_resource_through_mcp (mcp , "file:///test.txt" )
745+ except ValueError as e :
746+ # Older FastMCP versions may not support this URI pattern
747+ if "Unknown resource" in str (e ):
748+ pytest .skip (
749+ f"Resource URI not supported in this FastMCP version: { e } "
750+ )
751+ raise
714752
715753 # Resource content is returned as-is
716754 assert "file contents" in result .contents [0 ].text
@@ -759,9 +797,17 @@ async def read_url(resource: str):
759797 return "resource data"
760798
761799 with start_transaction (name = "fastmcp tx" ):
762- result = await call_resource_through_mcp_async (
763- mcp , "https://example.com/resource"
764- )
800+ try :
801+ result = await call_resource_through_mcp_async (
802+ mcp , "https://example.com/resource"
803+ )
804+ except ValueError as e :
805+ # Older FastMCP versions may not support this URI pattern
806+ if "Unknown resource" in str (e ):
807+ pytest .skip (
808+ f"Resource URI not supported in this FastMCP version: { e } "
809+ )
810+ raise
765811
766812 assert "resource data" in result .contents [0 ].text
767813
@@ -1076,21 +1122,8 @@ def none_return_tool(action: str) -> None:
10761122 with start_transaction (name = "fastmcp tx" ):
10771123 result = call_tool_through_mcp (mcp , "none_return_tool" , {"action" : "log" })
10781124
1079- # Handle different return types between mcp.server.fastmcp and fastmcp
1080- if isinstance (result , dict ):
1081- # mcp.server.fastmcp wraps in dict
1082- assert result ["result" ] is None
1083- elif hasattr (result , "content" ):
1084- # fastmcp package returns CallToolResult directly
1085- # For None returns, content is empty or contains null/None
1086- if result .content :
1087- assert result .content [0 ].text in ("null" , "None" , "" )
1088- else :
1089- # Empty content means None
1090- assert result .content == []
1091- else :
1092- # Some versions might return None directly
1093- assert result is None
1125+ # Helper function normalizes to {"result": value} format
1126+ assert result ["result" ] is None
10941127
10951128 (tx ,) = events
10961129 assert tx ["type" ] == "transaction"
0 commit comments