Release notes and version history for Excel MCP Server
All notable changes to ExcelMcp will be documented in this file.
This changelog covers all components:
Screenshot capture now works reliably for non-active sheets and offscreen ranges (#563, #583): screenshot capture-range and capture-sheet could export mostly blank images when the target content lived offscreen or on a non-active sheet. The capture path now normalizes minimized Excel windows, scrolls/selects the target range before CopyPicture, retries paste operations, and creates the temporary export chart at an onscreen origin so Excel exports the actual captured content instead of a white artifact. Added focused regressions for repeated offscreen captures plus direct non-active-sheet and offscreen-range image-content validation.
Procedural VBA hardening across reopened workbooks, MCP, and CLI: Fixed a reopened .xlsm regression where vba run could fail after reopening an existing macro-enabled workbook even though vba list still succeeded. ExcelBatch now keeps macro execution available for explicit VBA operations on reopened .xlsm sessions, and vba run no longer pre-gates on AccessVBOM, preserves late-bound COM invocation, restores AutomationSecurity after explicit execution, and treats missing run parameters as an empty list instead of throwing. Added reopened-workbook Core regressions (list -> run, update -> run, delete -> import -> run), an end-to-end MCP vba run proof on a real .xlsm workbook, and a dedicated CLI transport proof that verifies both workbook side effects and persisted state after reopen.
CLI required-parameter validation now rejects whitespace-only values: The shared required-parameter guard treated whitespace-only strings as valid, allowing inputs like vba run --procedure-name " " to slip through CLI validation and reach Excel COM. Fixed by upgrading the shared validator to reject null, empty, and whitespace-only values, and added a focused CLI regression for whitespace-only procedureName.
vba(action: ‘run’) fails on Office 365 Click-to-Run (#550): vba.run used early-bound PIA call Application.Run() which triggered assembly resolution of Microsoft.Vbe.Interop.dll — a DLL not available on Click-to-Run Office installations without the Visual Studio Office workload. Switched to late-bound COM dispatch via Type.InvokeMember, matching the pattern used by all other VBA operations. Also fixed parameter spreading — multiple macro arguments are now passed as individual COM parameters instead of a single array.
Session startup “Specified cast is not valid” now includes COM diagnostic info (#559): When Activator.CreateInstance succeeds but PIA interface cast fails (typically due to COM registration mismatches on certain Office Click-to-Run configurations), the error message now includes the resolved CLSID, PIA interface GUID, process bitness, and Office install path. This helps diagnose machine-specific COM registration issues without requiring remote debugging.
Enterprise-managed devices: auth/sign-in pop-ups could freeze session startup: On enterprise-managed Windows devices, Excel sometimes shows modal authentication or sign-in dialogs during startup. Because ExcelMcp started Excel hidden, these dialogs were invisible and blocked COM calls indefinitely (SERVERCALL_REJECTED). Fixed with two changes: (1) OleMessageFilter.RetryRejectedCall now retries SERVERCALL_REJECTED responses for up to 120 seconds instead of cancelling immediately, giving users time to interact with auth dialogs. (2) ExcelBatch now starts Excel visible during session open so auth dialogs are interactable, then hides it after all workbooks are loaded if show=false was requested.
Failed session startup could leave a hidden Excel.exe process behind and keep the workbook locked: ExcelBatch created the hidden Excel instance before validating/opening the workbook, but if startup failed early (for example because the workbook was locked) the STA-thread cleanup only looked at the promoted instance fields, not the startup locals. The constructor also surfaced the startup exception before the STA cleanup thread had fully finished. Fixed by cleaning up from startup locals when field promotion never happened and waiting for failed-startup cleanup to complete before rethrowing the session-open error.
Power Query privacy/firewall failures were flattened into generic service errors or hangs instead of surfacing a stable diagnostic: Core now classifies recognized Power Query failures into structured categories such as Privacy, Expression, Connectivity, and Authentication via PowerQueryCommandException. The service, CLI, and MCP layers now preserve errorCategory in their responses, and refresh timeouts on firewall-prone query formulas are reported as likely privacy issues instead of leaving callers blind. Added a privacy-safe synthetic firewall repro in Core, a CLI regression for structured privacy output, and real verification against a representative real-world workbook/query scenario.
Synchronous COM refresh follow-up stability (#544): powerquery update still used a standalone synchronous refresh path while related Data Model and DAX-backed table refresh operations continued to rely on callback-sensitive COM patterns. Fixed by routing powerquery update through the shared COM-safe refresh helper, replacing EnterLongOperation() in Data Model refresh with pending-cancellation handling, and wrapping DAX table refresh calls with the same OleMessageFilter.SetPendingCancellationToken(...) pattern. Added both Core and MCP regression coverage for powerquery update, and the full Power Query feature slice now passes locally.
LLM integration test framework overhaul: Migrated llm-tests/ from pytest-aitest to pytest-skill-engineering with a clean full rewrite and no backward-compatibility shim. The new harness uses CopilotEval for both CLI and MCP workflows, enforces GitHub Copilot authentication checks, and standardizes explicit timeout and turn limits. All LLM workflow scenarios were rewritten around natural-language prompts and outcome-focused assertions, with active docs and instructions updated to match the new framework and workflow.
Power Query stability diagnostics (#560): Added DIAG traces throughout the shutdown/dispose/PQ refresh paths (gated by EXCELMCP_DIAGNOSTICS=1 environment variable) to aid future debugging of intermittent MashupContainer.Loader.exe crashes. New SessionDiagnostics helper class for conditional diagnostic output.
CLI backward-compatibility aliases: Added --sheet and --range short aliases in the CLI source generator for backward compatibility with pre-generator parameter names (--sheet-name and --range-address remain primary).
Crash isolation test infrastructure (#560): Added ExcelCrashIsolationTests (4 experiments isolating MashupContainer crash residue and connection property defaults), PowerQuerySerialWorkflowRegressionTests (two-pass serial PQ refresh workflow), ParameterAliasBackwardCompatTests, and supporting fixtures (CliPowerQueryWorkflowFixture, enhanced CliProcessHelper).
MCP tool cancellation could leave the in-process server wedged until Excel was killed manually: The MCP ServiceBridge created timeout and cancellation tokens but never applied them to the in-process service call, and tool methods did not flow request cancellation into the bridge. When VS Code cancelled a long-running tool call, the Excel COM work could continue on a blocked batch thread while the poisoned session remained in the server, making subsequent requests appear hung. Fixed by running bridge dispatch on a separate task, force-closing the affected session or resetting the service on timeout/cancellation, and propagating request cancellation from MCP tool methods through the shared tool base into the bridge.
Remaining synchronous Power Query and connection load paths could still deadlock despite the earlier refresh fix: powerquery evaluate, powerquery load-to, powerquery create load destinations, and connection worksheet/data refreshes still wrapped QueryTable.Refresh(false) or connection.Refresh() in EnterLongOperation(). That reused the same callback-rejection pattern that previously deadlocked normal Power Query refresh: Excel and MashupHost could not deliver the inbound COM callbacks needed to complete the synchronous load. Fixed by removing EnterLongOperation() from those paths too and switching them to the same OleMessageFilter.SetPendingCancellationToken(...) pattern used by the repaired Power Query refresh implementation.
powerquery refresh could hang indefinitely (permanent COM deadlock): EnterLongOperation was called before QueryTable.Refresh(false) and connection.Refresh() in the PowerQuery refresh path. EnterLongOperation sets _isInLongOperation=true, causing HandleInComingCall to return SERVERCALL_RETRYLATER for ALL inbound COM calls — including essential MashupHost callbacks Excel needs to complete the synchronous refresh. This created a permanent mutual deadlock (observed: 30-minute hang in production on a worksheet-loaded query). Fixed by removing EnterLongOperation from both refresh paths and registering a CancellationToken with OleMessageFilter so MessagePending returns PENDINGMSG_CANCELCALL when the token fires, enabling clean STA thread exit. Trade-off: Elevated CPU (~88%) during refresh is accepted as preferable to a permanent hang. The CPU spin regression tests (PowerQueryRefreshCpuSpinTests) are now intentionally expected to fail — they are excluded from CI via RunType=OnDemand and updated to document this known trade-off.
Structural COM stability improvements: Addressed root cause of intermittent operation hangs and orphan Excel processes. ExcelBatch.Execute() now automatically suppresses ScreenUpdating via a new ExcelWriteGuard, reducing COM callbacks and improving bulk operation performance. Unified multi-workbook shutdown to use the resilient ExcelShutdownService (was bare COM calls without retry). Added retry logic to workbook Save (for file locks) and Close (for COM busy errors). Excel process ID capture now retries 3 times with 500ms delay to prevent force-kill from being permanently disabled under load. Added safety-net ProcessExit handler that kills tracked Excel processes on unexpected .NET process termination.
Operation hangs with error handling improvements: Hardened the service dispatch layer so that cleanup failures during error handling no longer propagate secondary exceptions. Dead Excel sessions are now automatically detected and cleaned up in all exception paths. Power Query Evaluate Refresh() is now wrapped with EnterLongOperation to prevent CPU spin during M code evaluation. Added cancellation checks to COM collection loops for large workbooks.
PivotTable test suite no longer hangs: Fixed a test fixture deadlock caused by using both IClassFixture and a collection fixture on the same test class, which created concurrent Excel sessions that deadlocked. Consolidated to a single shared fixture — all 102 PivotTable tests now pass reliably.
range set-formulas and range get-formulas injected @ implicit intersection operator inside Excel Tables: The legacy Range.Formula COM property automatically prepends @ to formulas inside structured tables, causing #FIELD! errors with custom functions that return entity cards (e.g., Office Add-in rich data types). Switched to Range.Formula2 (Excel 365+) which respects dynamic array semantics and does not inject @.
Connection refresh and PowerQuery refresh / refresh-all could hang or miss cancellation on async data sources: WorkbookConnection.Refresh() returns immediately when the provider runs asynchronously, leaving the STA thread without a way to detect completion or honour the operation timeout. Both Connection and PowerQuery refresh now set the sub-connection’s BackgroundQuery = true, call Refresh(), then poll .Refreshing in a loop that responds to cancellation and calls .CancelRefresh() when the timeout fires. powerquery refresh-all was also updated to use the same robust RefreshConnectionByQueryName path (which includes QueryTable.Refresh(false) for worksheet queries) instead of a bare connection.Refresh().
CLI and MCP Server version always reported as 1.0.0 (#523): The update check and About dialog always showed version 1.0.0 instead of the actual installed version. Fixed by removing hardcoded version properties from project files so they inherit from the central version configuration.
table append JsonElement COM marshalling (#519): Row values containing booleans or strings were passed as raw System.Text.Json.JsonElement to cell.Value2, which COM interop cannot marshal to a Variant. Fixed by calling RangeHelpers.ConvertToCellValue() (the same fix already present in range set-values) to unwrap JsonElement to native types before assignment.
--values/--rows inline JSON: PowerShell quote-stripping + stdin sentinel (#521): Windows CreateProcess strips inner double-quotes when PowerShell passes arguments to native executables, so --values '[["ACD Full Term",0.26]]' arrives as [[ACD Full Term,0.26]] (invalid JSON). The generated DeserializeNestedCollection<T> now: (1) emits a clear error message that mentions --values-file and --values - as workarounds, and (2) supports a stdin sentinel — passing --values - (or --rows -) reads the JSON from Console.In, avoiding shell quoting entirely.
Table add-to-data-model bracket column names block DAX formulas: Excel table columns with literal bracket characters in their names (e.g., from OLEDB import sources) cannot be referenced in DAX formulas after being added to the Data Model. Added new stripBracketColumnNames parameter (default: false). When false, bracket column names are reported in bracketColumnsFound so users are aware of the issue. When true, the source table column headers are renamed (brackets removed) before adding to the Data Model, enabling full DAX access. The add-to-data-model result now includes bracketColumnsFound and bracketColumnsRenamed fields.
PowerQuery load-to data-model silently succeeded without loading data: powerquery load-to with data-model destination returned success: true but the table never appeared in the Power Pivot Data Model. The connection was registered via Connections.Add2() but connection.Refresh() was never called, so data was not actually loaded. Fixed by calling connection.Refresh() after creating the connection, consistent with how load-to worksheet works.
chartconfig set-data-labels threw raw COMException on Line charts with bar-only position: Setting labelPosition to InsideEnd, InsideBase, or OutsideEnd on a Line chart threw a raw COM exception with no user-friendly explanation. These positions are only valid for bar, column, and area chart types. Fixed by catching the COMException and throwing an InvalidOperationException with a descriptive message explaining which chart types support each position, consistent with how ShowPercentage handles unsupported chart types.
rangeformat format-range parameter documentation listed wrong valid values for borderStyle: The borderStyle parameter help incorrectly listed thin, medium, thick, dashed, and dotted as valid values — those are borderWeight values. The valid borderStyle values are continuous, dash, dot, dashdot, dashdotdot, double, slantdashdot, and none. Documentation corrected.
rangeformat format-range rejected middle as a vertical alignment value: The verticalAlignment parameter only accepted center but not the common alias middle. Both now accepted and produce identical center-vertical alignment.
screenshot CLI --output flag documentation clarified: The --output <path> flag saves the screenshot directly to a PNG or JPEG file instead of printing base64 JSON to stdout. This was already functional but was documented as “For CLI: saved to file” without explaining that --output is required to save to a file.
office.dll not found when opening workbooks with connections/data model (#487 follow-up): The AssemblyResolve handler only searched AppContext.BaseDirectory for office.dll. In NuGet-installed tool deployments, office.dll is never copied there (it is only present in local dev builds via Directory.Build.targets). Opening workbooks with external connections, Power Query, or a Data Model triggered code paths that caused the CLR to load Microsoft.Office.Interop.Excel.dll, which in turn requested office.dll v16. The handler returned null → FileNotFoundException. Fixed by adding fallback search order: (1) AppContext.BaseDirectory, (2) .NET Framework GAC v16, (3) GAC v15 (accepted by CLR as substitute), (4) Office 365 click-to-run installation directories. Directory.Build.targets also updated to prefer v16 GAC when available.
Microsoft.Office.Interop.Excel types for improved reliability and compile-time error detection. Power Query APIs (Workbook.Queries) and VBA project access remain as dynamic calls where PIA coverage is unavailable.All Excel sessions crashed with FileNotFoundException for office.dll (#487): After PIA migration, ExcelBatch STA thread declared tempExcel as typed Excel.Application. Casting a typed COM interop object to (dynamic) retains PIA type metadata; the DLR then resolved MsoAutomationSecurity from office.dll (Microsoft.Office.Core v16.0.0.0) at runtime, which is not bundled with the deployed .NET tool. Every session (create and open) crashed before opening any workbook. Fixed by casting to (object) first before (dynamic) to force pure IDispatch binding. Also removed a broken <Reference> to office.dll with a wrong v15.0.0.0 hint path (runtime required v16.0.0.0).
OleMessageFilter.MessagePending was returning 2 (PENDINGMSG_WAITNOPROCESS) instead of 1 (PENDINGMSG_WAITDEFPROCESS). When Excel fires a re-entrant callback (e.g. Calculate/SheetChange event) during a FormatConditions.Add() call, WAITNOPROCESS blocked COM from delivering the callback — Excel waited for the callback while the STA thread waited for Excel, causing a permanent deadlock. Any operation that triggers Excel’s internal event loop (conditional formatting on formula cells, PivotTable refresh, Power Query refresh) was affected. Fixed by returning 1 so COM delivers pending inbound calls during the outgoing IDispatch.Invoke.IDispatch.Invoke, WithSessionAsync had no catch (OperationCanceledException) handler — the session remained alive with a permanently blocked STA thread, causing all subsequent operations to hang. Fixed by adding catch (OperationCanceledException) that force-closes the session (same pattern as the existing TimeoutException handler).Slow Fail on Successive Calls After Timeout/Cancellation: After a timeout or cancellation, Execute<T> would queue new work on a permanently stuck STA thread, forcing each subsequent caller to wait for its own full timeout before failing. Fixed by adding a fail-fast pre-check: if _operationTimedOut is set, throw TimeoutException immediately.
Task.Run(() => workbook.Save()) in ExcelShutdownService — this marshalled the COM call from the STA thread to an MTA thread-pool thread, which is incorrect and fragile in .NET 8+. Save is now called directly on the STA thread, which is always the case inside ExcelBatch.Execute().ExcelBatch. When the Hwnd path fails, force-kill is now disabled with a warning rather than risking killing an unrelated Excel workbook the user has open.Thread.Sleep in Dispose (#482): Removed 100 ms Thread.Sleep from ExcelBatch.Dispose(). The preceding _shutdownCts.Cancel() call immediately wakes the STA thread from WaitToReadAsync, making the sleep redundant and adding unnecessary latency.ExcelMcpService top-level catch blocks now return "{ExType}: {ex.Message}" instead of just ex.Message, making unexpected failures distinguishable without a full stack trace.WaitForSingleObject; ExcelMcpService catches TimeoutException to prevent unhandled exceptionsIConfiguration reload-on-change in MCP Server to prevent 85%+ CPU usage from FileSystemWatcher pollingProcess object not being disposed in ExcelBatch.ForceKillExcelProcess()ServiceInfoExtractor that could lose type information across partial interfacestype → trendlineType in IChartConfigCommands to avoid COM parameter ambiguitySetStyle error message to show valid range when styleId is out of boundsInvalidOperationException in chart appearance commandsFile.Copy() instead of creating individual files via COM, eliminating ~74 redundant Excel sessionsquality parameter on screenshot tool (High/Medium/Low). Default is Medium (JPEG at 75% scale, ~4–8x smaller than original PNG). Use High (PNG, full scale) when fine text needs careful inspection, Low (JPEG at 50% scale) for layout overviews.window tool with 9 operations to control Excel window visibility, position, state, and status bar — enabling “Agent Mode” where users watch AI work in Excel
show / hide — Toggle Excel visibility (syncs with session metadata)bring-to-front — Bring Excel to foregroundget-info — Query window state (visibility, position, size, foreground status)set-state — Set normal / minimized / maximizedset-position — Set window left, top, width, heightarrange — Preset layouts: left-half, right-half, top-half, bottom-half, center, full-screenset-status-bar — Display live operation status text in Excel’s status barclear-status-bar — Restore default status bar text--output flag for all commands: Save command output directly to a file. Screenshot commands automatically save decoded PNG images instead of base64 JSONexcelcli batch command executes multiple CLI commands from a JSON file in a single process launch
session.open/session.create, auto-clear on session.close--stop-on-error flag to halt on first failure (default: continue all)--help crash (#463): Fixed Spectre.Console markup crash when parameter descriptions contain [/] characters (e.g., [A1 notation])mcpTool ?? "unknown" fallback; added HasMcpToolAttribute to correctly filter MCP-only toolsconditionalformat.md and slicer.md reference filesCO_E_SERVER_EXEC_FAILURE, RPC_E_CALL_FAILED) during Excel process startup under resource constraintsSbroenne.ExcelMcp.CLI alongside MCP Server
net10.0 → net10.0-windows) and formatting errors in build workflowSee BREAKING-CHANGES.md for complete migration guide.
LLMs pick up these changes automatically via tools/list (MCP) and --help (CLI).
excel_ prefix from all 23 MCP tool names (e.g., excel_range → range, excel_file → file). Titles also shortened (e.g., "Chart Operations"). VS Code extension server name → excel-mcp.calculation_mode tool/CLI command (automatic, manual, semi-automatic modes; workbook/sheet/range scopes)npx add-mcp as primary installation method in docsexcel_ prefix from prompt nameschatSkills contribution pointnet10.0-windowsDockerfile, glama.json, .dockerignore, docs)RefreshTable() called after each field operation triggered synchronous Analysis Services queriespivottable(refresh) explicitly to update visual display after configuring fieldssuccess, errorMessage, filePath)[JsonPropertyName] attributes from model filesconnection set-properties: Added description, backgroundQuery, savePassword, refreshPeriodpowerquery create/load-to: Added targetSheet, targetCellAddresschart create-* and move: Added left, top, width, heighttable append: Fixed to parse CSV into proper rows formatvba run: Added timeoutSecondscheck-cli-settings-usage.ps1) to prevent future occurrencesSessionManager never checked if Excel process was alive, leaving dead sessions in dictionaryGetSession(), GetActiveSessions(), and IsSessionAlive() now check process health and auto-cleanupExcelBatch.Execute() validates Excel is alive before queueing operationsSessionManager)SetFormulas, SetValues, Table.Append, NamedRange.Write=INDEX(KPIs[Total_ACR],1) now work without “The operation was canceled” errorrefresh action returned success: true even when Power Query had formula errors
Connection.Refresh() silently swallows errors for worksheet queries (InModel=false)QueryTable.Refresh(false) for worksheet queries which properly throws errorsConnection.Refresh() which does throw errors"[Expression.Error] The name 'Source' wasn't recognized..."table create --range A1 created single-cell table
ListObjects.Add() doesn’t auto-expand from a single cellRange.CurrentRegion when single cell provided, capturing all contiguous dataevaluate action to execute M code directly and return results
excelcli powerquery evaluate --file data.xlsx --mcode "let Source = #table({\"Name\",...})"mCodeFile parameter on powerquery tool for create, update, evaluate actionsmCode and mCodeFile providedvbaCodeFile parameter on vba tool for create-module, update-module actionsvbaCode and vbaCodeFile provided--session parameter (was positional in some commands)--help descriptions on all commands synced with MCP tool descriptions--file parameters support both new file creation and existing filesexcelcli list-actions command to discover all available operations-q/--quiet flag suppresses banner for agent-friendly JSON-only output
Version Check: excelcli version --check queries NuGet to show if update available
--save flag for atomic save-and-close workflow
check-cli-action-coverage.ps1 script
timeoutSeconds parameter on file(open) and file(create) actionsTimeoutExceptioncreate-and-open to simpler create action
unload action removes data from all load destinations
skills/excel-cli/ - CLI-specific skill with commands referenceskills/excel-mcp/ - MCP Server skill with tools referenceskills/shared/ - Shared workflows, anti-patterns, behavioral rules--timeout <seconds> (was --timeout-seconds)--session parameterslicer tool for interactive filtering
execute-dmv action on datamodel toolevaluate action on datamodel tool for ad-hoc DAX queriescreate-from-dax, update-dax, get-dax actionsrename action for Power Query queriesrename-table action for Data Model tablesisError signaling for tool execution failuresRefreshTable() callscopy-to-file and move-to-file actionsCreateFromPivotTable now works with OLAP/Data Model PivotTablesshowExcel: true to watch AI changes liveget-data action returns table rowsdelete actionmove actionset-load-to-data-model failuresexcel_querytable toolcreate action