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

ParametersJSON Schema
NameRequiredDescriptionDefault
opsYes

TDQS

A4.6/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description fully discloses behavior: operation syntax, all subcommands, response prefixes, and conventions. It covers destructive actions (clear, delete) and provides a comprehensive reference.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is very long and detailed, but structured into sections. It is not concise; many details could be moved to a help file. However, the front-loading of the operation format and conventions helps usability.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool's complexity, lack of schema descriptions, output schema, or annotations, the description is remarkably complete. It covers all operation categories, response indicators, and usage conventions, leaving minimal ambiguity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 0%, so the description compensates fully. It explains the 'ops' array format (VERB TARGET [key:value ...]) and provides exhaustive examples, adding immense semantic value beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states it 'Execute sheets operations' and provides a comprehensive list of subcommands, distinguishing it from siblings like sheets_query (querying) and sheets_help (reference).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

While the description implies this tool is for executing modifications and mentions sheets_help for reference, it does not explicitly contrast when to use this versus siblings for reading or session management. The example workflow demonstrates typical usage.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheets_helpA

Returns the sheets reference card with all syntax.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It correctly describes a straightforward read-only operation with no side effects, which is sufficient for this simple help tool.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single, concise sentence with no wasted words. Every part adds value, and it is front-loaded with the key verb and noun.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no parameters, no output schema, and the simple nature of a help tool, the description is complete. It fully explains what the tool does without needing additional context.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, and the description does not need to add parameter details. According to the rubric, 0 parameters starts at a baseline of 4, and the description is adequate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Returns' and the resource 'sheets reference card', specifying that it returns all syntax. This is specific and distinguishes it from sibling tools like sheets, sheets_query, and sheets_session, as it is a help/reference tool.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies usage for obtaining syntax reference but provides no explicit guidance on when to use this tool vs alternatives like sheets_query or sheets_session. No when-not-to-use or prerequisite context is given.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheets_queryD

Query sheets state.

ParametersJSON Schema
NameRequiredDescriptionDefault
qYes

TDQS

D1.3/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations provided, the description must fully convey behavioral traits. It only states 'Query sheets state', omitting crucial details such as read-only nature, side effects, access requirements, or output behavior. The description is insufficient for safe invocation.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is extremely short (three words), but this is under-specification rather than conciseness. While it is front-loaded, it lacks necessary details to be useful.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given the tool has one required parameter and no output schema, the description is severely incomplete. It does not explain what 'sheets state' means, the expected query format, or what the tool returns, leaving major gaps for the agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has one parameter 'q' with zero description coverage. The tool description does not mention or explain 'q' at all, failing to add meaning beyond the schema's bare type definition.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description 'Query sheets state' is largely tautological with the tool name 'sheets_query'. It adds the word 'state' but fails to specify what 'state' refers to or how it differs from sibling tools like 'sheets', 'sheets_session', and 'sheets_help'. Purpose is vague and not distinct.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is provided on when to use this tool versus alternatives. The description does not mention contexts, prerequisites, or exclusions, leaving the agent without criteria for tool selection.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sheets_sessionD

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

ParametersJSON Schema
NameRequiredDescriptionDefault
actionYes

TDQS

D1.5/5.0
Behavior1/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations provided. The description only gives example action strings without explaining behavior, side effects, permissions, or what happens on execution. Completely opaque.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single line, which is concise, but it sacrifices clarity. It lists commands without explanation, making it under-informative.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with one parameter and no output schema, the description fails to explain the tool's purpose, valid input format, and relationship to siblings. Incomplete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The only parameter 'action' has no schema description (0% coverage). The description hints at possible values via examples, but the format is unclear (e.g., quotes, slashes). It partially compensates but remains ambiguous.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description lists example actions like 'new "Title"' and 'open ./file' but does not clearly state what the tool does. It is ambiguous whether this tool executes session operations or lists commands. No verb+resource clarity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance on when to use this tool versus siblings (sheets, sheets_query, sheets_help). No indication of appropriate context or alternatives.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections. Dates show when Glama detected each change.

  1. 4 tool updatesv0.2.0
    • First observedsheets
    • First observedsheets_help
    • First observedsheets_query
    • First observedsheets_session

TDQS

B3.1/5.0
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

ActivitySlowing
ResponsivenessSyncing

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Connectors

Related MCP Servers

  • A
    license
    C
    quality
    A
    maintenance
    Local-first Excel MCP server for AI agents enabling structured reads, workbook introspection, and safer .xlsx mutation without Microsoft Excel or LibreOffice.
    78
    2
    MIT
  • A
    license
    Not graded
    quality
    C
    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

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/os-tack/fcp-sheets'

If you have feedback or need assistance with the MCP directory API, please join our Discord server