From f03070edbb09ad35ffbbe49c7af10d787b5262aa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:27:25 +0000 Subject: [PATCH 1/5] Initial plan From ef9f9ba9a2f6cb534a14c22f222a1b7dd0d388d9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:35:58 +0000 Subject: [PATCH 2/5] Fix MCP tool detection from toolNames.json Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- package-lock.json | 7 ++++ src/extension.ts | 92 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 84 insertions(+), 15 deletions(-) diff --git a/package-lock.json b/package-lock.json index 149eaf5..26a156b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -563,6 +563,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" }, @@ -604,6 +605,7 @@ } ], "license": "MIT", + "peer": true, "engines": { "node": ">=18" } @@ -1894,6 +1896,7 @@ "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.54.0", "@typescript-eslint/types": "8.54.0", @@ -2554,6 +2557,7 @@ "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4036,6 +4040,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -8648,6 +8653,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -8883,6 +8889,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/src/extension.ts b/src/extension.ts index da7c837..9fe396e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1574,12 +1574,25 @@ class CopilotTokenTracker implements vscode.Disposable { // Detect tool calls from Copilot CLI if (event.type === 'tool.call' || event.type === 'tool.result') { - analysis.toolCalls.total++; const toolName = event.data?.toolName || event.toolName || 'unknown'; - analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; + + // Check if this is an MCP tool by name pattern + if (this.isMcpTool(toolName)) { + // Count as MCP tool + analysis.mcpTools.total++; + // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) + const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); + const serverName = parts.length > 0 ? parts[0] : 'unknown'; + analysis.mcpTools.byServer[serverName] = (analysis.mcpTools.byServer[serverName] || 0) + 1; + analysis.mcpTools.byTool[toolName] = (analysis.mcpTools.byTool[toolName] || 0) + 1; + } else { + // Count as regular tool call + analysis.toolCalls.total++; + analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; + } } - // Detect MCP tools + // Detect MCP tools from explicit MCP events if (event.type === 'mcp.tool.call' || (event.data?.mcpServer)) { analysis.mcpTools.total++; const serverName = event.data?.mcpServer || 'unknown'; @@ -1691,12 +1704,25 @@ class CopilotTokenTracker implements vscode.Disposable { // Detect tool invocations if (responseItem.kind === 'toolInvocationSerialized' || responseItem.kind === 'prepareToolInvocation') { - analysis.toolCalls.total++; const toolName = responseItem.toolId || responseItem.toolName || responseItem.invocationMessage?.toolName || 'unknown'; - analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; + + // Check if this is an MCP tool by name pattern + if (this.isMcpTool(toolName)) { + // Count as MCP tool + analysis.mcpTools.total++; + // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) + const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); + const serverName = parts.length > 0 ? parts[0] : 'unknown'; + analysis.mcpTools.byServer[serverName] = (analysis.mcpTools.byServer[serverName] || 0) + 1; + analysis.mcpTools.byTool[toolName] = (analysis.mcpTools.byTool[toolName] || 0) + 1; + } else { + // Count as regular tool call + analysis.toolCalls.total++; + analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; + } } // Detect MCP servers starting @@ -1791,6 +1817,14 @@ class CopilotTokenTracker implements vscode.Disposable { } } + /** + * Check if a tool name indicates it's an MCP (Model Context Protocol) tool. + * MCP tools are identified by names starting with "mcp." or "mcp_" + */ + private isMcpTool(toolName: string): boolean { + return toolName.startsWith('mcp.') || toolName.startsWith('mcp_'); + } + /** * Analyze text for context references like #file, #selection, @workspace */ @@ -2577,11 +2611,29 @@ class CopilotTokenTracker implements vscode.Disposable { if ((event.type === 'tool.call' || event.type === 'tool.result') && turns.length > 0) { const lastTurn = turns[turns.length - 1]; const toolName = event.data?.toolName || event.toolName || 'unknown'; - lastTurn.toolCalls.push({ - toolName, - arguments: event.type === 'tool.call' ? JSON.stringify(event.data?.arguments || {}) : undefined, - result: event.type === 'tool.result' ? event.data?.output : undefined - }); + + // Check if this is an MCP tool by name pattern + if (this.isMcpTool(toolName)) { + // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) + const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); + const serverName = parts.length > 0 ? parts[0] : 'unknown'; + lastTurn.mcpTools.push({ server: serverName, tool: toolName }); + } else { + // Add to regular tool calls + lastTurn.toolCalls.push({ + toolName, + arguments: event.type === 'tool.call' ? JSON.stringify(event.data?.arguments || {}) : undefined, + result: event.type === 'tool.result' ? event.data?.output : undefined + }); + } + } + + // Handle explicit MCP tool calls from CLI + if ((event.type === 'mcp.tool.call' || event.data?.mcpServer) && turns.length > 0) { + const lastTurn = turns[turns.length - 1]; + const serverName = event.data?.mcpServer || 'unknown'; + const toolName = event.data?.toolName || event.toolName || 'unknown'; + lastTurn.mcpTools.push({ server: serverName, tool: toolName }); } } catch { // Skip malformed lines @@ -2736,11 +2788,21 @@ class CopilotTokenTracker implements vscode.Disposable { // Extract tool invocations if (item.kind === 'toolInvocationSerialized' || item.kind === 'prepareToolInvocation') { const toolName = item.toolId || item.toolName || item.invocationMessage?.toolName || item.toolSpecificData?.kind || 'unknown'; - toolCalls.push({ - toolName, - arguments: item.input ? JSON.stringify(item.input) : undefined, - result: item.result ? (typeof item.result === 'string' ? item.result : JSON.stringify(item.result)) : undefined - }); + + // Check if this is an MCP tool by name pattern + if (this.isMcpTool(toolName)) { + // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) + const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); + const serverName = parts.length > 0 ? parts[0] : 'unknown'; + mcpTools.push({ server: serverName, tool: toolName }); + } else { + // Add to regular tool calls + toolCalls.push({ + toolName, + arguments: item.input ? JSON.stringify(item.input) : undefined, + result: item.result ? (typeof item.result === 'string' ? item.result : JSON.stringify(item.result)) : undefined + }); + } } // Extract MCP tools From fa38386fa726d8ea55a2aa6dd1dd4d46f1d386ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:38:05 +0000 Subject: [PATCH 3/5] Extract server name logic into helper function Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/extension.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index 9fe396e..5d5dbc6 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1580,9 +1580,7 @@ class CopilotTokenTracker implements vscode.Disposable { if (this.isMcpTool(toolName)) { // Count as MCP tool analysis.mcpTools.total++; - // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) - const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); - const serverName = parts.length > 0 ? parts[0] : 'unknown'; + const serverName = this.extractMcpServerName(toolName); analysis.mcpTools.byServer[serverName] = (analysis.mcpTools.byServer[serverName] || 0) + 1; analysis.mcpTools.byTool[toolName] = (analysis.mcpTools.byTool[toolName] || 0) + 1; } else { @@ -1713,9 +1711,7 @@ class CopilotTokenTracker implements vscode.Disposable { if (this.isMcpTool(toolName)) { // Count as MCP tool analysis.mcpTools.total++; - // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) - const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); - const serverName = parts.length > 0 ? parts[0] : 'unknown'; + const serverName = this.extractMcpServerName(toolName); analysis.mcpTools.byServer[serverName] = (analysis.mcpTools.byServer[serverName] || 0) + 1; analysis.mcpTools.byTool[toolName] = (analysis.mcpTools.byTool[toolName] || 0) + 1; } else { @@ -1825,6 +1821,18 @@ class CopilotTokenTracker implements vscode.Disposable { return toolName.startsWith('mcp.') || toolName.startsWith('mcp_'); } + /** + * Extract server name from an MCP tool name. + * MCP tool names follow the format: mcp.server.tool or mcp_server_tool + */ + private extractMcpServerName(toolName: string): string { + // Remove the mcp. or mcp_ prefix + const withoutPrefix = toolName.replace(/^mcp[._]/, ''); + // Split on . or _ and take the first part + const parts = withoutPrefix.split(/[._]/); + return parts.length > 0 ? parts[0] : 'unknown'; + } + /** * Analyze text for context references like #file, #selection, @workspace */ @@ -2614,9 +2622,7 @@ class CopilotTokenTracker implements vscode.Disposable { // Check if this is an MCP tool by name pattern if (this.isMcpTool(toolName)) { - // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) - const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); - const serverName = parts.length > 0 ? parts[0] : 'unknown'; + const serverName = this.extractMcpServerName(toolName); lastTurn.mcpTools.push({ server: serverName, tool: toolName }); } else { // Add to regular tool calls @@ -2791,9 +2797,7 @@ class CopilotTokenTracker implements vscode.Disposable { // Check if this is an MCP tool by name pattern if (this.isMcpTool(toolName)) { - // Extract server name from tool name (format: mcp.server.tool or mcp_server_tool) - const parts = toolName.replace(/^mcp[._]/, '').split(/[._]/); - const serverName = parts.length > 0 ? parts[0] : 'unknown'; + const serverName = this.extractMcpServerName(toolName); mcpTools.push({ server: serverName, tool: toolName }); } else { // Add to regular tool calls From 126a57e92565ce474d0b6a5d0b4e390d30ee1e2c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:38:44 +0000 Subject: [PATCH 4/5] Fix edge case for empty server names in extractMcpServerName Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/extension.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 5d5dbc6..51af923 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1830,7 +1830,8 @@ class CopilotTokenTracker implements vscode.Disposable { const withoutPrefix = toolName.replace(/^mcp[._]/, ''); // Split on . or _ and take the first part const parts = withoutPrefix.split(/[._]/); - return parts.length > 0 ? parts[0] : 'unknown'; + // Return the first non-empty part, or 'unknown' if none exist + return parts.length > 0 && parts[0] ? parts[0] : 'unknown'; } /** From 053badf924f7b9c877e4f71cb7b774af1503fa03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 7 Feb 2026 22:39:17 +0000 Subject: [PATCH 5/5] Simplify server name extraction condition Co-authored-by: rajbos <6085745+rajbos@users.noreply.github.com> --- src/extension.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension.ts b/src/extension.ts index 51af923..367c330 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1831,7 +1831,7 @@ class CopilotTokenTracker implements vscode.Disposable { // Split on . or _ and take the first part const parts = withoutPrefix.split(/[._]/); // Return the first non-empty part, or 'unknown' if none exist - return parts.length > 0 && parts[0] ? parts[0] : 'unknown'; + return parts[0] || 'unknown'; } /**