@@ -24,50 +24,54 @@ def is_fastmcp_server(server: Any) -> bool:
24
24
# A FastMCP server should have both _mcp_server and _tool_manager
25
25
return hasattr (server , "_mcp_server" ) and hasattr (server , "_tool_manager" )
26
26
27
+
27
28
def has_required_fastmcp_attributes (server : Any ) -> bool :
28
29
"""Check if a FastMCP server has all required attributes for monkey patching.
29
-
30
+
30
31
This validates that the server has all the attributes that monkey_patch.py expects.
31
32
"""
32
33
# Check for _tool_manager and its required methods
33
34
if not hasattr (server , "_tool_manager" ):
34
35
return False
35
-
36
+
36
37
tool_manager = server ._tool_manager
37
38
required_tool_manager_methods = ["add_tool" , "call_tool" , "list_tools" ]
38
39
for method in required_tool_manager_methods :
39
- if not hasattr (tool_manager , method ) or not callable (getattr (tool_manager , method )):
40
+ if not hasattr (tool_manager , method ) or not callable (
41
+ getattr (tool_manager , method )
42
+ ):
40
43
return False
41
-
44
+
42
45
# Check for _tools dict on tool_manager (used for tracking existing tools)
43
46
if not hasattr (tool_manager , "_tools" ) or not isinstance (tool_manager ._tools , dict ):
44
47
return False
45
-
48
+
46
49
# Check for add_tool method on the server itself (used for adding get_more_tools)
47
50
if not hasattr (server , "add_tool" ) or not callable (server .add_tool ):
48
51
return False
49
-
52
+
50
53
# Check for _mcp_server (used for event tracking and session management)
51
54
if not hasattr (server , "_mcp_server" ):
52
55
return False
53
-
56
+
54
57
return True
55
58
59
+
56
60
def has_neccessary_attributes (server : Any ) -> bool :
57
61
"""Check if the server has necessary attributes for compatibility."""
58
62
required_methods = ["list_tools" , "call_tool" ]
59
-
63
+
60
64
# Check for core methods that both FastMCP and Server implementations have
61
65
for method in required_methods :
62
66
if not hasattr (server , method ):
63
67
return False
64
-
68
+
65
69
# For FastMCP servers, verify all required attributes for monkey patching
66
70
if is_fastmcp_server (server ):
67
71
# Use the comprehensive FastMCP validation
68
72
if not has_required_fastmcp_attributes (server ):
69
73
return False
70
-
74
+
71
75
# Additional checks for request handling
72
76
# Use dir() to avoid triggering property getters that might raise exceptions
73
77
if "request_context" not in dir (server ._mcp_server ):
@@ -90,7 +94,7 @@ def has_neccessary_attributes(server: Any) -> bool:
90
94
return False
91
95
if not isinstance (server .request_handlers , dict ):
92
96
return False
93
-
97
+
94
98
return True
95
99
96
100
@@ -105,26 +109,29 @@ def get_mcp_compatible_error_message(error: Any) -> str:
105
109
return str (error )
106
110
return str (error )
107
111
112
+
108
113
def is_mcp_error_response (response : ServerResult ) -> tuple [bool , str ]:
109
114
"""Check if the response is an MCP error."""
110
115
try :
111
116
# ServerResult is a RootModel, so we need to access its root attribute
112
- if hasattr (response , ' root' ):
117
+ if hasattr (response , " root" ):
113
118
result = response .root
114
119
# Check if it's a CallToolResult with an error
115
- if hasattr (result , ' isError' ) and result .isError :
120
+ if hasattr (result , " isError" ) and result .isError :
116
121
# Extract error message from content
117
- if hasattr (result , ' content' ) and result .content :
122
+ if hasattr (result , " content" ) and result .content :
118
123
# content is a list of TextContent/ImageContent/EmbeddedResource
119
124
for content_item in result .content :
120
125
# Check if it has a text attribute (TextContent)
121
- if hasattr (content_item , ' text' ):
126
+ if hasattr (content_item , " text" ):
122
127
return True , str (content_item .text )
123
128
# Check if it has type and content attributes
124
- elif hasattr (content_item , 'type' ) and hasattr (content_item , 'content' ):
125
- if content_item .type == 'text' :
129
+ elif hasattr (content_item , "type" ) and hasattr (
130
+ content_item , "content"
131
+ ):
132
+ if content_item .type == "text" :
126
133
return True , str (content_item .content )
127
-
134
+
128
135
# If no text content found, stringify the first item
129
136
if result .content and len (result .content ) > 0 :
130
137
return True , str (result .content [0 ])
@@ -136,4 +143,4 @@ def is_mcp_error_response(response: ServerResult) -> tuple[bool, str]:
136
143
return False , ""
137
144
except Exception as e :
138
145
# Log unexpected errors but still return a valid response
139
- return False , f"Error checking response: { str (e )} "
146
+ return False , f"Error checking response: { str (e )} "
0 commit comments