Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,7 @@ def _setup_mcp_tool_trigger(self, agent_name: str, agent_description: str | None
"isArray": False,
},
{
"propertyName": "threadId",
"propertyName": "thread_id",
"propertyType": "string",
"description": "Optional thread identifier for conversation continuity.",
"isRequired": False,
Expand All @@ -561,7 +561,7 @@ async def mcp_tool_handler(context: str, client: df.DurableOrchestrationClient)
"""Handle MCP tool invocation for the agent.

Args:
context: MCP tool invocation context containing arguments (query, threadId)
context: MCP tool invocation context containing arguments (query, thread_id)
client: Durable orchestration client for entity communication

Returns:
Expand Down Expand Up @@ -610,8 +610,8 @@ async def _handle_mcp_tool_invocation(
if not query or not isinstance(query, str):
raise ValueError("MCP Tool invocation is missing required 'query' argument of type string.")

# Extract optional threadId
thread_id = arguments.get("threadId")
# Extract optional thread_id
thread_id = arguments.get("thread_id")
Comment on lines +613 to +614
Copy link

Copilot AI Jan 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching the MCP argument name from threadId to thread_id means existing MCP clients still sending threadId will silently lose conversation continuity (a breaking API behavior change). Consider accepting both keys during a transition period (e.g., read thread_id and fall back to threadId, optionally with a deprecation warning), or explicitly mark/document this as a breaking change for consumers.

Suggested change
# Extract optional thread_id
thread_id = arguments.get("thread_id")
# Extract optional thread_id (prefer snake_case, but support legacy camelCase 'threadId')
thread_id = arguments.get("thread_id")
if thread_id is None:
legacy_thread_id = arguments.get("threadId")
if legacy_thread_id is not None:
logger.warning(
"MCP argument 'threadId' is deprecated; please use 'thread_id' instead."
)
thread_id = legacy_thread_id

Copilot uses AI. Check for mistakes.

# Create or parse session ID
if thread_id and isinstance(thread_id, str) and thread_id.strip():
Expand Down
8 changes: 4 additions & 4 deletions python/packages/azurefunctions/tests/test_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ async def test_handle_mcp_tool_invocation_with_json_string(self) -> None:
client.read_entity_state.return_value = mock_state

# Create JSON string context
context = '{"arguments": {"query": "test query", "threadId": "test-thread"}}'
context = '{"arguments": {"query": "test query", "thread_id": "test-thread"}}'

with patch.object(app, "_get_response_from_entity") as get_response_mock:
get_response_mock.return_value = {"status": "success", "response": "Test response"}
Expand All @@ -1008,7 +1008,7 @@ async def test_handle_mcp_tool_invocation_with_json_context(self) -> None:
client.read_entity_state.return_value = mock_state

# Create JSON string context
context = json.dumps({"arguments": {"query": "test query", "threadId": "test-thread"}})
context = json.dumps({"arguments": {"query": "test query", "thread_id": "test-thread"}})

with patch.object(app, "_get_response_from_entity") as get_response_mock:
get_response_mock.return_value = {"status": "success", "response": "Test response"}
Expand Down Expand Up @@ -1088,7 +1088,7 @@ async def test_handle_mcp_tool_invocation_ignores_agent_name_in_thread_id(self)

# Thread ID contains a different agent name (@StockAdvisor@poc123)
# but we're invoking PlantAdvisor - it should use PlantAdvisor's entity
context = json.dumps({"arguments": {"query": "test query", "threadId": "@StockAdvisor@test123"}})
context = json.dumps({"arguments": {"query": "test query", "thread_id": "@StockAdvisor@test123"}})

with patch.object(app, "_get_response_from_entity") as get_response_mock:
get_response_mock.return_value = {"status": "success", "response": "Test response"}
Expand Down Expand Up @@ -1120,7 +1120,7 @@ async def test_handle_mcp_tool_invocation_uses_plain_thread_id_as_key(self) -> N
client.read_entity_state.return_value = mock_state

# Plain thread_id without @name@key format
context = json.dumps({"arguments": {"query": "test query", "threadId": "simple-thread-123"}})
context = json.dumps({"arguments": {"query": "test query", "thread_id": "simple-thread-123"}})

with patch.object(app, "_get_response_from_entity") as get_response_mock:
get_response_mock.return_value = {"status": "success", "response": "Test response"}
Expand Down
Loading