diff --git a/Sensor/adr_sensor/parsers/copilot_parser.py b/Sensor/adr_sensor/parsers/copilot_parser.py index f3475cf4..766c5421 100644 --- a/Sensor/adr_sensor/parsers/copilot_parser.py +++ b/Sensor/adr_sensor/parsers/copilot_parser.py @@ -150,6 +150,7 @@ def parse_session_dir(self, session_dir: Path) -> Optional[AgentEvent]: ToolUsage( tool_name=tool_dict["tool_name"], tool_type=tool_dict["tool_type"], + server_name=tool_dict.get("server_name"), arguments=tool_dict["arguments"], result=tool_dict.get("result"), status=tool_dict.get("status"), @@ -264,6 +265,7 @@ def _process_event(self, event: Dict[str, Any], session_data: Dict[str, Any]): "status": "pending", "error": None, } + self._apply_mcp_server(tool_dict, request.get("mcpServerName")) tools.append(tool_dict) if tool_call_id: session_data["pending_tool_calls"][tool_call_id] = tool_dict @@ -291,6 +293,7 @@ def _process_event(self, event: Dict[str, Any], session_data: Dict[str, Any]): tool["tool_name"] = data.get("toolName") or tool["tool_name"] tool["arguments"] = self._truncate_large_arguments(data.get("arguments") or tool["arguments"]) tool["status"] = "running" + self._apply_mcp_server(tool, data.get("mcpServerName")) session_data["model"] = data.get("model") or session_data["model"] return @@ -427,6 +430,13 @@ def _process_event(self, event: Dict[str, Any], session_data: Dict[str, Any]): if event_type == "session.plan_changed": session_data["plan_changes"].append(self._sanitize_for_context(data)) + @staticmethod + def _apply_mcp_server(tool: Dict[str, Any], server_name: Any) -> None: + """Attribute a tool to the MCP server Copilot recorded for it.""" + if isinstance(server_name, str) and server_name: + tool["tool_type"] = "mcp_tool" + tool["server_name"] = server_name + def _get_or_create_tool( self, session_data: Dict[str, Any], diff --git a/Sensor/tests/test_parsers.py b/Sensor/tests/test_parsers.py index ffeb2fd2..5e966428 100644 --- a/Sensor/tests/test_parsers.py +++ b/Sensor/tests/test_parsers.py @@ -1680,6 +1680,58 @@ def test_tool_failure_uses_error_field_when_result_is_absent(self, tmp_path): assert tool.result is None assert tool.error == "File not found" + def test_mcp_tools_are_attributed_to_their_server(self, tmp_path): + """toolRequests and tool.execution_start carry mcpServerName for MCP tools.""" + session_dir = tmp_path / "mcp-session" + events = self._minimal_events("mcp-session") + [ + { + "type": "assistant.message", + "timestamp": "2026-08-10T10:00:02.000Z", + "data": { + "messageId": "assistant-1", + "content": "", + "toolRequests": [ + { + "toolCallId": "call-1", + "name": "github-mcp-server-get_issue", + "type": "function", + "arguments": {"issue_number": 7}, + "mcpServerName": "github-mcp-server", + "mcpToolName": "get_issue", + }, + { + "toolCallId": "call-2", + "name": "bash", + "type": "function", + "arguments": {"command": "ls"}, + }, + ], + }, + }, + { + "type": "tool.execution_start", + "timestamp": "2026-08-10T10:00:03.000Z", + "data": { + "toolCallId": "call-3", + "toolName": "playwright-browser_navigate", + "arguments": {"url": "https://example.com"}, + "mcpServerName": "playwright", + "mcpToolName": "browser_navigate", + }, + }, + ] + self._write_events(session_dir, events) + + entry = CopilotParser(base_path=tmp_path).parse_session_dir(session_dir) + + tools = {tool.tool_name: tool for message in entry.chat_history for tool in message.tools} + assert tools["github-mcp-server-get_issue"].tool_type == "mcp_tool" + assert tools["github-mcp-server-get_issue"].server_name == "github-mcp-server" + assert tools["playwright-browser_navigate"].tool_type == "mcp_tool" + assert tools["playwright-browser_navigate"].server_name == "playwright" + assert tools["bash"].tool_type == "function" + assert tools["bash"].server_name is None + def test_default_max_age_days(self): assert CopilotParser().max_age_days == 14