From 4d1c4346db0115170bf14dabf7a40bb9ce748bdf Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 15:48:38 +0200 Subject: [PATCH 01/11] Switch MCP server from BinlogInsights.Mcp to AITools.BinlogMcp Replace the BinlogInsights.Mcp dotnet tool with AITools.BinlogMcp from the dotnet-eng Azure DevOps feed. Key changes: - Package ID: BinlogInsights.Mcp -> AITools.BinlogMcp - Executable: binlog-insights-mcp -> binlog-mcp - Feed: nuget.org -> pkgs.dev.azure.com/dnceng/public/.../dotnet-eng - Install/update commands now use --prerelease --add-source flags - Version detection updated for prerelease version format - All user-facing messages, docs, and prompts updated Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 43 +++++----------- package.json | 8 +-- resources/playbooks/core.md | 2 +- src/chatParticipant.ts | 2 +- src/extension.ts | 98 ++++++++++++++++++------------------- src/languageModelTools.ts | 2 +- src/mcpClient.ts | 2 +- 7 files changed, 70 insertions(+), 87 deletions(-) diff --git a/README.md b/README.md index d7cba1a..ae43968 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Analyze MSBuild binary logs (`.binlog`) with **GitHub Copilot Chat** and **MCP t @binlog /perf ``` -The [BinlogInsights.Mcp](https://www.nuget.org/packages/BinlogInsights.Mcp) server (28 analysis tools) is auto-installed on first use. +The [AITools.BinlogMcp](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/AITools.BinlogMcp) server (28 analysis tools) is auto-installed on first use. ## What You Get @@ -32,7 +32,7 @@ The [BinlogInsights.Mcp](https://www.nuget.org/packages/BinlogInsights.Mcp) serv | **Fix All Issues** | Copilot fixes all build errors/warnings, rebuilds, and loads before/after for comparison | | **Auto-fix Diagnostic** | Right-click any error/warning in the tree → "Auto-fix with Copilot" to fix it directly | | **Optimize Build** | Pick optimizations, Copilot applies changes, verify with A/B comparison | -| **Build Analysis Mode** | Chat mode pre-configured with BinlogInsights MCP tools — works with any agent | +| **Build Analysis Mode** | Chat mode pre-configured with AITools.BinlogMcp MCP tools — works with any agent | | **Language Model Tools** | `binlog_lm_overview`, `binlog_lm_errors`, `binlog_lm_search`, `binlog_lm_perf`, `binlog_lm_compare` — available to @workspace, agent mode, and custom chat modes | | **CI/CD Integration** | Download binlogs from Azure DevOps Pipelines and GitHub Actions — filter by branch or PR | | **Problems Panel** | Build diagnostics as native VS Code errors/warnings with per-project CodeLens and "Ask @binlog" CodeActions | @@ -52,53 +52,36 @@ The [BinlogInsights.Mcp](https://www.nuget.org/packages/BinlogInsights.Mcp) serv ## Troubleshooting: MCP Server Installation -The extension auto-installs [BinlogInsights.Mcp](https://www.nuget.org/packages/BinlogInsights.Mcp) via `dotnet tool install -g`. In corporate environments with restricted NuGet feeds, this may fail. Here are the workarounds: +The extension auto-installs [AITools.BinlogMcp](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/AITools.BinlogMcp) via `dotnet tool install -g`. In corporate environments with restricted NuGet feeds, this may fail. Here are the workarounds: -### 1. Install with explicit NuGet source +### 1. Install with explicit feed source ```bash -dotnet tool install -g BinlogInsights.Mcp --add-source https://api.nuget.org/v3/index.json +dotnet tool install -g AITools.BinlogMcp --prerelease --add-source https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json ``` -### 2. Manual download fallback - -If all `dotnet tool install` attempts fail (e.g., nuget.org is blocked): - -1. Download the `.nupkg` directly from [nuget.org](https://www.nuget.org/packages/BinlogInsights.Mcp) -2. Install from the local file: - -```bash -# Download (replace {version} with latest, e.g. 0.2.0) -Invoke-WebRequest -Uri "https://www.nuget.org/api/v2/package/BinlogInsights.Mcp/{version}" -OutFile "BinlogInsights.Mcp.nupkg" - -# Install from local file -dotnet tool install -g BinlogInsights.Mcp --add-source . -``` - -### 3. Diagnose NuGet issues +### 2. Diagnose NuGet issues ```bash dotnet nuget list source ``` Common problems: -- **nuget.org not listed or disabled** — the tool is published on nuget.org -- **Authenticated feed requires credentials** — may block fallthrough to nuget.org -- **Package source mapping** excludes nuget.org for this package +- **dotnet-eng feed not configured** — the tool is published on the dotnet-eng Azure DevOps feed +- **Authenticated feed requires credentials** — may block access to the feed +- **Package source mapping** excludes the dotnet-eng feed for this package -### 4. Verify installation +### 3. Verify installation ```bash -dotnet tool list -g | Select-String BinlogInsights -binlog-insights-mcp --help +dotnet tool list -g | Select-String AITools.BinlogMcp +binlog-mcp --help ``` -> For the full troubleshooting guide, see [BinlogInsights repo setup instructions](https://github.com/SergeyTeplyakov/BinlogInsights/blob/main/samples/repo-setup/.github/skills/build-tool-setup/SKILL.md). - ## Related Projects - [MSBuild Structured Log Viewer](https://github.com/KirillOsenkov/MSBuildStructuredLog) — WPF viewer with secrets redaction -- [BinlogInsights](https://github.com/SergeyTeplyakov/BinlogInsights) — CLI + MCP server for binlog analysis +- [AITools.BinlogMcp](https://dev.azure.com/dnceng/public/_artifacts/feed/dotnet-eng/NuGet/AITools.BinlogMcp) — MCP server for binlog analysis - [MSBuild Binary Log docs](https://learn.microsoft.com/en-us/visualstudio/msbuild/obtaining-build-logs-with-msbuild#save-a-binary-log) ## License diff --git a/package.json b/package.json index db8d22c..5d5f44e 100644 --- a/package.json +++ b/package.json @@ -247,12 +247,12 @@ "binlogAnalyzer.mcpServerPath": { "type": "string", "default": "", - "description": "Custom path to the MCP server dotnet tool (BinlogInsights.Mcp or baronfel.binlog.mcp). Leave empty to use the globally installed tool." + "description": "Custom path to the MCP server dotnet tool (AITools.BinlogMcp). Leave empty to use the globally installed tool." }, "binlogAnalyzer.mcpServerArgs": { "type": "string", "default": "--binlog ${binlog}", - "markdownDescription": "Argument template for the MCP server. Use `${binlog}` as a placeholder for each binlog path. Repeated for each loaded binlog. Examples:\n- BinlogInsights: `--binlog ${binlog}` (default)\n- baronfel: `--binlog ${binlog}`\n- Custom: `--file ${binlog}`" + "markdownDescription": "Argument template for the MCP server. Use `${binlog}` as a placeholder for each binlog path. Repeated for each loaded binlog. Examples:\n- AITools.BinlogMcp: `--binlog ${binlog}` (default)\n- Custom: `--file ${binlog}`" }, "binlogAnalyzer.autoLoad": { "type": "boolean", @@ -520,14 +520,14 @@ { "slug": "build-analysis", "name": "Build Analysis", - "description": "Pre-configured for MSBuild binlog investigation with BinlogInsights MCP tools.", + "description": "Pre-configured for MSBuild binlog investigation with AITools.BinlogMcp MCP tools.", "toolsReferences": [ { "type": "tool", "id": "binlog_insights_mcp" } ], - "systemPrompt": "You are an MSBuild build analysis assistant with BinlogInsights MCP tools available. Begin investigations with binlog_lm_overview (or binlog_overview) unless the question is narrowly scoped, then drill in with the most specific tool. For performance: rank with binlog_expensive_targets / binlog_expensive_tasks / binlog_expensive_projects / binlog_expensive_analyzers. For errors: binlog_lm_errors (or binlog_errors) then binlog_lm_search (or binlog_search). For deeper context the @binlog chat participant exposes the same tools plus prepared playbooks (try /perf, /incremental, /summary, /errors, /buildcheck). All tools take a binlog_file path parameter — ask the user if you don't know it. Reference real file paths, line numbers and error codes from tool output; don't invent numbers." + "systemPrompt": "You are an MSBuild build analysis assistant with AITools.BinlogMcp MCP tools available. Begin investigations with binlog_lm_overview (or binlog_overview) unless the question is narrowly scoped, then drill in with the most specific tool. For performance: rank with binlog_expensive_targets / binlog_expensive_tasks / binlog_expensive_projects / binlog_expensive_analyzers. For errors: binlog_lm_errors (or binlog_errors) then binlog_lm_search (or binlog_search). For deeper context the @binlog chat participant exposes the same tools plus prepared playbooks (try /perf, /incremental, /summary, /errors, /buildcheck). All tools take a binlog_file path parameter — ask the user if you don't know it. Reference real file paths, line numbers and error codes from tool output; don't invent numbers." } ], "menus": { diff --git a/resources/playbooks/core.md b/resources/playbooks/core.md index 86c8492..f9eaf1d 100644 --- a/resources/playbooks/core.md +++ b/resources/playbooks/core.md @@ -1,4 +1,4 @@ -You are an MSBuild build analysis assistant embedded in VS Code. The user has loaded one or more `.binlog` files (binary build logs) and you have BinlogInsights MCP tools available to inspect them. +You are an MSBuild build analysis assistant embedded in VS Code. The user has loaded one or more `.binlog` files (binary build logs) and you have AITools.BinlogMcp MCP tools available to inspect them. Workflow contract: - Act on the user's request immediately. Do not ask the user for permission, do not ask "would you like me to…", do not echo the request back. Just call the right tool. diff --git a/src/chatParticipant.ts b/src/chatParticipant.ts index 4a9b853..4abecb5 100644 --- a/src/chatParticipant.ts +++ b/src/chatParticipant.ts @@ -129,7 +129,7 @@ export class BinlogChatParticipant { if (tools.length === 0 && !isBuildCheck) { stream.markdown( '⚠️ No binlog MCP tools found. The MCP server may not be running.\n\n' + - '1. Check `dotnet tool list -g` for `BinlogInsights.Mcp`\n' + + '1. Check `dotnet tool list -g` for `AITools.BinlogMcp`\n' + '2. Restart VS Code to reload MCP servers\n', ); return; diff --git a/src/extension.ts b/src/extension.ts index fe02fc2..bc302af 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -29,7 +29,8 @@ let statusBarItem: vscode.StatusBarItem | undefined; let extensionContext: vscode.ExtensionContext | undefined; let openedViaUri = false; let optimizeInProgress = false; -let cachedInsightsExePath: string | null | undefined; // undefined = not searched yet +let cachedMcpExePath: string | null | undefined; // undefined = not searched yet +const DOTNET_ENG_FEED = 'https://pkgs.dev.azure.com/dnceng/public/_packaging/dotnet-eng/nuget/v3/index.json'; let codeLensRegistered = false; function escapeHtml(s: string): string { @@ -2029,7 +2030,7 @@ async function startMcpClientForTree(binlogPaths: string[]) { const config = vscode.workspace.getConfiguration('binlogAnalyzer'); const customPath = config.get('mcpServerPath', ''); - let toolExe = customPath || findBinlogInsightsTool(); + let toolExe = customPath || findMcpTool(); if (!toolExe) { // Don't block tree loading with install — configureMcpServer handles install return; @@ -2100,12 +2101,12 @@ async function configureMcpServer(binlogPaths: string[], config: vscode.Workspac ...(workspaceCwd && { cwd: workspaceCwd }), }; } else { - let insightsExe = findBinlogInsightsTool(); + let insightsExe = findMcpTool(); if (!insightsExe) { - insightsExe = await installBinlogInsightsTool(); + insightsExe = await installMcpTool(); // After install, start the tree client (it skipped earlier because tool wasn't found) if (insightsExe) { - cachedInsightsExePath = insightsExe; + cachedMcpExePath = insightsExe; startMcpClientForTree(binlogPaths).then(() => { treeDataProvider?.setLoading(false); updateStatusBar(); @@ -2113,7 +2114,7 @@ async function configureMcpServer(binlogPaths: string[], config: vscode.Workspac treeDataProvider?.setLoading(false); updateStatusBar(); surfaceMcpError('startMcpClientForTree:postInstall', err, { - userMessage: 'BinlogInsights installed but the MCP server failed to start. Reload the window.', + userMessage: 'AITools.BinlogMcp installed but the MCP server failed to start. Reload the window.', }); }); } @@ -2131,15 +2132,15 @@ async function configureMcpServer(binlogPaths: string[], config: vscode.Workspac insightsConfig = { type: 'stdio', command: 'dotnet', - args: ['tool', 'run', 'binlog-insights-mcp', '--', ...binlogArgs], + args: ['tool', 'run', 'binlog-mcp', '--', ...binlogArgs], ...(workspaceCwd && { cwd: workspaceCwd }), }; vscode.window.showWarningMessage( - 'Could not find or install BinlogInsights.Mcp. Install it manually: `dotnet tool install -g BinlogInsights.Mcp`', + 'Could not find or install AITools.BinlogMcp. Install it manually: `dotnet tool install -g AITools.BinlogMcp --prerelease --add-source ' + DOTNET_ENG_FEED + '`', 'Copy Command' ).then(sel => { if (sel === 'Copy Command') { - vscode.env.clipboard.writeText('dotnet tool install -g BinlogInsights.Mcp'); + vscode.env.clipboard.writeText('dotnet tool install -g AITools.BinlogMcp --prerelease --add-source ' + DOTNET_ENG_FEED); } }); } @@ -2201,14 +2202,14 @@ async function writeUserMcpJson(serverConfig: Record) { } catch { /* non-fatal */ } } -const NUGET_PACKAGE_ID = 'BinlogInsights.Mcp'; +const NUGET_PACKAGE_ID = 'AITools.BinlogMcp'; -/** Checks whether a server config entry refers to the BinlogInsights MCP server. */ -function serverMatchesBinlogInsights(s: any): boolean { +/** Checks whether a server config entry refers to the binlog MCP server. */ +function serverMatchesBinlogMcp(s: any): boolean { const cmd = typeof s?.command === 'string' ? s.command : ''; const args: string[] = Array.isArray(s?.args) ? s.args : []; const combined = [cmd, ...args].join(' ').toLowerCase(); - return combined.includes('binlog-insights-mcp') || combined.includes('binloginsights.mcp'); + return combined.includes('binlog-mcp') || combined.includes('binloginsights.mcp') || combined.includes('aitools.binlogmcp'); } /** Returns paths to mcp.json files that define the binlog-insights server. */ @@ -2223,7 +2224,7 @@ function getMcpConfigPaths(): string[] { try { const content = JSON.parse(fs.readFileSync(wsMcp, 'utf8')); const servers = content.servers || {}; - if (Object.values(servers).some((s: any) => serverMatchesBinlogInsights(s))) { + if (Object.values(servers).some((s: any) => serverMatchesBinlogMcp(s))) { paths.push(wsMcp); } } catch { /* ignore parse errors */ } @@ -2250,7 +2251,7 @@ function getMcpConfigPaths(): string[] { } async function fetchAboutInfo(mode: 'interactive' | 'auto' | 'silent') { - const toolPath = findBinlogInsightsTool(); + const toolPath = findMcpTool(); const version = toolPath ? await getInstalledMcpVersion(toolPath) : null; const latestVersion = await getLatestNuGetVersion(); @@ -2267,23 +2268,23 @@ async function fetchAboutInfo(mode: 'interactive' | 'auto' | 'silent') { if ((mode === 'interactive' || mode === 'auto') && updateAvailable) { const choice = await vscode.window.showInformationMessage( - `BinlogInsights.Mcp update available: v${version} → v${latestVersion}`, + `AITools.BinlogMcp update available: v${version} → v${latestVersion}`, 'Update Now' ); if (choice === 'Update Now') { await updateMcpServer(); } } else if (mode === 'interactive' && !updateAvailable && version) { - vscode.window.showInformationMessage(`BinlogInsights.Mcp v${version} is up to date.`); + vscode.window.showInformationMessage(`AITools.BinlogMcp v${version} is up to date.`); } } async function getInstalledMcpVersion(toolPath: string): Promise { // Primary: read version from the .store directory (works for all versions, even old ones without --version) try { - const storeDir = path.join(os.homedir(), '.dotnet', 'tools', '.store', 'binloginsights.mcp'); + const storeDir = path.join(os.homedir(), '.dotnet', 'tools', '.store', 'aitools.binlogmcp'); if (fs.existsSync(storeDir)) { - const versions = fs.readdirSync(storeDir).filter(d => /^\d+\.\d+\.\d+$/.test(d)); + const versions = fs.readdirSync(storeDir).filter(d => /^\d+\.\d+\.\d+/.test(d)); if (versions.length > 0) { // Sort and pick the highest (there should only be one for a global tool) versions.sort(compareVersions); @@ -2309,8 +2310,9 @@ async function getLatestNuGetVersion(): Promise { const cp = require('child_process'); return new Promise((resolve) => { - // Use dotnet CLI which respects all configured NuGet sources (including local feeds) - cp.execFile('dotnet', ['package', 'search', NUGET_PACKAGE_ID, '--exact-match', '--format', 'json'], + // Search the dotnet-eng feed for the package (includes prereleases) + cp.execFile('dotnet', ['package', 'search', NUGET_PACKAGE_ID, '--exact-match', '--format', 'json', '--prerelease', + '--source', DOTNET_ENG_FEED], { timeout: 30000, encoding: 'utf8' }, (error: any, stdout: string) => { if (error) { @@ -2329,11 +2331,9 @@ async function getLatestNuGetVersion(): Promise { } } } - // Filter out prereleases and find the highest version - const stable = allVersions.filter(v => !v.includes('-')); - if (stable.length === 0) { resolve(null); return; } - stable.sort(compareVersions); - resolve(stable[stable.length - 1]); + if (allVersions.length === 0) { resolve(null); return; } + allVersions.sort(compareVersions); + resolve(allVersions[allVersions.length - 1]); } catch { resolve(null); } @@ -2359,16 +2359,16 @@ async function updateMcpServer() { // No binlog loaded — no MCP server running from our extension, try direct update const cp = require('child_process'); const result = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'Updating BinlogInsights MCP server...' }, + { location: vscode.ProgressLocation.Notification, title: 'Updating AITools.BinlogMcp MCP server...' }, () => new Promise<{ success: boolean; output: string }>((resolve) => { - cp.execFile('dotnet', ['tool', 'update', '-g', 'BinlogInsights.Mcp'], { timeout: 60000 }, (err: Error | null, stdout: string, stderr: string) => { + cp.execFile('dotnet', ['tool', 'update', '-g', 'AITools.BinlogMcp', '--prerelease', '--add-source', DOTNET_ENG_FEED], { timeout: 60000 }, (err: Error | null, stdout: string, stderr: string) => { resolve({ success: !err, output: (stderr || stdout || '').toString() }); }); }) ); - cachedInsightsExePath = undefined; + cachedMcpExePath = undefined; if (result.success) { - vscode.window.showInformationMessage('BinlogInsights MCP server updated successfully.'); + vscode.window.showInformationMessage('AITools.BinlogMcp MCP server updated successfully.'); await fetchAboutInfo('silent'); } else { vscode.window.showErrorMessage(`Failed to update: ${result.output.substring(0, 200)}`); @@ -2399,9 +2399,9 @@ async function applyPendingToolUpdate(): Promise { const cp = require('child_process'); const result = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'Updating BinlogInsights MCP server...' }, + { location: vscode.ProgressLocation.Notification, title: 'Updating AITools.BinlogMcp MCP server...' }, () => new Promise<{ success: boolean; output: string }>((resolve) => { - cp.execFile('dotnet', ['tool', 'update', '-g', 'BinlogInsights.Mcp'], { timeout: 60000 }, (err: Error | null, stdout: string, stderr: string) => { + cp.execFile('dotnet', ['tool', 'update', '-g', 'AITools.BinlogMcp', '--prerelease', '--add-source', DOTNET_ENG_FEED], { timeout: 60000 }, (err: Error | null, stdout: string, stderr: string) => { resolve({ success: !err, output: (stderr || stdout || '').toString() }); }); }) @@ -2409,25 +2409,25 @@ async function applyPendingToolUpdate(): Promise { // Don't clear binlog.updatingMcp here — the auto-restore will clear it after loading binlogs, // or we clear it below if there are no binlogs to restore. - cachedInsightsExePath = undefined; + cachedMcpExePath = undefined; if (result.success) { - vscode.window.showInformationMessage('BinlogInsights MCP server updated successfully.'); + vscode.window.showInformationMessage('AITools.BinlogMcp MCP server updated successfully.'); } else { - vscode.window.showErrorMessage(`Failed to update BinlogInsights.Mcp: ${result.output.substring(0, 200)}`); + vscode.window.showErrorMessage(`Failed to update AITools.BinlogMcp: ${result.output.substring(0, 200)}`); } } -function findBinlogInsightsTool(): string | null { - if (cachedInsightsExePath !== undefined) { return cachedInsightsExePath; } +function findMcpTool(): string | null { + if (cachedMcpExePath !== undefined) { return cachedMcpExePath; } const homeDir = os.homedir(); const isWindows = process.platform === 'win32'; - const exeName = isWindows ? 'binlog-insights-mcp.exe' : 'binlog-insights-mcp'; + const exeName = isWindows ? 'binlog-mcp.exe' : 'binlog-mcp'; // Global dotnet tools are installed in ~/.dotnet/tools/ const globalToolPath = path.join(homeDir, '.dotnet', 'tools', exeName); if (fs.existsSync(globalToolPath)) { - cachedInsightsExePath = globalToolPath; + cachedMcpExePath = globalToolPath; return globalToolPath; } @@ -2437,7 +2437,7 @@ function findBinlogInsightsTool(): string | null { const candidate = path.join(dir, exeName); try { if (fs.existsSync(candidate)) { - cachedInsightsExePath = candidate; + cachedMcpExePath = candidate; return candidate; } } catch { @@ -2445,28 +2445,28 @@ function findBinlogInsightsTool(): string | null { } } - cachedInsightsExePath = null; + cachedMcpExePath = null; return null; } -async function installBinlogInsightsTool(): Promise { - cachedInsightsExePath = undefined; // Reset cache so findBinlogInsightsTool re-scans after install +async function installMcpTool(): Promise { + cachedMcpExePath = undefined; // Reset cache so findMcpTool re-scans after install const cp = require('child_process'); const result = await vscode.window.withProgress( - { location: vscode.ProgressLocation.Notification, title: 'Installing BinlogInsights MCP server (dotnet tool)...' }, + { location: vscode.ProgressLocation.Notification, title: 'Installing AITools.BinlogMcp MCP server (dotnet tool)...' }, () => new Promise((resolve) => { - cp.execFile('dotnet', ['tool', 'install', '-g', 'BinlogInsights.Mcp'], { timeout: 60000 }, (err: Error | null) => { + cp.execFile('dotnet', ['tool', 'install', '-g', 'AITools.BinlogMcp', '--prerelease', '--add-source', DOTNET_ENG_FEED], { timeout: 60000 }, (err: Error | null) => { if (err) { - cp.execFile('dotnet', ['tool', 'update', '-g', 'BinlogInsights.Mcp'], { timeout: 60000 }, () => { - const exe = findBinlogInsightsTool(); + cp.execFile('dotnet', ['tool', 'update', '-g', 'AITools.BinlogMcp', '--prerelease', '--add-source', DOTNET_ENG_FEED], { timeout: 60000 }, () => { + const exe = findMcpTool(); telemetry.trackToolInstall(!!exe); resolve(exe); }); } else { - const exe = findBinlogInsightsTool(); + const exe = findMcpTool(); telemetry.trackToolInstall(!!exe); if (exe) { - vscode.window.showInformationMessage('✅ BinlogInsights MCP server installed successfully.'); + vscode.window.showInformationMessage('✅ AITools.BinlogMcp MCP server installed successfully.'); } resolve(exe); } diff --git a/src/languageModelTools.ts b/src/languageModelTools.ts index 1b1154b..8c0b911 100644 --- a/src/languageModelTools.ts +++ b/src/languageModelTools.ts @@ -2,7 +2,7 @@ import * as vscode from 'vscode'; import { McpClient } from './mcpClient'; /** - * Bridges BinlogInsights MCP tools into VS Code's `vscode.lm.tools` registry. + * Bridges AITools.BinlogMcp MCP tools into VS Code's `vscode.lm.tools` registry. * * Why this exists * --------------- diff --git a/src/mcpClient.ts b/src/mcpClient.ts index f56747e..a73fb68 100644 --- a/src/mcpClient.ts +++ b/src/mcpClient.ts @@ -17,7 +17,7 @@ function log(msg: string) { /** * Minimal MCP (Model Context Protocol) clientthat communicates with - * BinlogInsights.Mcp over stdio using JSON-RPC 2.0 with newline-delimited JSON. + * AITools.BinlogMcp over stdio using JSON-RPC 2.0 with newline-delimited JSON. * * Emits: * - 'unexpected-exit' when the server process exits unexpectedly (not via dispose()) From fb69991f60b1699393576dd881d6f381491c193d Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:07:02 +0200 Subject: [PATCH 02/11] Fix format compatibility with AITools.BinlogMcp AITools.BinlogMcp returns different response formats than BinlogInsights: - binlog_overview: human-readable text instead of JSON - binlog_search: human-readable text instead of JSON array - binlog_projects: uses projectFile instead of fullPath - binlog_expensive_targets/tasks: uses targetName/taskName instead of name Changes: - binlogDocumentProvider: handle text overview, read projectFile in array format - binlogTreeView: handle text search results in analyzer fallback, extract extractAnalyzerTiming() method for reuse across JSON and text formats - Both old JSON and new text formats are supported for backward compatibility Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogDocumentProvider.ts | 20 ++++++-- src/binlogTreeView.ts | 95 +++++++++++++++++++++-------------- 2 files changed, 75 insertions(+), 40 deletions(-) diff --git a/src/binlogDocumentProvider.ts b/src/binlogDocumentProvider.ts index e70a684..6d83061 100644 --- a/src/binlogDocumentProvider.ts +++ b/src/binlogDocumentProvider.ts @@ -119,7 +119,21 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Build overview try { const overviewResult = await this.call('binlog_overview', {}, binlogPath); - const ov = JSON.parse(overviewResult.text); + // Handle both JSON (BinlogInsights) and text (AITools.BinlogMcp) formats + let ov: any; + try { + ov = JSON.parse(overviewResult.text); + } catch { + // AITools.BinlogMcp returns human-readable text — parse it + const text = overviewResult.text; + ov = { + succeeded: /SUCCEEDED/i.test(text), + msBuildVersion: text.match(/MSBuild:\s*(.+)/)?.[1]?.trim() || '', + errorCount: parseInt(text.match(/Errors:\s*(\d+)/)?.[1] || '0'), + warningCount: parseInt(text.match(/Warnings:\s*(\d+)/)?.[1] || '0'), + duration: text.match(/Duration:\s*(.+)/)?.[1]?.trim() || '', + }; + } const status = ov.succeeded ? '✅ BUILD SUCCEEDED' : '❌ BUILD FAILED'; const dur = ov.duration || ''; // Parse "HH:MM:SS.xxx" duration to a readable format @@ -152,8 +166,8 @@ export class BinlogDocumentProvider implements vscode.TextDocumentContentProvide // Handle both formats: array (BinlogInsights) and object (baronfel) let projectFiles: string[] = []; if (Array.isArray(projData)) { - // BinlogInsights: [{ fullPath, isLegacy }, ...] - projectFiles = projData.map((p: any) => p.fullPath || '').filter(Boolean); + // Array format: [{ fullPath/projectFile, ... }, ...] + projectFiles = projData.map((p: any) => p.fullPath || p.projectFile || '').filter(Boolean); } else { // baronfel: { "id": { projectFile, entryTargets }, ... } projectFiles = Object.values(projData as Record) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index 9e67e93..0f688a1 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -878,10 +878,15 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider 0 || + (checkText && !checkText.startsWith('No results') && checkText.includes('Total analyzer execution')); - if (checkEntries.length === 0) { + if (!hasResults) { this.analyzersCache = []; return [this.makeInfoItem('No analyzer data found', 'info')]; } @@ -915,43 +920,19 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider 0.001 && name.length > 5 && !name.startsWith('Total')) { - const durationMs = Math.round(seconds * 1000); - const existing = analyzerMap.get(name); - if (existing) { - existing.durationMs += durationMs; - existing.count++; - } else { - analyzerMap.set(name, { durationMs, count: 1 }); - } - } - continue; - } - // Match assembly-level summary: "363 ms AssemblyFullName, Version=... : AnalyzerName = 341 ms" - const asmMatch = msg.match(/^(\d+)\s*ms\s{2,}(.+)/); - if (asmMatch && entry.nodeType === 'Item') { - const durationMs = parseInt(asmMatch[1]); - let name = asmMatch[2].trim(); - // Trim version info: "Name, Version=... : SubName = Nms" → just "Name" - const colonIdx = name.indexOf(':'); - if (colonIdx > 0) { name = name.substring(0, colonIdx).trim(); } - const commaIdx = name.indexOf(','); - if (commaIdx > 0) { name = name.substring(0, commaIdx).trim(); } - if (durationMs > 0 && name.length > 3) { - const existing = analyzerMap.get(name); - if (existing) { - existing.durationMs += durationMs; - existing.count++; - } else { - analyzerMap.set(name, { durationMs, count: 1 }); - } + this.extractAnalyzerTiming(msg, entry.nodeType, analyzerMap); + } + + // Process text lines (new AITools.BinlogMcp format) + if (entries.length === 0 && result.text && !result.text.startsWith('No results')) { + for (const line of result.text.split('\n')) { + const msgMatch = line.match(/^\s*\[(\w+)\]\s*(.+)/); + if (msgMatch) { + this.extractAnalyzerTiming(msgMatch[2].trim(), msgMatch[1], analyzerMap); } } } @@ -989,6 +970,46 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider): void { + // Match individual analyzer timing: "0.176 71 FullAnalyzerName (CA1234)" + const timingMatch = msg.match(/^(\d+\.\d+)\s+\d+\s{2,}(.+)/); + if (timingMatch) { + const seconds = parseFloat(timingMatch[1]); + const name = timingMatch[2].trim(); + if (seconds > 0.001 && name.length > 5 && !name.startsWith('Total')) { + const durationMs = Math.round(seconds * 1000); + const existing = analyzerMap.get(name); + if (existing) { + existing.durationMs += durationMs; + existing.count++; + } else { + analyzerMap.set(name, { durationMs, count: 1 }); + } + } + return; + } + // Match assembly-level summary: "363 ms AssemblyFullName, Version=... : AnalyzerName = 341 ms" + const asmMatch = msg.match(/^(\d+)\s*ms\s{2,}(.+)/); + if (asmMatch && (nodeType === 'Item' || nodeType === undefined)) { + const durationMs = parseInt(asmMatch[1]); + let name = asmMatch[2].trim(); + const colonIdx = name.indexOf(':'); + if (colonIdx > 0) { name = name.substring(0, colonIdx).trim(); } + const commaIdx = name.indexOf(','); + if (commaIdx > 0) { name = name.substring(0, commaIdx).trim(); } + if (durationMs > 0 && name.length > 3) { + const existing = analyzerMap.get(name); + if (existing) { + existing.durationMs += durationMs; + existing.count++; + } else { + analyzerMap.set(name, { durationMs, count: 1 }); + } + } + } + } + private parsePerfItems(text: string, icon: string): TreeNodeData[] { const data = this.tryParseJson(text); const items: TreeNodeData[] = []; From 0961afaa9fa9ea7a459e39377690284d13be6055 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:14:10 +0200 Subject: [PATCH 03/11] Fix search command and remaining format compatibility issues - Add parseSearchResults() helper to handle both JSON array (old) and human-readable text (new) binlog_search responses - Fix binlog.search and binlog.searchLoadAll commands that would show 'no results' despite the new tool finding matches - Parse text search format: [NodeType] message + Project/Target/Task metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/extension.ts | 45 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 41 insertions(+), 4 deletions(-) diff --git a/src/extension.ts b/src/extension.ts index bc302af..18cad8e 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -1151,8 +1151,7 @@ export async function activate(context: vscode.ExtensionContext) { let results: any[] = []; try { - const data = JSON.parse(result.text); - results = Array.isArray(data) ? data : []; + results = parseSearchResults(result.text); } catch { results = []; } @@ -1205,8 +1204,7 @@ export async function activate(context: vscode.ExtensionContext) { }); let page: any[] = []; try { - const data = JSON.parse(result.text); - page = Array.isArray(data) ? data : []; + page = parseSearchResults(result.text); } catch { break; } allResults.push(...page); if (page.length < pageSize) { break; } @@ -2481,6 +2479,45 @@ function getFileName(filePath: string): string { return filePath.split(/[/\\]/).pop() || filePath; } +/** + * Parse binlog_search results from either JSON array (BinlogInsights) or + * human-readable text (AITools.BinlogMcp) into a uniform array of objects. + */ +function parseSearchResults(text: string): any[] { + // Try JSON first + try { + const data = JSON.parse(text); + if (Array.isArray(data)) { return data; } + } catch { /* not JSON */ } + + // Parse AITools.BinlogMcp text format: + // Search 'query': N results + // [NodeType] Message text... + // Project: file.csproj Target: Name Task: Name + if (text.startsWith('No results')) { return []; } + const results: any[] = []; + const lines = text.split('\n'); + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + const entryMatch = line.match(/^\s*\[(\w+)\]\s*(.+)/); + if (!entryMatch) { continue; } + const entry: any = { nodeType: entryMatch[1], message: entryMatch[2].trim() }; + // Check next line for Project/Target/Task metadata + if (i + 1 < lines.length) { + const meta = lines[i + 1]; + const projMatch = meta.match(/Project:\s*(\S+)/); + if (projMatch) { entry.projectFile = projMatch[1]; } + const targetMatch = meta.match(/Target:\s*(\S+)/); + if (targetMatch) { entry.targetName = targetMatch[1]; } + const taskMatch = meta.match(/Task:\s*(\S+)/); + if (taskMatch) { entry.taskName = taskMatch[1]; } + if (projMatch || targetMatch || taskMatch) { i++; } + } + results.push(entry); + } + return results; +} + export function deactivate() { diagnosticsProvider?.dispose(); mcpClient?.dispose(); From abc20439181225002a79a19c20b44cf0eb255ec0 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:19:17 +0200 Subject: [PATCH 04/11] Show consistent unique project count in overview The binlog_overview tool reports total project evaluations (including restore and multi-targeting duplicates), while the tree view deduplicates by filename. This caused the /summary chat to show e.g. '56 projects' while the tree showed '22'. Fix: binlog_lm_overview now appends the deduplicated project count from binlog_projects to the overview text, so the model reports the unique count that matches the tree view. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/languageModelTools.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/languageModelTools.ts b/src/languageModelTools.ts index 8c0b911..271a6e6 100644 --- a/src/languageModelTools.ts +++ b/src/languageModelTools.ts @@ -143,9 +143,30 @@ class BinlogLmTool implements vscode.LanguageModelTool { try { if (this.mcpToolName) { + let resultText: string; const result = await client.callTool(this.mcpToolName, args); + resultText = result.text || '(empty result)'; + + // Enrich overview with deduplicated project count from binlog_projects + if (this.toolName === 'binlog_lm_overview') { + try { + const projResult = await client.callTool('binlog_projects', args); + const projData = JSON.parse(projResult.text); + if (Array.isArray(projData)) { + const seen = new Set(); + for (const p of projData) { + const fp = (p.fullPath || p.projectFile || '').split(/[/\\]/).pop()?.toLowerCase() || ''; + if (fp) { seen.add(fp); } + } + if (seen.size > 0) { + resultText += `\nUnique projects: ${seen.size} (total evaluations may be higher due to restore/multi-targeting)`; + } + } + } catch { /* non-fatal */ } + } + return new vscode.LanguageModelToolResult([ - new vscode.LanguageModelTextPart(result.text || '(empty result)'), + new vscode.LanguageModelTextPart(resultText), ]); } From 56cedd452fe88530b4371b4eae23a74bae57ff42 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:23:13 +0200 Subject: [PATCH 05/11] Fix evaluations tree: parse text format from AITools.BinlogMcp binlog_evaluations returns human-readable text instead of JSON. Parse the text format '[id=N] path/to/project.csproj (Nms)' into structured data for the tree view. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index 0f688a1..ccfa959 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -1460,7 +1460,23 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider { const file = e.projectFile || e.ProjectFile || ''; From 2baf1ae230fd220acc704eb10c2002120ba9b271 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:26:39 +0200 Subject: [PATCH 06/11] Fix empty project list for solution builds AITools.BinlogMcp's binlog_projects only returns top-level projects (e.g. the .sln or Build.proj), not the individual .csproj files that are built as part of the solution. The old BinlogInsights.Mcp returned all projects recursively. Fix: when binlog_projects returns <= 2 entries, fall back to binlog_evaluations to extract the full list of unique project files. Evaluations contain entries for every project evaluation in the build. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 54 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index ccfa959..3c25da2 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -239,6 +239,60 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = []; + // Parse text format + for (const line of evalResult.text.split('\n')) { + const m = line.match(/^\s*\[id=\d+\]\s*(.+?)\s+\(\d+ms\)/); + if (m) { evalEntries.push({ projectFile: m[1].trim() }); } + } + // Also try JSON format + if (evalEntries.length === 0) { + try { + const evalData = JSON.parse(evalResult.text); + if (Array.isArray(evalData)) { + for (const e of evalData) { + evalEntries.push({ projectFile: e.projectFile || e.fullPath || '' }); + } + } + } catch { /* not JSON */ } + } + // Deduplicate by filename and filter out .sln/.proj wrappers + const seen = new Set(); + const projItems: TreeNodeData[] = []; + for (const e of evalEntries) { + const filePath = e.projectFile; + const fileName = this.extractFileName(filePath).toLowerCase(); + if (!fileName || seen.has(fileName)) { continue; } + if (fileName.endsWith('.sln') || fileName.endsWith('.slnf')) { continue; } + seen.add(fileName); + const dirPath = this.extractDirectory(filePath); + projItems.push({ + kind: 'project', + label: this.extractFileName(filePath), + description: dirPath || undefined, + tooltip: `${filePath}\n\nExpand to see targets, or click to view project details`, + icon: 'package', + projectFile: filePath, + projectId: String(projItems.length), + children: [], + command: { + command: 'binlog.openProjectDetails', + title: 'View Project Details', + arguments: [String(projItems.length), filePath, {}], + }, + }); + } + if (projItems.length > this.projectsCache.length) { + this.projectsCache = projItems; + } + } catch { /* non-fatal */ } + } + this._onDidChangeTreeData.fire(undefined); } From 547175cc872c15e95323e233e4518762a86178b1 Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Fri, 15 May 2026 16:38:19 +0200 Subject: [PATCH 07/11] Remove evaluations fallback for project list The AITools.BinlogMcp ProjectsQuery was fixed to enumerate all projects via VisitAllChildren + ProjectEvaluation nodes, so the extension no longer needs the binlog_evaluations fallback workaround. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 54 ------------------------------------------- 1 file changed, 54 deletions(-) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index 3c25da2..ccfa959 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -239,60 +239,6 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = []; - // Parse text format - for (const line of evalResult.text.split('\n')) { - const m = line.match(/^\s*\[id=\d+\]\s*(.+?)\s+\(\d+ms\)/); - if (m) { evalEntries.push({ projectFile: m[1].trim() }); } - } - // Also try JSON format - if (evalEntries.length === 0) { - try { - const evalData = JSON.parse(evalResult.text); - if (Array.isArray(evalData)) { - for (const e of evalData) { - evalEntries.push({ projectFile: e.projectFile || e.fullPath || '' }); - } - } - } catch { /* not JSON */ } - } - // Deduplicate by filename and filter out .sln/.proj wrappers - const seen = new Set(); - const projItems: TreeNodeData[] = []; - for (const e of evalEntries) { - const filePath = e.projectFile; - const fileName = this.extractFileName(filePath).toLowerCase(); - if (!fileName || seen.has(fileName)) { continue; } - if (fileName.endsWith('.sln') || fileName.endsWith('.slnf')) { continue; } - seen.add(fileName); - const dirPath = this.extractDirectory(filePath); - projItems.push({ - kind: 'project', - label: this.extractFileName(filePath), - description: dirPath || undefined, - tooltip: `${filePath}\n\nExpand to see targets, or click to view project details`, - icon: 'package', - projectFile: filePath, - projectId: String(projItems.length), - children: [], - command: { - command: 'binlog.openProjectDetails', - title: 'View Project Details', - arguments: [String(projItems.length), filePath, {}], - }, - }); - } - if (projItems.length > this.projectsCache.length) { - this.projectsCache = projItems; - } - } catch { /* non-fatal */ } - } - this._onDidChangeTreeData.fire(undefined); } From 1270497f78ee21531e565ce3073b0cd9feae4f8c Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Mon, 18 May 2026 13:02:54 +0200 Subject: [PATCH 08/11] Handle missing binlog_evaluations tool gracefully The binlog_evaluations tool is not registered in current feed versions of AITools.BinlogMcp. Show an informational message instead of an error when the tool is unavailable. Text format parsing is retained for when the tool becomes available. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index ccfa959..bde43c2 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -1459,6 +1459,13 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider Date: Mon, 18 May 2026 13:27:19 +0200 Subject: [PATCH 09/11] Fix: handle Unknown tool error in catch block, not result text The MCP server throws isError=true for unknown tools, which causes McpClient.callTool to throw an exception. The previous check on result.text never ran. Move the 'Unknown tool' detection to the catch block. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index bde43c2..56eddaa 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -1460,12 +1460,6 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider Date: Mon, 18 May 2026 15:55:52 +0200 Subject: [PATCH 10/11] Always inject binlog_file in tree MCP calls The McpClient may have been created with multiple binlog paths and not recreated when one is removed. The tree view's binlogPaths gets updated but McpClient.loadedBinlogs is stale. Always inject binlog_file using the active binlog path to avoid 'requires explicit binlog_file' errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index 56eddaa..ed600c6 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -1633,10 +1633,8 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider 1) { - args.binlog_file = this.binlogPaths[0]; + if (!args.binlog_file && this.binlogPaths.length >= 1) { + args.binlog_file = this.activeBinlogPath || this.binlogPaths[0]; } this.loadingSet.add(parentKind); @@ -1661,8 +1659,8 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = {}): Promise<{ text: string }> { if (!this.mcpClient) { throw new Error('MCP server not connected'); } - if (!args.binlog_file && this.binlogPaths.length > 1) { - args.binlog_file = this.binlogPaths[0]; + if (!args.binlog_file && this.binlogPaths.length >= 1) { + args.binlog_file = this.activeBinlogPath || this.binlogPaths[0]; } return this.mcpClient.callTool(tool, args); } From 132cb8143814c0a4df06c7c7201c8c6abb778cac Mon Sep 17 00:00:00 2001 From: Yuliia Kovalova Date: Tue, 19 May 2026 11:37:49 +0200 Subject: [PATCH 11/11] Fix parameter names for binlog_tasks_in_target and binlog_task_details - binlog_tasks_in_target: send 'target' not 'target_name' - binlog_task_details: use task_id (int) when available, fallback to name-based lookup for older MCP versions - Store taskId on tree items from binlog_tasks_in_target response Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- src/binlogTreeView.ts | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/binlogTreeView.ts b/src/binlogTreeView.ts index ed600c6..55c598c 100644 --- a/src/binlogTreeView.ts +++ b/src/binlogTreeView.ts @@ -74,6 +74,8 @@ interface TreeNodeData { itemType?: string; /** For evaluation nodes: evaluation id from MCP */ evaluationId?: number; + /** For task nodes: task id from MCP */ + taskId?: number; } export class BinlogTreeDataProvider implements vscode.TreeDataProvider { @@ -1139,7 +1141,7 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = { - target_name: targetName, + target: targetName, project: projectName, }; const result = await this.mcpCall('binlog_tasks_in_target', args); @@ -1161,6 +1163,7 @@ export class BinlogTreeDataProvider implements vscode.TreeDataProvider = { - task_name: taskName, - project: projectName, - target_name: element.targetName || '', - }; + const args: Record = {}; + if (element.taskId !== undefined) { + // New MCP: use task_id directly + args.task_id = element.taskId; + } else { + // Legacy fallback: look up by name + const projectName = element.projectFile + ? this.extractFileName(element.projectFile).replace(/\.[^.]+$/, '') + : ''; + args.task_name = taskName; + args.project = projectName; + args.target_name = element.targetName || ''; + } const result = await this.mcpCall('binlog_task_details', args); const items: BinlogTreeItem[] = []; @@ -1878,6 +1886,8 @@ export class BinlogTreeItem extends vscode.TreeItem { itemType?: string; /** For evaluation nodes: evaluation id from MCP */ evaluationId?: number; + /** For task nodes: task id from MCP */ + taskId?: number; constructor(label: string, collapsibleState: vscode.TreeItemCollapsibleState) { super(label, collapsibleState); }