# KitchenSink4XL > Excel (.xlsx and .xlsm) MCP server for agents. 69 operations in 7 tools (131 counting each action), ~5.5k tokens for the whole tool list, everything available from the first message. stdio. Every mutation is preceded by an automatic backup and committed by an atomic verified save. Every value a read returns carries a label saying whether it is current. Ambiguous targets are refused with every candidate listed instead of guessed. Release: v2.0.0 MCP registry name: io.github.KitchenSink4AI/kitchensink4xl Transport: stdio. Platform: the file tools need no Excel installation and run on Windows and Linux (macOS is untested in this release). The `xl_app` tool requires Windows with Microsoft Excel installed for its Excel operations; it drives a private hidden Excel instance and never touches the user's open session. Its `update_now` and `update_notices` operations need neither. This document is written for the agent that will call these tools. Every line below is a behavior you can rely on without testing it first. ## 1. How the surface works The server lists seven tools: `xl_read`, `xl_edit`, `xl_format`, `xl_structure`, `xl_workbook`, `xl_app` and `xl_help`. Each tool except `xl_help` takes `{op, args}`: `op` names an operation and `args` holds its arguments, including `path` (the workbook file) unless the operation names other paths. Call `xl_help` with an `op` to get that operation's arguments and a worked example, or with `topic='location'` for the location object. Every operation has a curated example. - All 69 operations are available from the first message. There is nothing to enable and no tool-set switching. - About 5.5k tokens (about 5,490 approximate tokens, measured over real stdio by `scripts/measure_surface.py`) cover the whole tool list; the server's own instructions add about 500. Per tool, operations and tokens: `xl_read` 18 / 1,038, `xl_edit` 10 / 832, `xl_format` 11 / 900, `xl_structure` 9 / 768, `xl_workbook` 9 / 814, `xl_app` 12 / 926, `xl_help` 0 / 212. - Routing is lenient and meaning is strict: an operation sent to the wrong tool is rerouted when that is safe, a wrong shape is accepted when the intent is clear, and a value that means something different from what you asked is refused. Values are snake_case (`greater_than`, `last_7_days`, `text_length`). - Reads are token-shaped on purpose: `get_grid_view` projects a sheet instead of dumping it, and `query_range` filters, groups and aggregates SERVER-SIDE so rows never cross the wire. Prefer both over `read_range` on anything large. - Clients that defer tool schemas until first call pay close to zero at startup. - Launch settings can narrow the surface. `KS4XL_READ_ONLY=1` lists only the operations that read. `KS4XL_FORBID` removes named tools or operations from the list and from help; `KS4XL_FORBID=xl_app` closes every route that starts Excel, `verify_com` and `KS4XL_VERIFY_COM` included. A call refused because of a launch setting returns `NOT_AVAILABLE`, and no tool call can undo it. What is removed no longer counts toward the token figure. - Settings from earlier versions are handled for you: an old default (`KS4XL_MODE` set to lite, full or everything, or `KS4XL_PACK_POLICY=auto`) is ignored with a one-line notice, and a setting that locked the tools (`KS4XL_PACK_POLICY=locked` or a `KS4XL_MODE` list of tool groups) stops the start with a message naming what to use instead. ## 2. The calculation contract An .xlsx file stores each formula cell's value as of the last time Excel calculated it. No pure-Python engine recomputes it. This server therefore never returns a bare number: every returned cell value carries a label from a closed five-value vocabulary. - `value` a plain literal. No formula behind it. Nothing to be stale. - `cached` a formula cell, carrying the value a prior real calculation left in the file. It may be stale. - `calculated` a formula cell this session actually recalculated. Produced by `recalculate` (`xl_app`), never inferred. - `absent` a formula cell with NO cached value at all, common in files written by other tools. It is not blank, and it is never reported as blank. - `formula` the read returned the formula string rather than a value, because that is what was asked for. Behaviors that follow: - Reads name the affected addresses when anything they touch is `cached` or `absent`. You get the cells, not just a warning. - Aggregations state which cells a filter skipped, so a total is never quietly computed over fewer rows than you think. - `recalculate` asks Excel itself to compute; its values come back `calculated`. That is the only route to `calculated`. - A file-tier write cannot recompute anything downstream of itself. An edit names which formula results may now be stale, including results reached through LAMBDA names, relative names and what-if data tables, and sets recalculate-on-open. `read_range`, `get_cells`, `query_range`, `export_range`, `export_file`, `get_grid_view` and `find_cells` carry a `cached_note` when the workbook is flagged for recalculation and the read used cached results. - Formula detection is by cell TYPE, not by a leading `=`. Text that merely looks like a formula is never re-armed as a formula on copy, move, sort, or reference rewrite. - A formula that returns an array (`UNIQUE`, `FILTER`, `SORT`, `SEQUENCE` and the other array-returning functions) is written as a dynamic array and spills in Excel. A bare range expression such as `=A1:A5*2` is still written as a legacy formula. ## 3. Behavioral contracts - Backup before mutation: every file mutation first rotates the current content into two bounded slots in a hidden `.ks4xl-backups/` folder beside the workbook. `manage_backups` lists, restores, purges, and snapshots them. Storage stays bounded no matter how many edits a session makes. A destructive call also keeps a labeled snapshot of the workbook as it was, restorable by label. - Atomic verified save: the write goes to a temporary file, the result is re-opened and validated, then swapped in. A failed operation leaves the original byte-identical. Other programs may see the workbook's path missing for a few milliseconds during a save, as with Office's own saves; this server's own calls wait that out. - Byte-preserving cell edits: an ordinary cell edit leaves the rest of the workbook as it was (printer settings, app.xml, shared strings, row heights, pictures, x14 validation, external links and other parts). The full rewrite used by structural edits carries back custom properties, customXml and SharePoint parts, app.xml, external links, printer settings, cached formula results, automatic row heights, sensitivity labels, the password to modify (`fileSharing`), and the saved-folder path used by relative links. - Deep verification on request: pass `verify_com: true` to any mutating call and the produced file must open in a real hidden Excel with no repair prompt, or the backup is restored and the save refuses. Off by default (it costs a COM round trip and needs Excel installed). `KS4XL_VERIFY_COM=1` makes it the default for every save; the per-call parameter wins over the environment either way. - Round-trip hazard scan: before a mutating save, parts the file writer cannot preserve are named to the caller by class. A loss is refused (`HAZARD_REFUSED`) unless the caller explicitly allows the named classes with `allow_loss`. - Structural edits carry their references. `modify_grid_structure` rewrites formulas, tables, named ranges, charts and cross-sheet references when rows or columns are inserted or deleted, and the result is checked against an independent expectation of Excel's own behavior. Row heights, hidden rows and outline levels move with their rows. A range that reaches the last row or column survives an insert. Sheet rename, sheet delete and table rename go through the same reference rewriter. - Writes honor sheet and workbook protection: a change the protection forbids refuses `PROTECTED`. A workbook with a password to modify is refused, with the remedy, by every `xl_app` operation that would open it for editing, so Excel's Password dialog never appears; most are refused before Excel starts. Read-only Excel operations still open it, and the file tools edit it as it is, keep the password and say so in `warnings`. - Excel's formula limits are enforced before saving: 8,192 characters, nesting depth 65, 255 arguments and the compiled size of the formula. - Sorting matches Excel's ranking exactly: numbers, then text case-insensitively, then FALSE, TRUE, errors, blanks last, in both directions. - Modern functions are written with correct `_xlfn` and `_xlpm` prefixing, so LAMBDA and its relatives open without a repair prompt. - Batches are all-or-nothing: `apply_edits` validates the whole batch first, then applies every edit under ONE backup and ONE save, or mutates nothing. - Closed error vocabulary: every refusal is `{ok: false, error: {code, message, hint}}` with `code` from the 21 values in Section 5. Branch on `code`, never on message text. - Refusals ride the wire with `isError=true`. A refusal never arrives as a success. - `hint` carries the next call to make, not a restatement of the failure. Internal faults are never blamed on the caller. - File-tier tools refuse a workbook that is open in Excel (`WORKBOOK_LOCKED`) rather than racing it. - The Excel tier (`xl_app`) opens its own hidden Excel instance, journals process IDs, and leaves no orphan processes. Calls against it are serialized, so concurrent agents queue instead of interleaving writes. An encrypted workbook is refused by signature before Excel starts unless a password is given. A save made through Excel gets the same broken-link integrity check as a file save, and a failure restores the backup. - Every call runs under one deadline (`KS4XL_CALL_TIMEOUT_S`, default 300 seconds). A `TIMEOUT` answer from a file edit means nothing was saved; if a save had already started the answer says so and you check the file. - Output is bounded (`KS4XL_MAX_OUTPUT_CHARS`, default 80,000 characters). A result that would pass the ceiling is cut where it can be and says so; when nothing is cuttable it refuses `LIMIT_EXCEEDED`. - Numbers that Excel cannot hold are written as asked with a warning: numbers of 2^53 or more (in either sign) and subnormals come back different from Excel, and the warning names the cells and the remedy (pass the value as a string and set the Text number format). - A write that stores a plain numeric string such as "250" as text says so in `warnings`, because `SUM` and `AVERAGE` skip text; send the value as a number to store a number. - Test evidence for 2.0.0: 5,340 tests in `tests/unit`, including checks of the tool list over real stdio, plus local gate scripts that drive a real Excel. Structural edits are checked against Excel's own recalculation, and formulas across every documented function family are written and reopened without repair prompts. ## 4. Addressing model - Positional operations take one `location` object carrying EXACTLY ONE selector key. It resolves to a normalized rectangle before the operation acts. A bare `"B7"` or `"A1:C10"` string is shorthand for a cell or range. - Selectors: `{"cell": "B7"}`, `{"range": "A1:C10"}`, `{"a1": ...}` for either, `{"r1c1": "R7C2"}`, `{"name": "SalesTotal"}` (alias `named_range`), `{"table": "Sales", "column": "Amount", "part": "data"}`, `{"used_range": "Q3"}`, `{"region": {"near": "B7"}}`, `{"search": {...}}`, `{"anchor": ""}`. - Add `"sheet"` to any selector that does not name one. Without it, the active sheet is used. - `used_range` computes the TRUE value-bearing bounds, which is not the same as the dimension string the file carries. Use it rather than trusting a stored dimension. - `get_grid_view` returns an anchor token for the rectangle it SHOWED: sheet, bounds, and a content fingerprint. It also names what it leaves out (pictures, charts). Feed the token straight back as `{"anchor": token}`. - An anchor whose content changed since the view refuses with `STALE_ANCHOR` and tells you to re-run the view. It never falls back to a nearby rectangle. - No operation acts on first match. A search or name resolving to more than one target refuses with `AMBIGUOUS_LOCATION` carrying every match and the address that disambiguates it. - Zero matches refuse with `NOT_FOUND` plus nearest-miss hints. Inverted or off-grid rectangles refuse with `RANGE_OUT_OF_BOUNDS` naming the valid bounds. - Read once, edit in bulk: one `get_grid_view` plus one `apply_edits` replaces a read-modify-verify loop per cell. ## 5. Documented refusals These are contracts. Handle them; do not retry blindly. The 21 closed codes: - `AMBIGUOUS_LOCATION` several targets matched. The message lists them with disambiguating addresses. Re-issue with one. - `NOT_FOUND` the target does not exist. Re-run `get_workbook_metadata` or `get_grid_view` for current sheets, the true used range, and anchors. A workbook path that does not exist gets its own hint (check the path, or make a new one with `create_workbook`), and an operation name that does not exist points you to `xl_help`. - `STALE_ANCHOR` the sheet changed since the view was taken. Re-run `get_grid_view` and resend with fresh anchors. - `RANGE_OUT_OF_BOUNDS` the range is inverted or exceeds the grid, or it is valid but holds more cells than one call takes. The message names the valid bounds or the limit and how to split the work. - `WORKBOOK_LOCKED` the file is open in Excel or held by another process. Close it and retry. - `VALIDATION_FAILED` the produced package failed its post-write check. The original was NOT modified. - `HAZARD_REFUSED` the workbook holds parts the file writer would drop on save. The message names the classes, states exactly what each one loses, and gives the two routes that exist: pass `allow_loss: true` to accept those losses (the workbook is backed up first and the losses are permanent in the saved file), or leave the workbook alone at the file tier. `xl_app` is not a route here: it drives Excel for recalculation, pivots, goal seek, export, rendering, conversion, and encryption, and none of its operations writes a cell, a format, or a row. Reads never touch the workbook and `copy_workbook` branches it byte-for-byte. - `FORMULA_REJECTED` the write hit the formula-injection or unsafe-function policy, or a recalculation or goal seek would reach the network, or its file could not be read well enough to rule that out. The message names the function, data connection or unread part, and the override. - `CALC_UNAVAILABLE` a fidelity recalculation needs Excel (`xl_app`) or functions the fallback does not cover. The message lists what it hit. - `UNSUPPORTED_CONTENT` structurally unsupported or password-protected content, refused instead of written partially. A workbook with a password to modify that `xl_app` would open for editing is refused here, with the remedy (clear the password in Excel under Save As, Tools, General Options). - `CONFLICT` the destination path exists and overwrite was not requested, or Excel disconnected during the call. The message says which. - `BAD_PARAMS` bad arguments, including a value Excel itself would reject and a path outside the configured sandbox roots. `xl_help` with the operation gives its arguments and an example; when the operation belongs to another tool, `details.tool` names it. - `APP_NOT_RUNNING` the operation needs Excel installed and reachable. - `APP_BUSY` Excel is showing a dialog or running a command. Clear it and retry. - `APP_BLOCKED` Excel is not answering. Wait or restart it. - `NOT_AVAILABLE` this server's launch settings turned the operation off, usually by IT (`KS4XL_READ_ONLY` or `KS4XL_FORBID`). No tool call can turn it back on. - `TIMEOUT` the call ran past its time limit and was stopped. A file edit that times out saves nothing; work handed to Excel may still finish, so check the workbook before retrying. - `INTERNAL_ERROR` something inside the server failed; the arguments were not the cause. Try once more, and if it repeats, report it with the version from `get_server_info`. - `LIMIT_EXCEEDED` the answer would pass the output limit. Narrow the request: a smaller range, `query_range` with a limit, or fewer columns. - `PROTECTED` the sheet's or the workbook's protection does not allow this change. Cells that are not locked can still be edited. If you may lift the protection, `xl_workbook` `set_protection` with `action='remove'` does, and nothing else changes. - `GUARD_TRIPPED` a safety check you passed did not hold, so nothing was changed. Read the workbook again and resend with values that match it. Refusals are the reliability feature, not the failure mode. A refusal names what was wrong, leaves the file untouched, and carries the next call in `hint`. Retrying the same call unchanged is never the fix. ## 6. Recipes `get_workflows()` returns the recipe list. `get_workflows(task="")` returns ordered steps, the tool for each step, and why the step exists. - `recover-workbook`: your file will not open or an edit went wrong. Get a good copy back from the automatic backups. - `merge-workbooks`: combine data from several workbooks into one target without silently losing anything either side holds. - `report-build`: raw data to report workbook. Import, table, formulas, chart, then the page and view polish. - `data-cleanup`: find and replace, then validation rules, then sort, verified at each end. - `formatting-audit-and-fix`: diagnose style bloat against the 64,000-format ceiling and consolidate formatting. - `safe-edit-of-rich-workbook`: editing a workbook holding pivots, charts, macros or queries without silent loss. Diagnose, hazard-aware edit, verify. - `migrate-from-incumbent`: tool-by-tool mapping from the common file-based Excel MCP server surface onto this one. - `migrate-from-v1`: translates 1.x calls (one tool per operation) to 2.0; the map lists every released 1.2.5 tool. ## 7. Inventory: 69 operations in 7 tools ### `xl_read`: 18 operations `get_workbook_metadata`, `get_grid_view`, `read_range`, `get_cells`, `query_range`, `find_cells`, `get_table`, `diagnose_workbook`, `validate`, `audit_formulas`, `audit_styles`, `get_pivot`, `get_connections`, `get_external_links`, `inspect_vba`, `get_workflows`, `get_server_info`, `com_status`. ### `xl_edit`: 10 operations `set_cell`, `set_cells`, `write_range`, `clear_range`, `copy_range`, `move_range`, `apply_edits`, `replace_cells`, `set_formula`, `import_data`. ### `xl_format`: 11 operations `format_cells`, `apply_style`, `copy_format`, `set_dimensions`, `set_merge`, `manage_conditional_format`, `manage_comment`, `manage_hyperlink`, `manage_image`, `manage_chart`, `set_view`. ### `xl_structure`: 9 operations `manage_worksheet`, `modify_grid_structure`, `create_table`, `manage_table`, `manage_name`, `sort_range`, `set_filter`, `clear_filter`, `manage_data_validation`. ### `xl_workbook`: 9 operations `create_workbook`, `copy_workbook`, `manage_backups`, `set_workbook_properties`, `set_protection`, `set_page_layout`, `set_header_footer`, `export_range`, `export_file`. ### `xl_app`: 12 operations, Windows with Excel only (except `update_now` and `update_notices`) `recalculate`, `com_manage_pivot`, `com_export_pdf`, `com_render_sheet`, `com_convert_format`, `com_save_with_password`, `com_autofit`, `com_goal_seek`, `com_set_sparkline`, `com_validate_opens_clean`, `update_now`, `update_notices`. ### `xl_help`: 0 operations Returns the arguments and a worked example for any operation, and explains the location object. ### Operations that take an action - `manage_table(action=...)`: add and delete columns and rows, rename, resize, set style, toggle totals, convert to range. Nine actions. - `validate(checks=[...])`: nine read-only checks, `structure`, `references`, `names`, `merges`, `tables`, `formatting_bloat`, `hazards`, `external_links`, `calc_staleness`. - `manage_worksheet(action=...)`: add, delete, rename, copy, reorder, hide, unhide. The workbook always keeps one visible sheet, so the last one refuses. - `manage_comment(action=...)`: add, edit, delete, reply, resolve, list. Threaded comments read as threads. - `manage_name(action=...)`: add, update, rename, delete, list, including LAMBDA definitions. - `set_protection(action=...)`: sheet, workbook, unlock, remove, status. - `modify_grid_structure(action=...)`: insert and delete rows and columns, with full reference carry. - `apply_edits(edits=[{op: ...}])`: `set_value`, `set_formula`, `write_range`, `clear`, batched under one backup and one save. - `manage_image`, `manage_chart`, `manage_conditional_format`, `manage_data_validation`, `manage_hyperlink`, `manage_backups`, `set_merge`, `com_manage_pivot`, `com_set_sparkline`: lifecycle verbs on one object family per operation. The operations figure is produced by `scripts/measure_surface.py` (69 over real stdio). The figure that counts each action separately, 131, is produced by `scripts/count_operations.py`, which reads the dispatch values each operation actually validates out of committed source. Its docstring carries the counting definition and the exclusions. ## 8. Upgrading from 1.x Version 2.0 regroups the 69 flat tools of 1.2.x (67 workbook tools plus `enable_tools` and `disable_tools`) into the seven tools above. Every 1.2.5 operation survives except `enable_tools` and `disable_tools`, which are gone because nothing needs enabling. Call a former tool as an operation of the group that now holds it: `xl_edit` with `op: "set_cell"` replaces the tool `set_cell`. The full map is `docs/surface_map.md` in the repository, and `get_workflows` with the `migrate-from-v1` recipe returns it too. A few arguments and values changed: conditional-format and validation values are snake_case (the camelCase spellings still work), and `manage_chart` takes `at_cell` (`anchor` still works). ## 9. Install - Claude Desktop: download the `.mcpb` bundle from the latest GitHub release and open it. Requires `uv` on PATH. - Claude Code: `claude mcp add xl -s user -- uvx kitchensink4xl` - pip: `pip install kitchensink4xl` (Excel tool: `pip install kitchensink4xl[com]`, a no-op off Windows). On Windows the COM dependency often arrives transitively with the base install; installing with `[com]` is the guaranteed route either way, and harmless to repeat. - uvx, no install: `uvx kitchensink4xl` - Any MCP client: run the `kitchensink4xl` (or `xl-mcp`) executable over stdio, for example `{"mcpServers": {"xl": {"command": "kitchensink4xl"}}}` - Interpreter plus module, same server: `python -m ks4xl_community_launch`. The shared core does not run on its own, so `python -m xlsx_mcp` and `python -m xlsx_mcp.server` stop with a message that names the launcher. The distribution is `kitchensink4xl` and the installed core package is `xlsx_mcp`; `import kitchensink4xl` fails. First launch through uvx downloads and builds the environment and can take 20 to 30 seconds before the server answers; every launch after that starts in about two. If a client reports a timeout on first install, launch once from a terminal and try again. Environment: `KS4XL_READ_ONLY`, `KS4XL_FORBID`, `KS4XL_ALLOWED_ROOTS` (an `os.pathsep`-separated path sandbox, gating reads as well as writes), `KS4XL_CALL_TIMEOUT_S`, `KS4XL_MAX_OUTPUT_CHARS`, `KS4XL_AUTHOR` (`user`, the default, signs a note added without an author with your Office user name, or your Windows sign-in name when Office has none; `assistant` signs "AI Assistant via KS4XL", as does a call with `author="assistant"`; an email address is never written as an author), `KS4XL_VERIFY_COM`, `KS4XL_VALIDATE_COM`, `KS4XL_COM_TIMEOUT`, `KS4XL_UPDATE_CHECK` (`off` turns the update check off completely: no network call, no cache file; the older `KS4XL_NO_UPDATE_CHECK=1` still works), `KS4XL_UPDATE_NOTICES` (`off` turns update notices off and keeps the check; `on` turns them back on). Update notices: at most once a week, after a tool call, the server asks PyPI in the background which version is newest; the request names only the package and the installed version. When a newer version exists, a client with form elicitation shows a dialog (Update now / Not now) after a read when the server runs from uvx or the Claude Desktop bundle; otherwise a read result gets one `update` field, to relay once. `xl_app` `update_now` installs the new version only when the user asks or agrees, and `update_notices` (`on`, `off`, `status`) changes the setting from any client. It never downloads or installs a new version unless the user chooses Update now in its notice or asks the AI to update it. ## 10. Known limits - `com_render_sheet` goes through Excel's clipboard and needs an interactive window station. It fails in non-interactive sessions and works from a normal signed-in desktop. - One user, one machine. The server runs over stdio under a single account, and the Excel tier drives an Excel belonging to that Windows session. It is not a shared service. - A file-tier write leaves downstream cached values stale until Excel recalculates. The labels say so; `recalculate` fixes it. - Deleting a sheet turns references to it into `#REF!`, as Excel does. Renaming a sheet rewrites them. ## 11. Links - GitHub: https://github.com/KitchenSink4AI/KitchenSink4XL - PyPI: https://pypi.org/project/kitchensink4xl/ - Landing page: https://community.kitchensink4.ai/xl/ - Family: https://community.kitchensink4.ai/ - Issues, including licensing: https://github.com/KitchenSink4AI/KitchenSink4XL/issues/new/choose ## 12. License AGPL-3.0. Free for any use that meets the AGPL's terms, including its source-sharing obligations. A company shipping this in a closed product of its own needs a commercial license, with terms worked out case by case, arranged here: https://github.com/KitchenSink4AI/KitchenSink4XL/issues/new?template=commercial_license.yml ## 13. Trademarks Not affiliated with, endorsed by, or sponsored by Microsoft Corporation. Microsoft and Excel are trademarks of Microsoft Corporation. KitchenSink4XL works with Microsoft Excel files; the trademarks are used nominatively to describe that compatibility, and no Microsoft logos or trade dress are used.