Skip to content

Commit 8d2707e

Browse files
committed
update format, broken chat
1 parent 5f9b07c commit 8d2707e

8 files changed

Lines changed: 627 additions & 142 deletions

File tree

README.md

Lines changed: 39 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,50 @@ The application combines three key components:
8383
└──────────────────┘
8484
```
8585

86-
## MCP Tools
86+
## Model Context Protocol (MCP)
87+
88+
This application implements a complete MCP server following the [MCP specification](https://modelcontextprotocol.io/introduction). The MCP server exposes todo management functionality as tools that AI agents can discover and use.
89+
90+
### MCP Documentation and Resources
91+
92+
See **[Connect to Model Context Protocol servers (preview)](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/model-context-protocol#how-it-works)** to learn about the agent integration this is used in this app.
93+
94+
### Important Limitations and Requirements
95+
96+
⚠️ **Azure AI Foundry MCP Connectivity**: Currently, Azure AI Foundry may have network restrictions that prevent connecting to external MCP servers. This is a known limitation in certain Azure regions and configurations.
97+
98+
**Requirements for MCP in Azure:**
99+
- Azure AI Foundry project must be in a supported region. See the supported regions [here](https://learn.microsoft.com/azure/ai-foundry/agents/how-to/tools/model-context-protocol#how-it-works).
100+
- Network policies must allow outbound connections to MCP servers
101+
- MCP server must be publicly accessible with proper CORS configuration
102+
- API version compatibility with Azure AI Agents service
103+
104+
**Workaround**: While MCP integration is implemented and working locally, you can still use the AI chat functionality through the direct agent API endpoints.
105+
106+
### MCP Tools Available
87107

88108
The application exposes these tools for AI agents:
89109

90-
- `create_todo` - Create new todos
91-
- `list_todos` - List all or filtered todos
92-
- `update_todo` - Update existing todos
93-
- `delete_todo` - Delete todos
110+
- `create_todo` - Create new todos with title, description, and priority
111+
- `list_todos` - List all todos or filter by completion status
112+
- `update_todo` - Update existing todo properties
113+
- `delete_todo` - Delete todos by ID
94114
- `mark_todo_complete` - Toggle completion status
95115

116+
### Testing MCP Locally
117+
118+
You can test the MCP server directly:
119+
120+
```bash
121+
# Test MCP server info
122+
curl http://localhost:8000/mcp/stream
123+
124+
# Test tool discovery
125+
curl -X POST http://localhost:8000/mcp/stream \
126+
-H "Content-Type: application/json" \
127+
-d '{"jsonrpc": "2.0", "id": 1, "method": "tools/list"}'
128+
```
129+
96130
## API Endpoints
97131

98132
### Web Interface

debug_mcp.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Debug script to test MCP integration with Azure AI Agents
4+
Based on the working sample script
5+
"""
6+
import os
7+
import asyncio
8+
from azure.identity import DefaultAzureCredential
9+
from azure.ai.agents import AgentsClient
10+
from azure.ai.agents.models import McpTool
11+
12+
# Configuration from our deployed environment
13+
PROJECT_ENDPOINT = "https://az-tda-foundry-wgznky2irncfe.services.ai.azure.com/api/projects/az-tda-project-wgznky2irncfe"
14+
MODEL_DEPLOYMENT = "gpt-4o"
15+
MCP_SERVER_URL = "https://az-tda-app-wgznky2irncfe.azurewebsites.net/mcp/stream"
16+
MCP_SERVER_LABEL = "todolist"
17+
18+
def main():
19+
print("Testing MCP integration with Azure AI Agents...")
20+
print(f"Project Endpoint: {PROJECT_ENDPOINT}")
21+
print(f"MCP Server: {MCP_SERVER_URL}")
22+
23+
# Connect to the agents client
24+
agents_client = AgentsClient(
25+
endpoint=PROJECT_ENDPOINT,
26+
credential=DefaultAzureCredential()
27+
)
28+
29+
# Initialize agent MCP tool
30+
mcp_tool = McpTool(
31+
server_label=MCP_SERVER_LABEL,
32+
server_url=MCP_SERVER_URL,
33+
)
34+
print(f"MCP Tool initialized: {mcp_tool.server_label} at {mcp_tool.server_url}")
35+
36+
# Create agent with MCP tool and process agent run
37+
with agents_client:
38+
try:
39+
# Create a new agent with the mcp tool definitions
40+
agent = agents_client.create_agent(
41+
model=MODEL_DEPLOYMENT,
42+
name="debug-mcp-agent",
43+
instructions="""
44+
You are a helpful agent that can use MCP tools to assist users.
45+
Use the available MCP tools to answer questions and perform tasks.""",
46+
tools=mcp_tool.definitions,
47+
)
48+
print(f"Created agent, ID: {agent.id}")
49+
print(f"MCP Tool definitions: {len(mcp_tool.definitions) if mcp_tool.definitions else 0} tools")
50+
51+
# Create thread for communication
52+
thread = agents_client.threads.create()
53+
print(f"Created thread, ID: {thread.id}")
54+
55+
# Create a message on the thread
56+
message = agents_client.messages.create(
57+
thread_id=thread.id,
58+
role="user",
59+
content="Create a high priority todo to buy groceries.",
60+
)
61+
print(f"Created message, ID: {message.id}")
62+
63+
# Set approval mode
64+
mcp_tool.set_approval_mode("never")
65+
print("Set MCP tool approval mode to 'never'")
66+
67+
# Create and process agent run in thread with MCP tools
68+
print("Creating and processing run...")
69+
run = agents_client.runs.create_and_process(
70+
thread_id=thread.id,
71+
agent_id=agent.id,
72+
tool_resources=mcp_tool.resources
73+
)
74+
print(f"Created run, ID: {run.id}")
75+
76+
# Check run status
77+
print(f"Run completed with status: {run.status}")
78+
if run.status == "failed":
79+
print(f"Run failed: {getattr(run, 'last_error', 'Unknown error')}")
80+
81+
# Display run steps and tool calls
82+
try:
83+
run_steps = agents_client.run_steps.list(thread_id=thread.id, run_id=run.id)
84+
print(f"Retrieved run steps...")
85+
for step in run_steps:
86+
print(f"Step {step.id} status: {step.status}")
87+
88+
# Check if there are tool calls in the step details
89+
step_details = getattr(step, 'step_details', None)
90+
if step_details:
91+
tool_calls = getattr(step_details, 'tool_calls', [])
92+
93+
if tool_calls:
94+
# Display the MCP tool call details
95+
print(" MCP Tool calls:")
96+
for call in tool_calls:
97+
print(f" Tool Call ID: {getattr(call, 'id', 'N/A')}")
98+
print(f" Type: {getattr(call, 'type', 'N/A')}")
99+
print(f" Name: {getattr(call, 'name', 'N/A')}")
100+
else:
101+
print(" No tool calls in this step")
102+
else:
103+
print(" No step details available")
104+
except Exception as e:
105+
print(f"Error retrieving run steps: {e}")
106+
107+
# Fetch and log all messages
108+
messages = agents_client.messages.list(thread_id=thread.id)
109+
print("\nConversation:")
110+
print("-" * 50)
111+
for msg in messages:
112+
if msg.text_messages:
113+
last_text = msg.text_messages[-1]
114+
print(f"{msg.role.upper()}: {last_text.text.value}")
115+
print("-" * 50)
116+
117+
# Clean-up and delete the agent once the run is finished.
118+
agents_client.delete_agent(agent.id)
119+
print("Deleted agent")
120+
121+
except Exception as e:
122+
print(f"Error during agent interaction: {e}")
123+
import traceback
124+
traceback.print_exc()
125+
126+
if __name__ == "__main__":
127+
main()

infra/main.bicep

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ resource gpt4oDeployment 'Microsoft.CognitiveServices/accounts/deployments@2024-
9191
name: 'gpt-4o'
9292
sku: {
9393
name: 'GlobalStandard'
94-
capacity: 50
94+
capacity: 100
9595
}
9696
properties: {
9797
model: {

main.py

Lines changed: 109 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from fastapi.middleware.cors import CORSMiddleware
1414
from fastapi.staticfiles import StaticFiles
1515
from fastapi.templating import Jinja2Templates
16-
from fastapi.responses import HTMLResponse
16+
from fastapi.responses import HTMLResponse, JSONResponse
1717
from pydantic import BaseModel
1818
from dotenv import load_dotenv
1919

@@ -28,7 +28,7 @@
2828
try:
2929
from azure.identity import DefaultAzureCredential
3030
from azure.ai.agents import AgentsClient
31-
from azure.ai.agents.models import McpTool
31+
from azure.ai.agents.models import McpTool, RunStatus
3232
AZURE_AI_AVAILABLE = True
3333
logger.info("✓ Azure AI packages imported successfully")
3434
except ImportError as e:
@@ -202,17 +202,18 @@ async def chat_with_agent(self, message: str) -> ChatResponse:
202202
credential=DefaultAzureCredential()
203203
)
204204

205-
# Initialize MCP tool
205+
# Re-enable MCP tool integration with proper implementation
206206
mcp_tool = McpTool(
207207
server_label=MCP_SERVER_LABEL,
208208
server_url=MCP_SERVER_URL,
209209
)
210210

211-
# Default instructions
211+
# Default instructions for MCP-enabled agent
212212
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.
216217
"""
217218

218219
with fresh_client:
@@ -242,22 +243,61 @@ async def chat_with_agent(self, message: str) -> ChatResponse:
242243

243244
# Create and process agent run with MCP tools
244245
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,
247248
tool_resources=mcp_tool.resources
248249
)
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')}"
250257

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}")
253276

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}"
261301

262302
# Clean up - delete the agent
263303
fresh_client.delete_agent(agent.id)
@@ -692,40 +732,80 @@ async def handle_tool_call(self, params: dict) -> dict:
692732
# MCP Server endpoints (for direct MCP client access)
693733
@app.get("/mcp/stream")
694734
async def mcp_stream_info():
695-
"""Information about the MCP stream endpoint"""
735+
"""Information about the MCP stream endpoint with enhanced compatibility info"""
696736
return {
697-
"info": "MCP Streamable HTTP Transport Endpoint",
737+
"info": "MCP Streamable HTTP Transport Endpoint",
698738
"description": "This endpoint provides access to todo management tools via MCP",
699739
"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+
},
700751
"available_tools": [
701752
"create_todo",
702753
"list_todos",
703754
"update_todo",
704755
"delete_todo",
705756
"mark_todo_complete"
706-
]
757+
],
758+
"endpoints": {
759+
"jsonrpc": f"{MCP_SERVER_URL}",
760+
"methods": ["initialize", "tools/list", "tools/call"]
761+
}
707762
}
708763

709764
@app.post("/mcp/stream")
710765
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"""
712767
try:
768+
# Get request data
713769
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')}")
715771

772+
# Handle the request through our MCP server
716773
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'))}")
718775

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+
)
720787

721788
except Exception as e:
722789
logger.error(f"Error in MCP endpoint: {e}")
723790
logger.error(f"Full traceback: {traceback.format_exc()}")
724-
return {
791+
792+
error_response = {
725793
"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+
}
728799
}
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+
)
729809

730810
@app.options("/mcp/stream")
731811
async def mcp_stream_options():

0 commit comments

Comments
 (0)