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..367c330 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1574,12 +1574,23 @@ 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++; + 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 { + // 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 +1702,23 @@ 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++; + 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 { + // Count as regular tool call + analysis.toolCalls.total++; + analysis.toolCalls.byTool[toolName] = (analysis.toolCalls.byTool[toolName] || 0) + 1; + } } // Detect MCP servers starting @@ -1791,6 +1813,27 @@ 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_'); + } + + /** + * 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 the first non-empty part, or 'unknown' if none exist + return parts[0] || 'unknown'; + } + /** * Analyze text for context references like #file, #selection, @workspace */ @@ -2577,11 +2620,27 @@ 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)) { + const serverName = this.extractMcpServerName(toolName); + 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 +2795,19 @@ 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)) { + const serverName = this.extractMcpServerName(toolName); + 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