Skip to main content
Glama
README.md
# excel-mcp

An MCP (Model Context Protocol) server for reading Excel workbooks (`.xlsx`). Built with [FastMCP](https://github.com/jlowin/fastmcp) and [openpyxl](https://openpyxl.readthedocs.io/).

## Features

- List all sheets in a workbook
- List all named tables across sheets
- List all pivot tables with source range info
- Read raw cell data from any sheet or optional cell range
- Read structured data (headers + rows) from named Excel tables
- Export any sheet to CSV, plain-text (delimited), or Markdown — save to file or return inline

## Usage with Claude Desktop

No installation required. Add the following to your `claude_desktop_config.json`:

```json
{
  "mcpServers": {
    "excel-mcp": {
      "command": "uvx",
      "args": [
        "--from",
        "git+https://github.com/urjeetpatel/excel_mcp_server",
        "excel-mcp"
      ]
    }
  }
}
```

`uvx` will pull the package directly from GitHub and run it in an isolated environment — no `pip install` or virtual environment setup needed.

## Running manually

```bash
uvx --from git+https://github.com/urjeetpatel/excel_mcp_server excel-mcp
```

## Tools

All tools accept a `file_path` parameter — the full path to the `.xlsx` file.

| Tool | Description |
|------|-------------|
| `list_sheets` | Returns the names of all sheets in the workbook |
| `list_tables` | Returns all named tables with name, sheet, and cell range |
| `list_pivot_tables` | Returns all pivot tables with location and source range info |
| `get_sheet_data` | Returns cell data from a sheet; optionally scoped to a range (e.g. `A1:D10`) |
| `get_table_data` | Returns headers and row data from a named Excel table |
| `export_sheet_to_csv` | Exports a sheet to a CSV file with configurable delimiter; supports inline return |
| `export_sheet_to_text` | Exports a sheet to a plain-text delimited file; supports inline return |
| `export_sheet_to_markdown` | Exports a sheet to a padded Markdown table; supports inline return |
| `create_blank_file` | Creates a new blank Excel file at the given path |
| `add_sheet` | Adds a new sheet to an existing Excel file |
| `add_data_to_sheet` | Adds a 2D array of data to a sheet, starting at a specified cell |
| `add_table_to_sheet` | Adds a table to a sheet over a given cell range |
| `set_cell_value` | Sets the value of a single cell in a sheet |
| `set_cell_formula` | Sets a formula in a single cell in a sheet |
### Write tool details

#### `create_blank_file(file_path)`
Creates a new blank Excel file at the specified path.

#### `add_sheet(file_path, sheet_name)`
Adds a new sheet to the Excel file. Fails if the sheet already exists.

#### `add_data_to_sheet(file_path, sheet_name, data, start_cell="A1")`
Adds a 2D array of data to the specified sheet, starting at the given cell (default A1).

#### `add_table_to_sheet(file_path, sheet_name, table_name, ref)`
Adds a table to the specified sheet, covering the given cell range (e.g. "A1:D10").

#### `set_cell_value(file_path, sheet_name, cell, value)`
Sets the value of a single cell (e.g. C5) in the specified sheet.

#### `set_cell_formula(file_path, sheet_name, cell, formula)`
Sets a formula (e.g. "=SUM(A1:A10)") in a single cell in the specified sheet.

### Tool details

#### `list_sheets(file_path)`
```json
["Sheet1", "Sheet2"]
```

#### `list_tables(file_path)`
```json
[{ "name": "SalesTable", "sheet": "Sheet1", "ref": "A1:D20" }]
```

#### `list_pivot_tables(file_path)`
```json
[{
  "name": "PivotTable1",
  "sheet": "Summary",
  "ref": "A1:C10",
  "source_sheet": "RawData",
  "source_ref": "A1:F500"
}]
```

#### `get_sheet_data(file_path, sheet_name, cell_range?)`
```json
{
  "sheet": "Sheet1",
  "range": "A1:D10",
  "rows": [["Name", "Age", "City"], ["Alice", 30, "New York"]]
}
```
- `cell_range` is optional. When omitted, the full used range is returned.
- Range strings are case-insensitive (`a1:d10` == `A1:D10`).

#### `get_table_data(file_path, table_name)`
```json
{
  "table": "PeopleTable",
  "sheet": "Sheet1",
  "ref": "A1:C4",
  "headers": ["Name", "Age", "City"],
  "rows": [["Alice", 30, "New York"], ["Bob", 25, "Chicago"]]
}
```

---

### Export tools

All three export tools share a common pattern:

- `output_path` — path to write the output file, **or** `"return inline"` to skip writing and return the content directly in the response.
- `cell_range` — optional Excel range string (e.g. `A1:D10`). When omitted, the full used range is exported.

When saving to a file the response contains `output_path`. When returning inline, `output_path` is replaced by `content`.

#### `export_sheet_to_csv(file_path, sheet_name, output_path, delimiter?, cell_range?)`

Exports a sheet using Python's `csv` module (values containing the delimiter or newlines are properly quoted).

`delimiter` options: `"comma"` (default), `"pipe"`, `"tab"`.

```json
{ "output_path": "/tmp/data.csv", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```
Inline variant (`output_path = "return inline"`):
```json
{ "content": "Name,Age,City\r\nAlice,30,New York\r\n...", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```

#### `export_sheet_to_text(file_path, sheet_name, output_path, delimiter?, cell_range?)`

Exports a sheet as a plain delimited text file — values are joined with the delimiter with no CSV quoting, making output easy to read or pipe into other tools.

`delimiter` options: `"pipe"` (default), `"comma"`, `"tab"`.

```json
{ "output_path": "/tmp/data.txt", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```
Inline variant:
```json
{ "content": "Name|Age|City\nAlice|30|New York\n...", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```

#### `export_sheet_to_markdown(file_path, sheet_name, output_path, cell_range?)`

Exports a sheet as a padded Markdown table. The first row is used as the header; a separator line is inserted beneath it. All columns are padded to align.

```json
{ "output_path": "/tmp/data.md", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```
Inline variant:
```json
{ "content": "| Name  | Age | City     |\n| ----- | --- | -------- |\n| Alice | 30  | New York |\n...", "sheet": "Sheet1", "range": "A1:C4", "rows_written": 4 }
```

## Local development

Requires Python >= 3.14 and [uv](https://docs.astral.sh/uv/).

```bash
git clone https://github.com/urjeetpatel/excel_mcp_server
cd excel_mcp_server
uv sync --group dev

# Run tests
uv run pytest

# Type check
uv run mypy src
```

## Project Structure

```
src/excel_mcp/
├── __init__.py       # Exposes main() entry point
├── server.py         # FastMCP server and tool registration
├── workbook.py       # Workbook loading helpers
└── tools/
    ├── __init__.py
    ├── read.py       # Read tool implementations
    └── export.py     # Export tool implementations (CSV, text, Markdown)
tests/
├── conftest.py       # pytest fixtures (builds workbooks programmatically)
├── test_read.py      # Tests for read tools
├── test_export.py    # Tests for export tools
└── test_workbook.py  # Tests for workbook helpers
```

## Notes

- Workbooks are opened in `data_only=True` mode — formula results are read, not formula strings.
- Export tools support `"return inline"` as `output_path` to return content directly to the agent without touching the filesystem.

## License

GPL-3.0-or-later

TDQS

A3.7/5.0

Scored across 14 tools

Disambiguation5/5

Each tool targets a distinct resource or action: listing vs reading vs exporting vs writing are clearly separated, and the three export tools differ by output format. Even the similar get_sheet_data and get_table_data are unambiguous because one returns raw sheet cells and the other returns a named table with headers.

Naming Consistency5/5

Tool names follow a consistent verb_noun pattern: list_*, get_*, export_sheet_to_*, add_*, and set_cell_*. The naming style is uniform and predictable across the entire set.

Tool Count5/5

14 tools is well within the ideal range for an Excel-focused MCP server. Each tool covers a distinct operation and the set feels appropriately scoped without unnecessary duplication.

Completeness4/5

The server covers the core Excel lifecycle: discovering structure, reading data, exporting, creating files, adding sheets/data/tables, and setting cell values or formulas. Minor gaps exist—such as no delete/rename/clear operations or updating existing tables—but agents can accomplish most common workflows.

Maintenance

ActivityInactive
ResponsivenessNo issues