S'limane ChanchoulExcel MCP server demo

Claude answers questions from your Excel files, and cannot change them

I asked Claude a business question about a 2,000-row sales workbook. Using only the tools of this MCP server, it answered in 30.5 seconds with 8 tool calls and saved an Excel report. A separate check of 26 claims in its answer found 25 right and one wording slip: it calls a 42% share "most".

Code available on request.
Answer time (real session)30.5 s
Tool calls by Claude8
Claims in the answer that check out25 / 26
Automated tests passing81

The question

Using the Excel tools: which region lost the most margin to discounts last quarter (Q3 2026), and how does that compare with Q2? Name the products behind it. Then save a short Excel summary report. Keep your answer short.

Claude's answer (unedited)

West lost the most margin to discounts in Q3 2026.

Product Q2 Q3
USB-C dock $1,486 $4,760
Office chair $1,850 $4,469
Keyboard $2,599 $4,149
34-inch ultrawide $2,049 $4,069

I calculated discount cost as units × unit_price × discount, which assumes unit_price is the price before the discount.

The report is saved at reports/Discount_Margin_Loss_Q3_vs_Q2_2026.xlsx. It has a Summary sheet, a region-by-quarter table and a West product-by-quarter table.

Check: 25 of 26 claims hold (numbers, names, method and report file). Underlined, one wording slip: Office chair is $6,592 of South's $15,746, which is 42%: the largest part, but not "most of it". The report's last key point repeats it.

What Claude did

  1. list_files List the files in the data folder
    → 2 files: returns_2026.csv, sales_2026.xlsx
  2. list_sheets file: sales_2026.xlsx
    → Orders (2,000 rows), Products (8 rows), Regions (5 rows)
  3. describe_sheet file: sales_2026.xlsx · sheet: Orders
    → Orders: 2,000 rows; columns order_id (text), date (date), region (text), product (text), units (integer), unit_price (number), discount (number)
  4. describe_sheet file: sales_2026.xlsx · sheet: Products
    → Products: 8 rows; columns sku (text), product (text), category (text), list_price (integer), unit_cost (number)
  5. query gross = units × unit_price · discount_cost = gross × discount · date between 2026-04-01 and 2026-09-30 · group by region, date:quarter · sum of discount_cost · sum of gross · sort region, date
    → Error: Unknown sort column 'date'. Available columns: region, date:quarter, discount_cost, gross_sales.
  6. query gross = units × unit_price · discount_cost = gross × discount · date between 2026-04-01 and 2026-09-30 · group by region, date:quarter · sum of discount_cost · sum of gross · sort region, date:quarter
    → 1,363 orders matched → 10 result rows
  7. query gross = units × unit_price · discount_cost = gross × discount · date between 2026-04-01 and 2026-09-30 · group by region, product, date:quarter · sum of discount_cost · sort -discount_cost
    → 1,363 orders matched → 80 result rows
  8. write_report name: Discount_Margin_Loss_Q3_vs_Q2_2026 · 2 tables, computed by the server from a query · 4 key points
    → Saved reports/Discount_Margin_Loss_Q3_vs_Q2_2026.xlsx with sheets Summary, Region by Quarter, West Products by Quarter

One call failed on a wrong sort column. The error message listed the valid columns, and Claude fixed it on the next call.

The report it saved

Summary sheet of the Excel report Claude saved: title, four key points, and a table of contents for two data sheets
Discount_Margin_Loss_Q3_vs_Q2_2026.xlsx, written by the server during the session. The two data sheets were computed by the server from the source file, so no number was retyped by the model. The key points were written by Claude; the last one has the same "most of that" slip.

The 6 tools

  • list_files: the Excel and CSV files in the folder
  • list_sheets: sheets with row and column counts
  • describe_sheet: columns, types, empty cells, 5 sample rows
  • read_rows: raw rows, paged
  • query: filters, groups by month or quarter, sums and averages, simple computed columns
  • write_report: saves a new formatted .xlsx with a summary sheet

Safety

  • Reads one folder only. ../, absolute paths, hidden files and links that lead outside are refused.
  • Source files are never changed. Tests check their hashes before and after.
  • Reports are always new files. Nothing is overwritten.
  • No code runs: fixed lists of filters and calculations, no eval, no formulas.
  • Limits on file size, rows loaded and rows returned per call.
  • Clear error messages, so Claude can fix its own mistakes.

Built for Claude Desktop and Claude Code

It is a standard MCP server (stdio). The session above was recorded in Claude Code. Claude Desktop starts it with the same command from its config file.

claude mcp add excel -- /path/to/.venv/bin/mcp-excel --root /path/to/excel-folder --out /path/to/reports

Synthetic data generated for this demo: 2,000 orders from January 1 to September 30, 2026, 8 products, 5 regions. All names and numbers are invented. Session: Claude Code 2.1.287, Claude Opus 5.5. Built with Python, the official MCP Python SDK (tests pass on versions 1.30.0 and 2.2.0), pandas and openpyxl. 3 sessions saved: run-1 failed on my setup (the server did not connect), run-3 found the same main figures but wrongly said the data has no cost column; this page shows run-2. Video: a replay built only from the saved session files (59 s, no sound). Code available on request.