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