You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
screenshot capture and capture-sheet fail with a bare COMException: 0x800A03EC when the target worksheet is protected. Unprotecting the sheet and repeating the same call succeeds — protection alone is the trigger, not the range, not the workbook, not whether Excel is visible.
v1.10.6 made this easy to reach by adding worksheet protection to the server. A caller can now protect a sheet and then be unable to capture it in the same session, with an error that doesn't explain itself. I'd expect the capture behaviour predates v1.10.6, but I only tested v1.10.7, so I can't call it a regression either way.
I searched existing issues for "screenshot", "protected worksheet" and "0x800A03EC" and didn't find a duplicate. #563 and #583 are the closest, and both are closed. #563 does list 0x800A03EC among its symptoms — there it was occasional, tied to particular ranges of a real .xlsm on v1.8.33, and mixed in with blank and junk images. This one is deterministic on a two-cell workbook and switches on and off with sheet protection alone.
Component
CLI (Command-line interface)
Core Library (Shared functionality)
Command/Usage
For CLI (verbatim, v1.10.7, <id> is the sessionId from the first command):
For MCP Server: tool screenshot, actions capture and capture-sheet, with worksheet_style set-protection to put the sheet in that state. Both entry points reach the same ScreenshotCommands path, but I reproduced this through the CLI: the desktop client on this machine still has the v1.10.5 server loaded in memory, so I have not re-run it over MCP on v1.10.7. Happy to do that after a restart if it matters.
Expected Behavior
Capturing a protected worksheet returns an image, the way it does when the sheet is unprotected. Protection restricts editing, and other read paths keep working while it is on: range get-values and conditionalformat list-worksheet-rules both returned normally on the same protected sheet.
If that isn't feasible, an error naming sheet protection would be enough. The current message gives the caller nothing to act on.
Actual Behavior
No image is returned. The session stays usable, and the next call after unprotecting succeeds.
capture-sheet returns the same with "command": "screenshot.capture-sheet".
Boundary matrix
All on v1.10.7, same workbook, same range:
Call
Result
capture, sheet unprotected
✅ image returned
capture, sheet protected
❌ COMException: 0x800A03EC
capture-sheet, sheet protected
❌ same
capture, after unprotecting
✅ image returned
capture, protected, Excel window shown via window show
❌ same
capture, unprotected, Excel window hidden
✅ image returned
The last two rows are there because I first assumed this was the known "CopyPicture needs a visible Excel" behavior. Visibility makes no difference in either direction, which matches the code: ExportRangeAsImage forces app.Visible = true before capturing and restores the previous state in its finally block, so session-level visibility cannot reach this failure.
Root cause
The export path creates a temporary ChartObject on the target worksheet:
ChartObjects.Add is what Excel refuses on a protected sheet. Probing the calls directly through COM on this machine, with the sheet protected by Worksheet.Protect() with no arguments, which is what SetProtection calls:
Direct COM call on the protected sheet
Result
Worksheet.ChartObjects().Add(0, 0, 150, 60)
0x800A03EC
Worksheet.Shapes.AddShape(...)
0x800A03EC
Range.CopyPicture(1, -4147) (xlScreen/xlPicture, the first mode the retry ladder tries)
returns
Range.Copy() (the final fallback)
returns
The DrawingObjects argument of Protect does not govern this. With Protect(DrawingObjects:=False), where ProtectDrawingObjects reads back False, Shapes.AddShape succeeds on the protected sheet while ChartObjects.Add still raises 0x800A03EC. Protect(UserInterfaceOnly:=True) is not an escape hatch either. ProtectContents looks like the whole trigger — convenient, since that is the property GetProtection already reads:
Two things point away from CopyPicture being the throw site, independently of that probing:
CopyPictureWithRetry cannot emit a bare COMException. It swallows every one across four appearance/format modes and ten attempts, then across ten more Range.Copy() attempts, and finally throws an InvalidOperationException with a descriptive message. A raw HRESULT reaching the caller means the failure happened outside that method.
$"Excel could not capture the range after {CopyPictureMaxRetries} attempts because CopyPicture and the range copy fallback kept failing. "+
"Excel may still be rendering, busy, minimized, or unable to access the clipboard. "+
"Retry the screenshot after the workbook finishes refreshing, or capture a smaller range.",
lastException);
}
The timing agrees. Failing calls returned in 3.7 s against 4.6 s for successful captures — exhausting the CopyPicture ladder would take over 30 s in sleeps alone before its InvalidOperationException.
Host the temporary chart off the target sheet, on a scratch worksheet or a temporary chart sheet. I have not verified whether the paste and the export survive that move, so this is a direction rather than a tested one.
Unprotect and restore around the export. A password-protected sheet can't be restored the same way, so this one would need a guard.
Which of these fits depends on how you want protection treated elsewhere in the server.
Environment
Windows Version: Windows 11 Pro (10.0.26200)
Excel Version: Microsoft 365 (Click-to-Run)
ExcelMcp Version: v1.10.7
.NET Version: standalone executables, no separate .NET runtime installed on this machine
Installation Method: Binary download, standalone excelcli.exe and mcp-excel.exe from the GitHub Release zips
File Format: .xlsx
AI Assistant (if using MCP Server): Claude Desktop
Sample File
Reproduced on a workbook created by session create with two cells set and nothing else — nothing useful to attach, but I can put one together if it helps.
The v1.10.6 drawing, page setup and PDF export actions let me drop a win32com detour I had been carrying for a while. Happy to retest after a fix.
Bug Description
screenshot captureandcapture-sheetfail with a bareCOMException: 0x800A03ECwhen the target worksheet is protected. Unprotecting the sheet and repeating the same call succeeds — protection alone is the trigger, not the range, not the workbook, not whether Excel is visible.v1.10.6 made this easy to reach by adding worksheet protection to the server. A caller can now protect a sheet and then be unable to capture it in the same session, with an error that doesn't explain itself. I'd expect the capture behaviour predates v1.10.6, but I only tested v1.10.7, so I can't call it a regression either way.
I searched existing issues for "screenshot", "protected worksheet" and "0x800A03EC" and didn't find a duplicate. #563 and #583 are the closest, and both are closed. #563 does list
0x800A03ECamong its symptoms — there it was occasional, tied to particular ranges of a real.xlsmon v1.8.33, and mixed in with blank and junk images. This one is deterministic on a two-cell workbook and switches on and off with sheet protection alone.Component
Command/Usage
For CLI (verbatim, v1.10.7,
<id>is the sessionId from the first command):For MCP Server: tool
screenshot, actionscaptureandcapture-sheet, withworksheet_style set-protectionto put the sheet in that state. Both entry points reach the sameScreenshotCommandspath, but I reproduced this through the CLI: the desktop client on this machine still has the v1.10.5 server loaded in memory, so I have not re-run it over MCP on v1.10.7. Happy to do that after a restart if it matters.Expected Behavior
Capturing a protected worksheet returns an image, the way it does when the sheet is unprotected. Protection restricts editing, and other read paths keep working while it is on:
range get-valuesandconditionalformat list-worksheet-rulesboth returned normally on the same protected sheet.If that isn't feasible, an error naming sheet protection would be enough. The current message gives the caller nothing to act on.
Actual Behavior
No image is returned. The session stays usable, and the next call after unprotecting succeeds.
Error Message
{ "success": false, "error": "COMException: 0x800A03EC", "errorMessage": "COMException: 0x800A03EC", "errorCategory": "ComInterop", "command": "screenshot.capture", "sessionId": "<redacted>", "isError": true, "exceptionType": "COMException", "hresult": "0x800A03EC" }capture-sheetreturns the same with"command": "screenshot.capture-sheet".Boundary matrix
All on v1.10.7, same workbook, same range:
capture, sheet unprotectedcapture, sheet protectedCOMException: 0x800A03ECcapture-sheet, sheet protectedcapture, after unprotectingcapture, protected, Excel window shown viawindow showcapture, unprotected, Excel window hiddenThe last two rows are there because I first assumed this was the known "CopyPicture needs a visible Excel" behavior. Visibility makes no difference in either direction, which matches the code:
ExportRangeAsImageforcesapp.Visible = truebefore capturing and restores the previous state in itsfinallyblock, so session-level visibility cannot reach this failure.Root cause
The export path creates a temporary
ChartObjecton the target worksheet:mcp-server-excel/src/ExcelMcp.Core/Commands/Screenshot/ScreenshotCommands.cs
Lines 196 to 217 in d526b22
ChartObjects.Addis what Excel refuses on a protected sheet. Probing the calls directly through COM on this machine, with the sheet protected byWorksheet.Protect()with no arguments, which is whatSetProtectioncalls:Worksheet.ChartObjects().Add(0, 0, 150, 60)0x800A03ECWorksheet.Shapes.AddShape(...)0x800A03ECRange.CopyPicture(1, -4147)(xlScreen/xlPicture, the first mode the retry ladder tries)Range.Copy()(the final fallback)The
DrawingObjectsargument ofProtectdoes not govern this. WithProtect(DrawingObjects:=False), whereProtectDrawingObjectsreads backFalse,Shapes.AddShapesucceeds on the protected sheet whileChartObjects.Addstill raises0x800A03EC.Protect(UserInterfaceOnly:=True)is not an escape hatch either.ProtectContentslooks like the whole trigger — convenient, since that is the propertyGetProtectionalready reads:mcp-server-excel/src/ExcelMcp.Core/Commands/Sheet/SheetCommands.Protection.cs
Lines 27 to 37 in d526b22
Two things point away from
CopyPicturebeing the throw site, independently of that probing:CopyPictureWithRetrycannot emit a bareCOMException. It swallows every one across four appearance/format modes and ten attempts, then across ten moreRange.Copy()attempts, and finally throws anInvalidOperationExceptionwith a descriptive message. A raw HRESULT reaching the caller means the failure happened outside that method.mcp-server-excel/src/ExcelMcp.Core/Commands/Screenshot/ScreenshotCommands.cs
Lines 275 to 333 in d526b22
InvalidOperationException.Directions, smallest change first:
Which of these fits depends on how you want protection treated elsewhere in the server.
Environment
excelcli.exeandmcp-excel.exefrom the GitHub Release zipsSample File
Reproduced on a workbook created by
session createwith two cells set and nothing else — nothing useful to attach, but I can put one together if it helps.The v1.10.6 drawing, page setup and PDF export actions let me drop a win32com detour I had been carrying for a while. Happy to retest after a fix.