|
13 | 13 | from fastapi.middleware.cors import CORSMiddleware |
14 | 14 | from fastapi.staticfiles import StaticFiles |
15 | 15 | from fastapi.templating import Jinja2Templates |
16 | | -from fastapi.responses import HTMLResponse |
| 16 | +from fastapi.responses import HTMLResponse, JSONResponse |
17 | 17 | from pydantic import BaseModel |
18 | 18 | from dotenv import load_dotenv |
19 | 19 |
|
|
28 | 28 | try: |
29 | 29 | from azure.identity import DefaultAzureCredential |
30 | 30 | from azure.ai.agents import AgentsClient |
31 | | - from azure.ai.agents.models import McpTool |
| 31 | + from azure.ai.agents.models import McpTool, RunStatus |
32 | 32 | AZURE_AI_AVAILABLE = True |
33 | 33 | logger.info("✓ Azure AI packages imported successfully") |
34 | 34 | except ImportError as e: |
@@ -202,17 +202,18 @@ async def chat_with_agent(self, message: str) -> ChatResponse: |
202 | 202 | credential=DefaultAzureCredential() |
203 | 203 | ) |
204 | 204 |
|
205 | | - # Initialize MCP tool |
| 205 | + # Re-enable MCP tool integration with proper implementation |
206 | 206 | mcp_tool = McpTool( |
207 | 207 | server_label=MCP_SERVER_LABEL, |
208 | 208 | server_url=MCP_SERVER_URL, |
209 | 209 | ) |
210 | 210 |
|
211 | | - # Default instructions |
| 211 | + # Default instructions for MCP-enabled agent |
212 | 212 | instructions = """ |
213 | | - You are a helpful agent that can use MCP tools to assist users with todo management. |
214 | | - Use the available MCP tools to answer questions and perform tasks like creating, |
215 | | - listing, updating, and deleting todos. |
| 213 | + You are a helpful AI assistant with access to todo management tools via MCP. |
| 214 | + You can help users create, list, update, and delete todo items using the available MCP tools. |
| 215 | + When users ask about todos, use the MCP tools to perform the requested actions. |
| 216 | + Always be helpful and provide clear feedback about what actions you've taken. |
216 | 217 | """ |
217 | 218 |
|
218 | 219 | with fresh_client: |
@@ -242,22 +243,61 @@ async def chat_with_agent(self, message: str) -> ChatResponse: |
242 | 243 |
|
243 | 244 | # Create and process agent run with MCP tools |
244 | 245 | run = fresh_client.runs.create_and_process( |
245 | | - thread_id=thread.id, |
246 | | - agent_id=agent.id, |
| 246 | + thread_id=thread.id, |
| 247 | + agent_id=agent.id, |
247 | 248 | tool_resources=mcp_tool.resources |
248 | 249 | ) |
249 | | - logger.info(f"Created run, ID: {run.id}") |
| 250 | + logger.info(f"Created run, ID: {run.id}, Status: {run.status}") |
| 251 | + |
| 252 | + # Check run status (detailed debugging like sample script) |
| 253 | + logger.info(f"Run completed with status: {run.status}") |
| 254 | + if str(run.status) == "RunStatus.FAILED": |
| 255 | + logger.error(f"Run failed: {getattr(run, 'last_error', 'Unknown error')}") |
| 256 | + assistant_response = f"Run failed: {getattr(run, 'last_error', 'Unknown error')}" |
250 | 257 |
|
251 | | - # Get the response |
252 | | - messages = fresh_client.messages.list(thread_id=thread.id) |
| 258 | + # Display run steps and tool calls (like sample script) |
| 259 | + try: |
| 260 | + run_steps = fresh_client.run_steps.list(thread_id=thread.id, run_id=run.id) |
| 261 | + for step in run_steps: |
| 262 | + logger.info(f"Step {step.id} status: {step.status}") |
| 263 | + |
| 264 | + # Check if there are tool calls in the step details |
| 265 | + step_details = getattr(step, 'step_details', {}) |
| 266 | + tool_calls = getattr(step_details, 'tool_calls', []) if step_details else [] |
| 267 | + |
| 268 | + if tool_calls: |
| 269 | + logger.info(" MCP Tool calls:") |
| 270 | + for call in tool_calls: |
| 271 | + logger.info(f" Tool Call ID: {getattr(call, 'id', 'N/A')}") |
| 272 | + logger.info(f" Type: {getattr(call, 'type', 'N/A')}") |
| 273 | + logger.info(f" Name: {getattr(call, 'name', 'N/A')}") |
| 274 | + except Exception as e: |
| 275 | + logger.error(f"Error retrieving run steps: {e}") |
253 | 276 |
|
254 | | - # Extract the assistant's response |
255 | | - assistant_response = "No response generated" |
256 | | - for msg in messages: |
257 | | - if msg.role == "assistant" and msg.text_messages: |
258 | | - last_text = msg.text_messages[-1] |
259 | | - assistant_response = last_text.text.value |
260 | | - break |
| 277 | + if str(run.status) == "RunStatus.COMPLETED": |
| 278 | + # Get the response |
| 279 | + messages_paged = fresh_client.messages.list(thread_id=thread.id) |
| 280 | + messages = list(messages_paged) # Convert ItemPaged to list |
| 281 | + logger.info(f"Retrieved {len(messages)} messages from thread") |
| 282 | + |
| 283 | + # Log all messages for debugging |
| 284 | + for i, msg in enumerate(messages): |
| 285 | + logger.info(f"Message {i}: role={msg.role}, has_text={bool(msg.text_messages)}") |
| 286 | + if msg.text_messages: |
| 287 | + for j, text_msg in enumerate(msg.text_messages): |
| 288 | + logger.info(f" Text {j}: {text_msg.text.value[:100]}...") |
| 289 | + |
| 290 | + # Extract the assistant's response (get the last assistant message) |
| 291 | + assistant_response = "No response generated" |
| 292 | + assistant_messages = [msg for msg in messages if msg.role == "assistant"] |
| 293 | + if assistant_messages: |
| 294 | + last_assistant_msg = assistant_messages[-1] |
| 295 | + if last_assistant_msg.text_messages: |
| 296 | + assistant_response = last_assistant_msg.text_messages[-1].text.value |
| 297 | + logger.info(f"Found assistant response: {assistant_response[:100]}...") |
| 298 | + else: |
| 299 | + logger.warning(f"Run completed with unexpected status: {run.status}") |
| 300 | + assistant_response = f"Run completed with status: {run.status}" |
261 | 301 |
|
262 | 302 | # Clean up - delete the agent |
263 | 303 | fresh_client.delete_agent(agent.id) |
@@ -692,40 +732,80 @@ async def handle_tool_call(self, params: dict) -> dict: |
692 | 732 | # MCP Server endpoints (for direct MCP client access) |
693 | 733 | @app.get("/mcp/stream") |
694 | 734 | async def mcp_stream_info(): |
695 | | - """Information about the MCP stream endpoint""" |
| 735 | + """Information about the MCP stream endpoint with enhanced compatibility info""" |
696 | 736 | return { |
697 | | - "info": "MCP Streamable HTTP Transport Endpoint", |
| 737 | + "info": "MCP Streamable HTTP Transport Endpoint", |
698 | 738 | "description": "This endpoint provides access to todo management tools via MCP", |
699 | 739 | "mcp_server_url": MCP_SERVER_URL, |
| 740 | + "protocol_version": "2024-11-05", |
| 741 | + "transport": "http", |
| 742 | + "capabilities": { |
| 743 | + "tools": { |
| 744 | + "listChanged": True |
| 745 | + } |
| 746 | + }, |
| 747 | + "server_info": { |
| 748 | + "name": "Todo MCP Server", |
| 749 | + "version": "1.0.0" |
| 750 | + }, |
700 | 751 | "available_tools": [ |
701 | 752 | "create_todo", |
702 | 753 | "list_todos", |
703 | 754 | "update_todo", |
704 | 755 | "delete_todo", |
705 | 756 | "mark_todo_complete" |
706 | | - ] |
| 757 | + ], |
| 758 | + "endpoints": { |
| 759 | + "jsonrpc": f"{MCP_SERVER_URL}", |
| 760 | + "methods": ["initialize", "tools/list", "tools/call"] |
| 761 | + } |
707 | 762 | } |
708 | 763 |
|
709 | 764 | @app.post("/mcp/stream") |
710 | 765 | async def mcp_stream_endpoint(request: Request): |
711 | | - """Main MCP endpoint with JSON-RPC support""" |
| 766 | + """Main MCP endpoint with JSON-RPC support and enhanced compatibility""" |
712 | 767 | try: |
| 768 | + # Get request data |
713 | 769 | request_data = await request.json() |
714 | | - logger.info(f"MCP request: {request_data}") |
| 770 | + logger.info(f"MCP request received: method={request_data.get('method')}, id={request_data.get('id')}") |
715 | 771 |
|
| 772 | + # Handle the request through our MCP server |
716 | 773 | response_data = await mcp_server.handle_request(request_data) |
717 | | - logger.info(f"MCP response: {response_data}") |
| 774 | + logger.info(f"MCP response: id={response_data.get('id')}, has_result={bool(response_data.get('result'))}, has_error={bool(response_data.get('error'))}") |
718 | 775 |
|
719 | | - return response_data |
| 776 | + # Return with proper headers for compatibility |
| 777 | + return JSONResponse( |
| 778 | + content=response_data, |
| 779 | + headers={ |
| 780 | + "Content-Type": "application/json", |
| 781 | + "Access-Control-Allow-Origin": "*", |
| 782 | + "Access-Control-Allow-Methods": "POST, OPTIONS", |
| 783 | + "Access-Control-Allow-Headers": "Content-Type, Accept, Authorization", |
| 784 | + "Cache-Control": "no-cache" |
| 785 | + } |
| 786 | + ) |
720 | 787 |
|
721 | 788 | except Exception as e: |
722 | 789 | logger.error(f"Error in MCP endpoint: {e}") |
723 | 790 | logger.error(f"Full traceback: {traceback.format_exc()}") |
724 | | - return { |
| 791 | + |
| 792 | + error_response = { |
725 | 793 | "jsonrpc": "2.0", |
726 | | - "id": None, |
727 | | - "error": {"code": -32603, "message": f"Internal error: {str(e)}"} |
| 794 | + "id": request_data.get("id") if "request_data" in locals() else None, |
| 795 | + "error": { |
| 796 | + "code": -32603, |
| 797 | + "message": f"Internal error: {str(e)}" |
| 798 | + } |
728 | 799 | } |
| 800 | + |
| 801 | + return JSONResponse( |
| 802 | + content=error_response, |
| 803 | + status_code=500, |
| 804 | + headers={ |
| 805 | + "Content-Type": "application/json", |
| 806 | + "Access-Control-Allow-Origin": "*" |
| 807 | + } |
| 808 | + ) |
729 | 809 |
|
730 | 810 | @app.options("/mcp/stream") |
731 | 811 | async def mcp_stream_options(): |
|
0 commit comments