Skip to main content
Glama
vohoailinh90

Excel MCP Server

by vohoailinh90
README.md
# Excel MCP Server — v1

An MCP server that exposes Excel operations (open/close files, read/write
cells, create sheets, apply formulas, and **run existing VBA macros by
name**) so an AI agent (Claude Desktop, Claude Code, or any MCP client)
can call them.

## Already tested in this sandbox

```
python demo/demo_test.py
```

Result: all 12 tools are registered correctly by the server, and the
full flow `open → read/write cell → apply formula → create sheet
(copy) → run macro → save → read back from disk to verify` runs
correctly end-to-end using `MockOpenpyxlBackend`.

**Important limitation of this demo**: it runs on Linux (no Excel
installed), so the "macro" in the demo is a Python function registered
manually to mimic the call-by-name convention — **it is not real VBA**.
To run your actual `.xlsm` macros, this needs to run on a Windows
machine with Excel installed (see below).

## Architecture

```
                      ┌─────────────────────┐
  Claude / AI agent   │   MCP Client         │
  (Desktop, Code,...) │  (stdio transport)   │
                      └──────────┬──────────┘
                                 │ JSON-RPC over stdio
                      ┌──────────▼──────────┐
                      │   server.py          │  ← 12 tools: excel_open_workbook,
                      │   (mcp.MCPServer)     │    excel_read_cell, excel_run_macro, ...
                      └──────────┬──────────┘
                                 │ calls through a shared interface
                      ┌──────────▼──────────┐
                      │  backends/base.py     │  (ABC, OS-independent)
                      └──────────┬──────────┘
                     ┌───────────┴────────────┐
        ┌────────────▼───────────┐  ┌─────────▼────────────┐
        │ windows_com.py          │  │ mock_openpyxl.py       │
        │ (xlwings + pywin32)     │  │ (openpyxl, any OS)     │
        │ - drives real Excel.exe │  │ - test/dev only, does  │
        │ - runs real VBA macros  │  │   NOT run real VBA     │
        │   in .xlsm files        │  └────────────────────────┘
        │ - dedicated COM thread  │
        │   to avoid STA errors   │
        └─────────────────────────┘
```

`server.py` automatically picks the backend: `platform.system() ==
"Windows"` → `WindowsComBackend`, otherwise → `MockOpenpyxlBackend`
(with a warning printed to stderr).

### Why xlwings instead of raw win32com?

You already have experience using `pywin32` directly (the Outlook RSVP
Tool). For Excel, `xlwings` is a better fit because:
- `wb.macro("MacroName")(*args)` calls a VBA `Sub`/`Function` directly
  by name, whether it lives in a regular Module or in `ThisWorkbook`.
- The cell/range API (`sheet.range("A1").value`) is much cleaner than
  raw COM's `worksheet.Cells(1,1).Value`.
- It still uses pywin32 under the hood, so you don't lose the ability
  to "drop down" to raw COM (`wb.api`, `sheet.api`) for advanced
  operations (pivot tables, charts, the VBA editor...) that xlwings
  doesn't wrap yet.

### Why a dedicated COM thread?

Excel COM is single-threaded apartment (STA). If multiple tool calls
arrive close together (an agent may call tools asynchronously), calling
COM directly from different threads/coroutines will crash or produce
"Application is busy" errors. The fix: a single background thread holds
`CoInitialize()`, and every command is pushed into a queue and executed
sequentially on that same thread — safe, simple, and fast enough for
the expected scale ("a handful of agents calling tools", not hundreds
of requests per second).

## Installation

### On a dev/test machine (no Windows required)

```bash
pip install -r requirements.txt
python demo/demo_test.py
```

### On a production Windows machine (with Excel + real macros)

```powershell
pip install -r requirements.txt
python server.py
```

The server will automatically detect Windows and use
`WindowsComBackend`.

**Safety recommendation**: restrict which macros can be run via an
environment variable, so an AI agent can't call arbitrary macros on a
real file:

```powershell
$env:EXCEL_MCP_ALLOWED_MACROS = "GenerateSummary,RefreshPivot,ExportReport"
python server.py
```

## Configuring Claude Desktop

Add this to Claude Desktop's MCP config file
(`%APPDATA%\Claude\claude_desktop_config.json` on Windows):

```json
{
  "mcpServers": {
    "excel-automation": {
      "command": "python",
      "args": ["C:\\path\\to\\excel-mcp-server\\server.py"],
      "env": {
        "EXCEL_MCP_ALLOWED_MACROS": "GenerateSummary,RefreshPivot"
      }
    }
  }
}
```

For Claude Code, add it the same way to the project's MCP server
config (via `.claude` or the `claude mcp add` command).

## Tool list (v1)

| Tool | What it does |
|---|---|
| `excel_open_workbook(path, visible)` | Opens a file, returns `workbook_id` |
| `excel_close_workbook(workbook_id, save)` | Closes a file |
| `excel_list_open_workbooks()` | Lists currently open files |
| `excel_list_sheets(workbook_id)` | Lists sheet names |
| `excel_read_cell(workbook_id, sheet, cell)` | Reads one cell |
| `excel_read_range(workbook_id, sheet, cell_range)` | Reads a range |
| `excel_write_cell(workbook_id, sheet, cell, value)` | Writes one cell |
| `excel_write_range(workbook_id, sheet, start_cell, values)` | Writes a 2D array |
| `excel_apply_formula(workbook_id, sheet, cell, formula)` | Applies a formula |
| `excel_create_sheet(workbook_id, name, copy_from, position)` | Creates/copies a sheet |
| `excel_run_macro(workbook_id, macro_name, args)` | Runs a VBA macro by name |
| `excel_save_workbook(workbook_id, path)` | Saves / save-as |

## Next steps (not part of this v1)

1. **Test with your actual `.xlsm` macros** on a Windows machine — need
   the specific Sub/Function names and argument counts to verify
   `excel_run_macro` works as expected.
2. **Handling multiple workbooks concurrently** more carefully —
   currently a single shared `xlwings.App` is used; if full isolation
   is needed (e.g. 2 agents working on 2 independent files without
   affecting each other's global Excel settings), separate `App`
   instances could be used instead — at the cost of more RAM/CPU.
3. **Idle timeout**: automatically close workbooks and shut down the
   `App` if no command arrives within N minutes, to avoid a hidden
   Excel process silently eating RAM if an agent forgets to close it.
4. **Logging/audit**: log every write/macro command (who called it,
   when, which macro, which arguments) — important since macros are
   arbitrary code execution.
5. **Retry/file-lock handling**: handle the case where you already have
   the file open manually in Excel while the agent also tries to open
   it — currently this raises a COM error that should be caught and
   turned into a clear message.
6. Consider `streamable-http` transport instead of `stdio` if you want
   multiple different clients/agents to connect to one long-running
   server process, instead of each Claude Desktop instance spawning its
   own server process.