Skip to content
Merged
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
99 changes: 84 additions & 15 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1653,12 +1653,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';
Expand Down Expand Up @@ -1726,12 +1737,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
Expand Down Expand Up @@ -1826,6 +1848,29 @@ 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
* For example: "mcp.io.github.git" → "io", "mcp_io_github_git" → "io"
* Note: Splits on both '.' and '_' to handle various naming conventions
*/
private extractMcpServerName(toolName: string): string {
// Remove the mcp. or mcp_ prefix
const withoutPrefix = toolName.replace(/^mcp[._]/, '');
// Split on . or _ and take the first part (server identifier)
const parts = withoutPrefix.split(/[._]/);
// Return the first non-empty part, or 'unknown' if none exist
return parts[0] || 'unknown';
}

/**
* Analyze a request object for all context references.
* This is the unified method that processes text, contentReferences, and variableData.
Expand Down Expand Up @@ -2862,11 +2907,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
Expand Down Expand Up @@ -3015,11 +3076,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
Expand Down
Loading