fix: mcp get_tools_prompt fix for tools with optional arguments

Rafael Uzarowski committed May 22, 2025 at 14:08 UTC 17600dee82cbcd81ff688925b45057e742f3b91d
1 file changed +38 -21
python/helpers/mcp_handler.py
+38 -21
@@ -81,10 +81,10 @@ class MCPTool(Tool):
81 user_message_text = content
82 else:
83 # Fallback for any other types (e.g. list, if that were possible for content)
84 - user_message_text = str(content)
85 -
84 + user_message_text = str(content)
85 +
86 # Ensure user_message_text is a string before length check and slicing
87 - user_message_text = str(user_message_text)
87 + user_message_text = str(user_message_text)
88
89 # Truncate user message context if it's too long to avoid overwhelming the prompt
90 max_user_context_len = 500 # characters
@@ -251,8 +251,8 @@ class MCPConfig(BaseModel):
251 try:
252 # Try with standard json.loads first, as it should handle escaped strings correctly
253 import json
254 - parsed_value = json.loads(config_str)
255 -
254 + parsed_value = json.loads(config_str)
255 +
256 if isinstance(parsed_value, list):
257 valid_servers = []
258 for item in parsed_value:
@@ -295,13 +295,13 @@ class MCPConfig(BaseModel):
295 f"Error parsing MCP config string with DirtyJson as well: {e_dirty}. Config string was: '{config_str}'"
296 )
297 # servers_data remains empty, allowing graceful degradation
298 -
298 +
299 # Initialize/update the singleton instance with the (potentially empty) list of server data
300 instance = cls.get_instance()
301 # Directly update the servers attribute of the existing instance or re-initialize carefully
302 # For simplicity and to ensure __init__ logic runs if needed for setup:
303 new_instance_data = {'servers': servers_data} # Prepare data for re-initialization or update
304 -
304 +
305 # Option 1: Re-initialize the existing instance (if __init__ is idempotent for other fields)
306 instance.__init__(servers_list=servers_data)
307
@@ -318,7 +318,7 @@ class MCPConfig(BaseModel):
318 # PrintStyle(background_color="grey", font_color="red", padding=True).print(
319 # f"MCPConfig.update: Failed to create MCPServer from item '{server_item_data.get('name', 'Unknown')}': {e_init}"
320 # )
321 -
321 +
322 cls.__initialized = True
323 return instance
324
@@ -329,13 +329,13 @@ class MCPConfig(BaseModel):
329 if servers_list: PrintStyle(background_color="blue", font_color="white", padding=True).print(f"MCPConfig.__init__ received servers_list: {servers_list}")
330
331 # This empties the servers list if MCPConfig is a Pydantic model and servers is a field.
332 - # If servers is a field like `servers: List[MCPServer] = Field(default_factory=list)`,
332 + # If servers is a field like `servers: List[MCPServer] = Field(default_factory=list)`,
333 # then super().__init__() might try to initialize it.
334 # We are re-assigning self.servers later in this __init__.
335 - super().__init__()
335 + super().__init__()
336
337 # Clear any servers potentially initialized by super().__init__() before we populate based on servers_list
338 - self.servers = []
338 + self.servers = []
339
340 if not isinstance(servers_list, Iterable):
341 (
@@ -419,25 +419,42 @@ class MCPConfig(BaseModel):
419 tool_args = ""
420 properties: dict[str, Any] = tool["input_schema"]["properties"]
421 for key, value in properties.items():
422 - tool_args += f" \"{key}\": \"...\",\n"
422 + optional = False
423 examples = ""
424 description = ""
425 + type = ""
426 + if "anyOf" in value:
427 + for nested_value in value["anyOf"]:
428 + if "type" in nested_value and nested_value["type"] != "null":
429 + optional = True
430 + value = nested_value
431 + break
432 + tool_args += f" \"{key}\": \"...\",\n"
433 if "examples" in value:
434 examples = f"(examples: {value['examples']})"
435 if "description" in value:
436 description = f": {value['description']}"
437 + if "type" in value:
438 + if optional:
439 + type = f"{value['type']}, optional"
440 + else:
441 + type = f"{value['type']}"
442 + else:
443 + if optional:
444 + type = "string, optional"
445 + else:
446 + type = "string"
447 prompt += (
430 - f" * {key} ({value['type']}){description} {examples}\n"
448 + f" * {key} ({type}){description} {examples}\n"
449 )
450 +
451 prompt += "\n"
452
453 prompt += (
454 f"#### Usage:\n"
455 f"~~~json\n"
456 f"{{\n"
438 - f" \"observations\": [\"...\"],\n"
457 f" \"thoughts\": [\"...\"],\n"
440 - f" \"reflection\": [\"...\"],\n"
458 f" \"tool_name\": \"{server_name}.{tool['name']}\",\n"
459 f" \"tool_args\": {{\n"
460 f"{tool_args}"
@@ -510,14 +527,14 @@ class MCPClientBase(ABC):
527 ClientSession(
528 stdio,
529 write,
513 - read_timeout_seconds=timedelta(seconds=600)
530 + read_timeout_seconds=timedelta(seconds=600)
531 )
532 )
533 await session.initialize()
534 # PrintStyle(font_color="green").print(f"MCPClientBase ({self.server.name} - {operation_name}): Session initialized.")
518 -
535 +
536 result = await coro_func(session)
520 -
537 +
538 # PrintStyle(font_color="green").print(f"MCPClientBase ({self.server.name} - {operation_name}): Operation successful.")
539 return result
540 except Exception as e:
@@ -535,11 +552,11 @@ class MCPClientBase(ABC):
552
553 async def update_tools(self) -> "MCPClientBase":
554 # PrintStyle(font_color="cyan").print(f"MCPClientBase ({self.server.name}): Starting 'update_tools' operation...")
538 -
555 +
556 async def list_tools_op(current_session: ClientSession):
557 response: ListToolsResult = await current_session.list_tools()
541 - with self.__lock:
542 - self.tools = [{
558 + with self.__lock:
559 + self.tools = [{
560 "name": tool.name,
561 "description": tool.description,
562 "input_schema": tool.inputSchema