fix: resolve StopIteration by making entire stack synchronous
Browse files- Removed async from all MCP tools (create_task, list_tasks, update_task, delete_task, complete_task)
- Made MCP server.call_tool() synchronous
- Made chat endpoint and execute_tool_calls synchronous
- Qwen client already synchronous from previous fix
- All async/await removed from chat flow
- Fixes 'coroutine raised StopIteration' error
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
- backend/src/api/chat.py +6 -7
- backend/src/mcp/server.py +4 -4
- backend/src/mcp/tools.py +5 -5
backend/src/api/chat.py
CHANGED
|
@@ -4,7 +4,6 @@
|
|
| 4 |
|
| 5 |
import json
|
| 6 |
import os
|
| 7 |
-
import asyncio
|
| 8 |
from typing import Optional, List, Dict, Any
|
| 9 |
from uuid import UUID
|
| 10 |
from fastapi import APIRouter, HTTPException, status, Depends
|
|
@@ -75,12 +74,12 @@ def extract_tool_call(ai_response: str) -> Optional[Dict[str, Any]]:
|
|
| 75 |
return None
|
| 76 |
|
| 77 |
|
| 78 |
-
|
| 79 |
tool_calls: List[Dict[str, Any]],
|
| 80 |
mcp_server: MCPServer
|
| 81 |
) -> List[Dict[str, Any]]:
|
| 82 |
"""
|
| 83 |
-
Execute multiple tool calls.
|
| 84 |
|
| 85 |
Args:
|
| 86 |
tool_calls: List of tool call dicts with 'tool' and 'parameters'
|
|
@@ -95,7 +94,7 @@ async def execute_tool_calls(
|
|
| 95 |
parameters = tool_call.get("parameters", {})
|
| 96 |
|
| 97 |
try:
|
| 98 |
-
result =
|
| 99 |
results.append({
|
| 100 |
"tool": tool_name,
|
| 101 |
"success": True,
|
|
@@ -113,7 +112,7 @@ async def execute_tool_calls(
|
|
| 113 |
|
| 114 |
|
| 115 |
@router.post("/", response_model=ChatResponse)
|
| 116 |
-
|
| 117 |
request: ChatRequest,
|
| 118 |
user_id: str = Depends(get_current_user_id),
|
| 119 |
session: Session = Depends(get_session)
|
|
@@ -200,7 +199,7 @@ async def chat(
|
|
| 200 |
if tool_call:
|
| 201 |
# Execute the tool call
|
| 202 |
logger.info(f"Executing tool call: {tool_call['tool']}")
|
| 203 |
-
results =
|
| 204 |
tool_results = results
|
| 205 |
|
| 206 |
# Format tool results for AI
|
|
@@ -243,6 +242,6 @@ async def chat(
|
|
| 243 |
|
| 244 |
|
| 245 |
@router.get("/health")
|
| 246 |
-
|
| 247 |
"""Health check endpoint for chat API"""
|
| 248 |
return {"status": "healthy", "service": "chat-api"}
|
|
|
|
| 4 |
|
| 5 |
import json
|
| 6 |
import os
|
|
|
|
| 7 |
from typing import Optional, List, Dict, Any
|
| 8 |
from uuid import UUID
|
| 9 |
from fastapi import APIRouter, HTTPException, status, Depends
|
|
|
|
| 74 |
return None
|
| 75 |
|
| 76 |
|
| 77 |
+
def execute_tool_calls(
|
| 78 |
tool_calls: List[Dict[str, Any]],
|
| 79 |
mcp_server: MCPServer
|
| 80 |
) -> List[Dict[str, Any]]:
|
| 81 |
"""
|
| 82 |
+
Execute multiple tool calls (synchronous).
|
| 83 |
|
| 84 |
Args:
|
| 85 |
tool_calls: List of tool call dicts with 'tool' and 'parameters'
|
|
|
|
| 94 |
parameters = tool_call.get("parameters", {})
|
| 95 |
|
| 96 |
try:
|
| 97 |
+
result = mcp_server.call_tool(tool_name, parameters)
|
| 98 |
results.append({
|
| 99 |
"tool": tool_name,
|
| 100 |
"success": True,
|
|
|
|
| 112 |
|
| 113 |
|
| 114 |
@router.post("/", response_model=ChatResponse)
|
| 115 |
+
def chat(
|
| 116 |
request: ChatRequest,
|
| 117 |
user_id: str = Depends(get_current_user_id),
|
| 118 |
session: Session = Depends(get_session)
|
|
|
|
| 199 |
if tool_call:
|
| 200 |
# Execute the tool call
|
| 201 |
logger.info(f"Executing tool call: {tool_call['tool']}")
|
| 202 |
+
results = execute_tool_calls([tool_call], mcp_server)
|
| 203 |
tool_results = results
|
| 204 |
|
| 205 |
# Format tool results for AI
|
|
|
|
| 242 |
|
| 243 |
|
| 244 |
@router.get("/health")
|
| 245 |
+
def health_check():
|
| 246 |
"""Health check endpoint for chat API"""
|
| 247 |
return {"status": "healthy", "service": "chat-api"}
|
backend/src/mcp/server.py
CHANGED
|
@@ -38,15 +38,15 @@ class MCPServer:
|
|
| 38 |
|
| 39 |
Args:
|
| 40 |
name: Tool name (e.g., "add_task", "list_tasks")
|
| 41 |
-
func:
|
| 42 |
description: Human-readable tool description
|
| 43 |
"""
|
| 44 |
self.tools[name] = func
|
| 45 |
logger.info(f"Registered MCP tool: {name} - {description or 'No description'}")
|
| 46 |
|
| 47 |
-
|
| 48 |
"""
|
| 49 |
-
Execute an MCP tool by name.
|
| 50 |
|
| 51 |
Args:
|
| 52 |
tool_name: Name of the tool to execute
|
|
@@ -67,7 +67,7 @@ class MCPServer:
|
|
| 67 |
logger.info(f"Executing MCP tool: {tool_name} with parameters: {list(parameters.keys())}")
|
| 68 |
|
| 69 |
try:
|
| 70 |
-
result =
|
| 71 |
logger.info(f"Tool '{tool_name}' executed successfully")
|
| 72 |
return result
|
| 73 |
except Exception as e:
|
|
|
|
| 38 |
|
| 39 |
Args:
|
| 40 |
name: Tool name (e.g., "add_task", "list_tasks")
|
| 41 |
+
func: Synchronous function implementing the tool
|
| 42 |
description: Human-readable tool description
|
| 43 |
"""
|
| 44 |
self.tools[name] = func
|
| 45 |
logger.info(f"Registered MCP tool: {name} - {description or 'No description'}")
|
| 46 |
|
| 47 |
+
def call_tool(self, tool_name: str, parameters: Dict[str, Any]) -> Dict[str, Any]:
|
| 48 |
"""
|
| 49 |
+
Execute an MCP tool by name (synchronous).
|
| 50 |
|
| 51 |
Args:
|
| 52 |
tool_name: Name of the tool to execute
|
|
|
|
| 67 |
logger.info(f"Executing MCP tool: {tool_name} with parameters: {list(parameters.keys())}")
|
| 68 |
|
| 69 |
try:
|
| 70 |
+
result = tool_func(**parameters)
|
| 71 |
logger.info(f"Tool '{tool_name}' executed successfully")
|
| 72 |
return result
|
| 73 |
except Exception as e:
|
backend/src/mcp/tools.py
CHANGED
|
@@ -18,7 +18,7 @@ class MCPTools:
|
|
| 18 |
self.repo = todo_repository
|
| 19 |
self.user_id = user_id
|
| 20 |
|
| 21 |
-
|
| 22 |
self,
|
| 23 |
title: str,
|
| 24 |
description: Optional[str] = None,
|
|
@@ -78,7 +78,7 @@ class MCPTools:
|
|
| 78 |
"error": f"Failed to create task: {str(e)}"
|
| 79 |
}
|
| 80 |
|
| 81 |
-
|
| 82 |
self,
|
| 83 |
status: Optional[str] = None,
|
| 84 |
priority: Optional[str] = None,
|
|
@@ -129,7 +129,7 @@ class MCPTools:
|
|
| 129 |
"error": f"Failed to list tasks: {str(e)}"
|
| 130 |
}
|
| 131 |
|
| 132 |
-
|
| 133 |
self,
|
| 134 |
task_id: str,
|
| 135 |
title: Optional[str] = None,
|
|
@@ -206,7 +206,7 @@ class MCPTools:
|
|
| 206 |
"error": f"Failed to update task: {str(e)}"
|
| 207 |
}
|
| 208 |
|
| 209 |
-
|
| 210 |
"""
|
| 211 |
Delete a task.
|
| 212 |
|
|
@@ -244,7 +244,7 @@ class MCPTools:
|
|
| 244 |
"error": f"Failed to delete task: {str(e)}"
|
| 245 |
}
|
| 246 |
|
| 247 |
-
|
| 248 |
"""
|
| 249 |
Mark a task as completed.
|
| 250 |
|
|
|
|
| 18 |
self.repo = todo_repository
|
| 19 |
self.user_id = user_id
|
| 20 |
|
| 21 |
+
def create_task(
|
| 22 |
self,
|
| 23 |
title: str,
|
| 24 |
description: Optional[str] = None,
|
|
|
|
| 78 |
"error": f"Failed to create task: {str(e)}"
|
| 79 |
}
|
| 80 |
|
| 81 |
+
def list_tasks(
|
| 82 |
self,
|
| 83 |
status: Optional[str] = None,
|
| 84 |
priority: Optional[str] = None,
|
|
|
|
| 129 |
"error": f"Failed to list tasks: {str(e)}"
|
| 130 |
}
|
| 131 |
|
| 132 |
+
def update_task(
|
| 133 |
self,
|
| 134 |
task_id: str,
|
| 135 |
title: Optional[str] = None,
|
|
|
|
| 206 |
"error": f"Failed to update task: {str(e)}"
|
| 207 |
}
|
| 208 |
|
| 209 |
+
def delete_task(self, task_id: str) -> dict:
|
| 210 |
"""
|
| 211 |
Delete a task.
|
| 212 |
|
|
|
|
| 244 |
"error": f"Failed to delete task: {str(e)}"
|
| 245 |
}
|
| 246 |
|
| 247 |
+
def complete_task(self, task_id: str) -> dict:
|
| 248 |
"""
|
| 249 |
Mark a task as completed.
|
| 250 |
|