Skip to main content
Glama
mcp-server-spreadsheet

mcp-server-spreadsheet

by marekrost

mcp-server-spreadsheet

mcp-name: io.github.marekrost/mcp-server-spreadsheet

Data-first MCP server for reading and writing spreadsheet files (.xlsx, .csv, .ods).

Key features

  • Multi-format — works with Excel (.xlsx), CSV (.csv), and OpenDocument (.ods) files through a unified tool interface.

  • Dual mode — cell-level workbook operations and a DuckDB-powered SQL query engine, interleaved freely on the same file.

  • Workbook essentials — worksheets, rows, columns, cells, search.

  • Data-only — preserves existing formatting but only reads and writes values.

  • Stateless — every call specifies file and sheet explicitly; no handles or sessions.

  • Atomic saves — writes go to a temp file, then os.replace() into the target path.

  • Type coercion on write — numeric strings become numbers, everything else is text.

  • SQL across sheets — JOINs, GROUP BY, aggregates, subqueries via in-memory DuckDB; mutations write back to the file.

  • CSV as single-sheet workbook — CSV files are treated as a workbook with one sheet named default.

Related MCP server: SheetForge MCP

Requirements

  • Python 3.10+

Installation

No local checkout needed — just configure your MCP client (see below).

From source (for development)

git clone https://github.com/marekrost/mcp-server-spreadsheet.git
cd mcp-server-spreadsheet
uv sync

Usage

Claude Desktop

Add to your claude_desktop_config.json:

Using PyPI (recommended):

{
  "mcpServers": {
    "mcp-server-spreadsheet": {
      "command": "uvx",
      "args": ["mcp-server-spreadsheet"]
    }
  }
}

Using local source:

{
  "mcpServers": {
    "mcp-server-spreadsheet": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp-server-spreadsheet", "main.py"]
    }
  }
}

Claude Code

Add to your .mcp.json:

Using PyPI (recommended):

{
  "mcpServers": {
    "mcp-server-spreadsheet": {
      "command": "uvx",
      "args": ["mcp-server-spreadsheet"]
    }
  }
}

Using local source:

{
  "mcpServers": {
    "mcp-server-spreadsheet": {
      "command": "uv",
      "args": ["run", "--directory", "/path/to/mcp-server-spreadsheet", "main.py"]
    }
  }
}

Standalone (stdio transport)

# PyPI
uvx mcp-server-spreadsheet

# Local source
uv run main.py

Restricting file access to a directory (optional)

Set MCP_SPREADSHEET_ROOT to confine all path arguments to a single directory tree. Paths outside it are rejected with a clear error returned to the agent.

{
  "mcpServers": {
    "mcp-server-spreadsheet": {
      "command": "uvx",
      "args": ["mcp-server-spreadsheet"],
      "env": { "MCP_SPREADSHEET_ROOT": "/home/me/spreadsheets" }
    }
  }
}

Unset (the default), any path the server process can access is allowed.

Format notes

Format

Sheets

Formulas

Types

.xlsx

Multiple

Preserved as strings

Native (int, float, date, bool)

.ods

Multiple

Not preserved

Native (int, float, date, bool)

.csv

Single (default)

N/A

Inferred on load (int, float, text)

Sheet management tools (add_sheet, delete_sheet, copy_sheet) raise an error for CSV files.

Tools

Workbook Operations

Tool

Description

list_workbooks

List all spreadsheet files in a directory (non-recursive)

create_workbook_file

Create a new empty spreadsheet file (format by extension)

copy_workbook

Copy an existing file to a new path

Sheet Operations

Tool

Description

list_sheets

List all sheet names in a workbook

add_sheet

Add a new sheet (optional name and position)

rename_sheet

Rename an existing sheet

delete_sheet

Delete a sheet by name

copy_sheet

Duplicate a sheet within a workbook (optional new name and position)

Reading Data

Tool

Description

read_sheet

Read entire sheet as rows (optional row/column bounds)

read_cell

Read a single cell value, e.g. B3

read_range

Read a rectangular range, e.g. A1:D10

get_sheet_dimensions

Get row and column count of the used range

Writing Data

Tool

Description

write_cell

Write a value to a single cell

write_range

Write a 2D array starting at a given cell

append_rows

Append rows after the last used row

insert_rows

Insert blank or pre-filled rows at a position (shifts rows down)

delete_rows

Delete rows by index (shifts rows up)

clear_range

Clear values in a range without removing rows/columns

copy_range

Copy a block of cells to another location (optionally to a different sheet)

Column Operations

Tool

Description

insert_columns

Insert blank columns at a position

delete_columns

Delete columns by index

Tool

Description

search_sheet

Search for a value or regex pattern, returns matching cell references

Table Mode (SQL)

Tool

Description

describe_table

Inspect column names, inferred types, row count, and sample values

sql_query

Execute a read-only SQL SELECT (supports JOINs across sheets, GROUP BY, aggregates, subqueries)

sql_execute

Execute INSERT INTO, UPDATE, or DELETE FROM — writes changes back to the file

SQL examples:

-- Filter and sort
SELECT name, revenue FROM Sales WHERE status = 'Active' ORDER BY revenue DESC LIMIT 20

-- Cross-sheet JOIN
SELECT o.order_id, c.name FROM Orders o JOIN Customers c ON o.customer_id = c.id

-- Aggregate
SELECT department, COUNT(*) AS n, AVG(salary) AS avg FROM Employees GROUP BY department

-- Mutate
UPDATE Sales SET status = 'Closed' WHERE quarter = 'Q1' AND revenue < 1000
DELETE FROM Logs WHERE date < '2024-01-01'

Sheet names with spaces must be quoted: SELECT * FROM "Q1 Sales".

Sheets whose table doesn't start at row 1

All three SQL tools accept header_row and data_start_row. Each can be an int (applied to every sheet) or a {sheet_name: row} mapping (sheets not listed fall back to the default). Use header_row when column titles live below row 1, and data_start_row when extra rows (e.g. a units row) sit between the header and the data.

# Header on row 3, data follows immediately
sql_query(file, 'SELECT * FROM "People"', header_row=3)

# Mixed workbook: People headers at row 3, Orders header at row 1 with a
# units row at row 2.
sql_query(
    file,
    'SELECT * FROM "Orders" o JOIN "People" p ON o.name = p.name',
    header_row={"People": 3, "Orders": 1},
    data_start_row={"Orders": 3},
)

sql_execute preserves rows above header_row when writing changes back.

Running tests

uv sync --group dev
uv run pytest

Every tool is exercised against .xlsx, .csv, and .ods fixtures generated into a temp directory.

Common Parameters

Every sheet-level tool accepts:

Parameter

Required

Description

file

yes

Path to the spreadsheet file (.xlsx, .csv, or .ods)

sheet

no

Sheet name. Defaults to the first sheet in the workbook

All row/column indices are 1-based. Cell references use A1 notation (A1, $B$2).

Available Tools

25 tools
add_sheetA

Add a new sheet to the workbook.

Returns the name of the newly created sheet. Not supported for CSV files.

Inputs: file (required), name, position. Returns: result

append_rowsA

Append one or more rows after the last used row in the sheet.

Values are type-coerced (numeric strings to numbers). This is the most efficient way to add data to the end of a sheet.

Inputs: file (required), data (required), sheet. Returns: result

clear_rangeA

Clear all cell values in a range without removing rows or columns.

Sets every cell in the range to null. Row/column structure is preserved.

Inputs: file (required), range_str (required), sheet. Returns: result

copy_rangeA

Copy a rectangular block of cells to another location.

Copies raw values only. The destination can be on the same sheet or a different sheet in the same workbook. Existing values at the destination are overwritten.

Inputs: file (required), source_range (required), dest_start (required), sheet, +1 more. Returns: result

copy_sheetA

Duplicate a sheet within the same workbook.

Copies all cell values. Returns the name of the new sheet. Not supported for CSV files.

Inputs: file (required), source_name (required), new_name, position. Returns: result

copy_workbookA

Copy an existing spreadsheet file to a new location.

Performs a full file copy preserving all data. The destination must not already exist. Returns the absolute path of the new file.

Inputs: source (required), destination (required). Returns: result

create_workbook_fileA

Create a new empty spreadsheet file at the given path.

The file format is determined by the extension (.xlsx, .csv, or .ods). The file must not already exist. Returns the absolute path of the created file.

Inputs: file (required), sheet_name. Returns: result

delete_columnsB

Delete one or more columns, shifting remaining columns left.

Inputs: file (required), column (required), count, sheet. Returns: result

delete_rowsB

Delete one or more rows, shifting remaining rows up.

All data in the deleted rows is permanently removed.

Inputs: file (required), row (required), count, sheet. Returns: result

delete_sheetA

Delete a sheet by name from the workbook.

All data in the sheet is permanently removed. Not supported for CSV files.

Inputs: file (required), name (required). Returns: result

describe_tableA

Inspect the structure of a sheet treated as a database table.

Returns column names, inferred data types (text, integer, number, boolean, date), total row count, and sample values from the first 3 data rows. Use this before writing SQL queries to understand the available columns and their types.

When sheet is omitted, returns a list of descriptions for all sheets.

Inputs: file (required), sheet, header_row. Returns: result

get_sheet_dimensionsA

Get the dimensions of the used range in a sheet.

Returns {"rows": N, "columns": M} where N is the number of the last used row and M is the number of the last used column. Both are 0 for an empty sheet.

Inputs: file (required), sheet.

insert_columnsB

Insert one or more blank columns, shifting existing columns right.

Inputs: file (required), column (required), count, sheet. Returns: result

insert_rowsA

Insert rows at a given position, shifting existing rows down.

If data is provided, the inserted rows are filled with those values (type-coerced). Otherwise the rows are left blank.

Inputs: file (required), row (required), count, data, +1 more. Returns: result

list_sheetsA

List all sheet names in a workbook, in workbook order.

Returns a list of sheet name strings. CSV files always return a single sheet named 'default'.

Inputs: file (required). Returns: result

list_workbooksA

List all spreadsheet files (.xlsx, .csv, .ods) in a directory (non-recursive).

Returns the full path of each file found, sorted alphabetically.

Inputs: directory (required). Returns: result

read_cellA

Read the value of a single cell.

Returns the cell's value: numbers as int/float, text as string, and empty cells as null.

Inputs: file (required), cell (required), sheet.

read_rangeA

Read a rectangular range of cells as a list of rows.

Returns a 2D array where each inner list is one row of values. Empty cells return null.

Inputs: file (required), range_str (required), sheet. Returns: result

read_sheetA

Read an entire sheet (or a bounded sub-region) as a list of rows.

Each row is a list of cell values. Empty cells appear as null. Use the optional row/column bounds to limit output for large sheets.

Inputs: file (required), sheet, start_row, end_row, +2 more. Returns: result

rename_sheetB

Rename an existing sheet in the workbook.

Returns the new sheet name on success.

Inputs: file (required), old_name (required), new_name (required). Returns: result

search_sheetA

Search all cells in a sheet for values matching a regex pattern.

Returns a list of matches, each with the cell reference and value, e.g. [{"cell": "B3", "value": "hello"}, ...]. Returns an empty list if no matches are found.

Inputs: file (required), pattern (required), sheet. Returns: result

sql_executeA

Execute a mutating SQL statement and write changes back to the file.

Supports INSERT INTO (adds rows), UPDATE (modifies cell values), and DELETE FROM (removes rows). The target sheet is determined from the SQL statement. After execution, the modified table is written back to the file atomically. Returns {"affected_rows": N}.

Inputs: file (required), sql (required), header_row.

sql_queryA

Execute a read-only SQL SELECT query against the spreadsheet data.

Every sheet in the workbook is loaded as a database table, with the header row defining column names and data rows below it. Returns results as a list of {column: value} objects.

Only SELECT (and WITH ... SELECT) statements are accepted. Use sql_execute for INSERT, UPDATE, or DELETE.

Inputs: file (required), sql (required), header_row. Returns: result

write_cellA

Write a single value to a cell.

Overwrites any existing value. The value is type-coerced: numeric strings become numbers, all else is text.

Inputs: file (required), cell (required), value (required), sheet. Returns: result

write_rangeA

Write a 2D array of values into a rectangular region.

Writing starts at start_cell and expands right and down to fit the data. Prefer this over multiple write_cell calls for efficiency.

Inputs: file (required), start_cell (required), data (required), sheet. Returns: result

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.9/5.0

    Scored across 25 tools

    Disambiguation5/5

    Each tool has a clearly distinct purpose with no significant overlap. For example, read_cell, read_range, and read_sheet provide progressively broader reading capabilities, while write_cell and write_range mirror this for writing. SQL tools (sql_query, sql_execute) offer a distinct query-based interface separate from direct cell operations.

    Naming Consistency5/5

    Tool names follow a highly consistent verb_noun pattern throughout, such as add_sheet, clear_range, copy_sheet, delete_columns, list_sheets, read_cell, rename_sheet, and write_range. All tools use snake_case with clear, descriptive action-object pairs, making them predictable and easy to understand.

    Tool Count3/5

    With 25 tools, the count is borderline high for a spreadsheet server, potentially overwhelming for agents. While many tools are justified for comprehensive spreadsheet operations (e.g., cell, range, sheet, and workbook-level actions), it approaches the upper limit where usability might decline due to complexity.

    Completeness5/5

    The toolset provides complete coverage for spreadsheet manipulation, including CRUD operations at multiple levels (cell, range, sheet, workbook), file management, structural modifications, and advanced querying via SQL. There are no obvious gaps; agents can perform all typical spreadsheet tasks without dead ends.

    Maintenance

    ActivityInactive
    ResponsivenessLimited data

    Related MCP Connectors

    Related MCP Servers