Skip to main content
Glama

fcp-sheets

MCP server for semantic spreadsheet operations.

What It Does

fcp-sheets lets LLMs create and edit Excel workbooks by describing spreadsheet intent -- data entry, formulas, styling, charts, conditional formatting -- and renders it into standard .xlsx files. Instead of writing openpyxl code cell-by-cell, the LLM works with operations like data A5 block entry, style A1:F1 bold fill:#1a1a2e, and chart add stacked-column data:B3:C7. Built on the FCP framework, powered by openpyxl for serialization.

Related MCP server: SheetForge MCP

Quick Example

sheets_session('new "Q4 Report" sheets:"Summary,Details"')

sheets([
    'data A1',
    '| Region | Q4 Revenue | Q4 Costs | Margin |',
    '| North | 1250000 | 875000 | =C2/B2 |',
    '| South | 980000 | 710000 | =C3/B3 |',
    '| East | 1100000 | 790000 | =C4/B4 |',
    '| West | 870000 | 620000 | =C5/B5 |',
    'data end',
    'style A1:D1 bold fill:#2F5496 color:#FFFFFF',
    'style B2:C5 fmt:$#,##0',
    'style D2:D5 fmt:0.0%',
    'chart add clustered-column title:"Q4 Revenue by Region" data:B1:C5 categories:A2:A5',
])

sheets_session('save as:./q4_report.xlsx')

Available MCP Tools

Tool

Purpose

sheets(ops)

Batch mutations -- data entry, formulas, styling, charts, merges, borders

sheets_query(q)

Inspect the workbook -- list sheets, describe ranges, read values, find

sheets_session(action)

Lifecycle -- new, open, save, checkpoint, undo, redo

sheets_help()

Full reference card

Benchmark

In a head-to-head against raw openpyxl on a 6-sheet PE portfolio workbook (84 audit checks):

Metric

FCP

Raw openpyxl

Delta

Audit Score

84/84 (100%)

84/84 (100%)

Tie

Total Time

559s (9.3 min)

1,360s (22.7 min)

FCP 2.4x faster

Total Cost

$3.37

$4.11

FCP 18% cheaper

Output Tokens

29,065

101,909

FCP 3.5x fewer

See docs/benchmark/ for the full writeup, audit script, and output files.

Installation

Requires Python >= 3.11.

pip install fcp-sheets

The image verb (inserting images into a workbook) requires Pillow, which is an optional extra:

pip install 'fcp-sheets[images]'

MCP Client Configuration

{
  "mcpServers": {
    "sheets": {
      "command": "uv",
      "args": ["run", "python", "-m", "fcp_sheets"]
    }
  }
}

Architecture

3-layer architecture:

MCP Server (Intent Layer)
  Parses op strings, dispatches to verb handlers
        |
Semantic Model
  Thin wrapper around openpyxl Workbook
  Cell ref parser, sheet index, block mode, undo/redo via byte snapshots
        |
Serialization (openpyxl)
  Semantic model -> .xlsx binary output

Key features:

  • Block data entry -- data/data end blocks enter tabular data with markdown table syntax

  • Formulas -- Including cross-sheet references (='Sheet 2'!B5)

  • Styling -- Font, fill, borders, number formats, merges, alignment

  • Charts -- Bar, line, pie, scatter, bubble, area, doughnut, stacked variants

  • Conditional formatting -- Cell-is rules, color scales, data bars

  • Named ranges & validation -- Drop-down lists, range names

  • Page setup -- Orientation, print titles, frozen panes, filters

  • Undo/redo -- Full workbook snapshots with event sourcing

Development

uv sync
uv run pytest       # 616 tests
uv run ruff check   # linting
uv run pyright      # type checking

License

MIT

Available Tools

4 tools
sheetsA

Execute sheets operations. Each op string follows: VERB TARGET [key:value ...] Call sheets_help for the full reference card.

CELLS: set CELL VALUE [fmt:FORMAT] data ANCHOR ... data end fill SRC dir:down|right [to:CELL|count:N] [until:COL] clear RANGE [all]

SHEETS: sheet add|remove|rename|copy|hide|unhide|activate NAME [at:N]

STYLE: style RANGE|@SEL [bold] [italic] [underline] [strike] [font:NAME] [size:N] [color:#HEX] [fill:#HEX] [align:ALIGN] [valign:VALIGN] [wrap] [indent:N] [rotate:N] [fmt:FORMAT] border RANGE|@SEL SIDES [line:STYLE] [color:#HEX] define-style NAME [font:F] [size:N] [bold] [fill:#HEX] [color:#HEX] [fmt:FORMAT] [align:A] [border:SIDES-STYLE] apply-style NAME RANGE|@SEL

STRUCTURE: merge RANGE [align:center] unmerge RANGE freeze CELL unfreeze filter RANGE | filter off width COL|RANGE SIZE|auto height ROW|RANGE SIZE hide-col COL|RANGE hide-row ROW|RANGE unhide-col COL|RANGE unhide-row ROW|RANGE group-rows RANGE [collapse] group-cols RANGE [collapse] ungroup-rows RANGE ungroup-cols RANGE

CHARTS: chart add TYPE [title:"TEXT"] data:RANGE [categories:RANGE] [at:CELL] [size:WxH] [legend:POS] [style:N]

TABLES: table add NAME range:RANGE [style:STYLE] [banded-rows] [banded-cols] [first-col] [last-col]

CONDITIONAL FORMATTING: cond-fmt RANGE TYPE [params...] Types: cell-is OP VALUE | formula =EXPR | color-scale | data-bar | icon-set | duplicate | unique | top N | bottom N

DATA VALIDATION: validate RANGE TYPE [params...] | validate off RANGE

EDITING: remove @SELECTOR copy RANGE to:CELL [sheet:NAME] move RANGE to:CELL [sheet:NAME] sort RANGE by:COL [dir:asc|desc] [by2:COL dir2:asc|desc] insert-row ROW [count:N] insert-col COL [count:N] delete-row ROW [count:N] delete-col COL [count:N]

MISC: name define|remove NAME [range:RANGE] [scope:SHEET] image CELL path:PATH [size:WxH] link CELL url:URL [text:"TEXT"] | link CELL sheet:NAME!CELL | link off CELL comment CELL "TEXT" | comment off CELL protect [password:PWD] unprotect [password:PWD] lock RANGE unlock RANGE page-setup [orient:landscape|portrait] [paper:letter|a4|legal] [margins:T,R,B,L] [header:TEXT] [footer:TEXT] [print-area:RANGE] [print-title-rows:ROW_RANGE] [print-title-cols:COL_RANGE] [fit-width:N] [fit-height:N] [gridlines] [center-h] [center-v]

CELL REFERENCES: A1 single | A1:D10 range | B:B column | 3:3 row | Sheet2!A1 cross-sheet @bottom_left @bottom_right @right_top (spatial anchors, +N offset)

DATA BLOCKS (PREFERRED FOR MULTI-CELL INPUT): CSV: data A1 Name,Age,City Alice,30,NYC Bob,25,LA data end Markdown: data A1 | Name | Age | City | |------|-----|----- | | Alice | 30 | NYC | data end Formulas: =SUM(A1:A10), =B2*0.22, =C3/C$8 all work inside data blocks Types: bare numbers → numeric, =expr → formula, "quoted" → text, 007 → text

NUMBER FORMATS: General | 0 | 0.00 | #,##0 | $#,##0 | $#,##0.00 0% | 0.00% | yyyy-mm-dd | mm/dd/yyyy | hh:mm:ss | @

COLORS: #4472C4 blue #ED7D31 orange #A5A5A5 gray #FFC000 gold #5B9BD5 lt-blue #70AD47 green #FF0000 red #00B050 dk-green #C6EFCE good-fill #FFC7CE bad-fill #FFEB9C neutral-fill

CHART TYPES: bar, column, line, pie, scatter, area, doughnut, radar, bubble stacked-bar, stacked-column, stacked-area 100-bar, 100-column, 100-area bar-3d, column-3d, line-3d, pie-3d, area-3d

SELECTORS: @sheet:NAME @range:A1:Z99 @row:N @col:A @type:formula|number|text|date|empty @table:NAME @name:NAME @all @recent @recent:N @not:TYPE:VALUE Combine to intersect: @sheet:Revenue @col:E @type:formula

BORDER STYLES: thin | medium | thick | dashed | dotted | double | hair Sides: all | outline | top | bottom | left | right | inner | h | v

CONDITIONAL FORMATTING: cond-fmt RANGE cell-is OP VALUE [VALUE2] [fill:#HEX] [color:#HEX] [bold] cond-fmt RANGE formula =EXPR [fill:#HEX] [color:#HEX] [bold] cond-fmt RANGE color-scale [min-color:#HEX] [max-color:#HEX] [mid-color:#HEX] cond-fmt RANGE data-bar [color:#HEX] cond-fmt RANGE icon-set [icons:arrows|flags|traffic|rating|symbols] cond-fmt RANGE duplicate|unique [fill:#HEX] [color:#HEX] cond-fmt RANGE top|bottom N [fill:#HEX] [color:#HEX] Operators (cell-is only): gt | lt | gte | lte | eq | neq | between | not-between

TABLE STYLES: TableStyleLight1-21 | TableStyleMedium1-28 | TableStyleDark1-11

RESPONSE PREFIXES:

  • cell/data created ~ chart/table created

  • style/format modified - cell/range removed ! error or meta @ bulk/selector operation

EXAMPLE WORKFLOW:

  1. sheets_session('new "Q4 Report"')

  2. sheets(['sheet add Revenue'])

  3. sheets(['merge A1:F1', 'set A1 "Revenue Summary"', 'style A1:F1 bold size:16 fill:#1a1a2e color:#FFFFFF'])

  4. sheets(['data A2', '| Month | Revenue | COGS | Gross Profit |', '|-------|---------|------|--------------|', '| Jan | 500000 | =B30.22 | =B3-C3 |', '| Feb | 600000 | =B40.22 | =B4-C4 |', '| Total | =SUM(B3:B4) | =SUM(C3:C4) | =SUM(D3:D4) |', 'data end'])

  5. sheets(['style A2:D2 bold fill:#4472C4 color:#FFFFFF', 'style B3:D6 fmt:$#,##0', 'freeze A3', 'width A 14', 'width B:D 16'])

  6. sheets(['chart add column title:"Revenue" data:B3:B4 categories:A3:A4'])

  7. sheets_session('save as:./report.xlsx')

CONVENTIONS:

  • Use data blocks for tables/grids — never set cells one-by-one

    • Batch multiple ops in one sheets() call for efficiency

    • Values beginning with = are formulas

    • Quoted strings are text; bare numbers are numeric

    • Active sheet is implicit target; use sheet:NAME for cross-sheet

    • Call sheets_help after context truncation for full reference

Inputs: ops (required).

sheets_helpA

Returns the sheets reference card with all syntax.

Inputs: no named parameters.

sheets_queryD

Query sheets state.

Inputs: q (required).

sheets_sessionD

Session: 'new "Title"', 'open ./file', 'save', 'checkpoint v1', 'undo', 'redo'

Inputs: action (required).

Tool Schema Changelog

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

Loading tool schema changelog…

    TDQS

    B3.1/5.0

    Scored across 4 tools

    Disambiguation5/5

    The four tools have clearly distinct purposes: 'sheets' executes operations, 'sheets_query' queries state, 'sheets_session' manages sessions, and 'sheets_help' returns documentation. There is no ambiguity or overlap.

    Naming Consistency5/5

    All auxiliary tools follow the 'sheets_<verb>' pattern, while the primary tool is simply 'sheets'. This is a consistent and predictable naming convention.

    Tool Count5/5

    With only 4 tools, the server is highly focused. The main 'sheets' tool encapsulates a vast DSL for all spreadsheet operations, making the count appropriate for its comprehensive purpose.

    Completeness5/5

    The server covers an extensive range of spreadsheet operations: cells, sheets, styles, charts, tables, conditional formatting, data validation, editing, and more. Combined with session management and querying, it is very complete.

    Maintenance

    ActivityInactive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      C
      quality
      B
      maintenance
      Local-first Excel MCP server for AI agents enabling structured reads, workbook introspection, and safer .xlsx mutation without Microsoft Excel or LibreOffice.
      83
      314 PyPI
      3
      MIT
    • A
      license
      Not graded
      quality
      D
      maintenance
      MCP server that turns Excel files into queryable databases, enabling AI agents to filter, aggregate, group, sort data and export results as new Excel files.
      2
      MIT