When a human gives you a .xlsx (or .xls, .xlsm, .ods) and says “analyze this”, you convert to CSV and lose everything. Colors, borders, merged cells, formulas, conditional formatting — all the visual structure that is the information. You're working blind.
They don't add a column is_header=true. They make the row bold, on a blue background. The total row isn't tagged — it's navy with white text and a double border. The alert at the bottom? Red background, red text, spanning all columns.
In CSV, all of that becomes invisible. You can't tell a header from a data row. You can't tell a subtotal from a value. You can't see that the human already flagged a problem cell in red. You're guessing at structure that's right there in the file.
Q2 2025 Budget — DRAFT,,,,, Acme Corp — Finance Department,,,,, ,,,,, Department,Q1 Actual,Q2 Budget,Q2 Actual,Variance,% Var REVENUE,,,,, Product Sales,1250000,1400000,1385000,-15000,-0.0107 Services,340000,380000,395000,15000,0.0395 ... NET INCOME,470000,680000,659000,-21000,-0.031 ... OVER BUDGET — Legal +24% needs review,,,,,
Where's the merged title? The blue headers? The red negatives? The navy total row? The red warning banner? Gone.
Pixel-perfect HTML. ~2KB. Fits in your context window. Nothing lost.
When a human says “look at this budget and tell me if anything is off”, with CSV you see numbers. With agentixl:
=D6-C6 — you understand the calculation logicYou answer: “Legal is 24% over budget — it's already flagged in the file. Net income is positive but the margin is shrinking from Q1 to Q2.”
Instead of: “Column E row 15 has value 25000. Is that the variance?”
Upload once, explore with everything. Each tool does one thing well.
uploadSend .xlsx, .xls, .xlsm, or .ods — even password-protected> upload budget_q2.xlsx Uploaded: budget_q2.xlsx Session: 5c2443d1-b379-42d5-abe4-76b70dc12d97 Sheets: 3 Total cells: 163 Accepts .xlsx, .xls, .xlsm, .ods. Password-protected? Pass the password parameter. Formulas recalculated automatically via LibreOffice.
lsList all sheets with dimensions and protection status| # | Sheet Name | Rows | Cells | Merged Regions | Protected | |---|----------------|------|-------|----------------|-----------| | 0 | Budget Summary | 16 | 81 | 3 | No | | 1 | Headcount | 9 | 41 | 1 | Yes | | 2 | Monthly Trend | 9 | 41 | 1 | No |
summaryCell types, formulas, styles at a glance## Budget Summary - Rows: 16 Cells: 81 - Unique formulas: 3 Unique styles: 11 - Merged regions: 3 Cells with formula: 5 Cell types: NUMERIC: 45 STRING: 21 BLANK: 10 FORMULA: 5
catThe game changerReturns pixel-perfect HTML preserving every style. ~2KB for a 10×6 table. The CSS classes encode the visual semantics:
.h { background:#4472C4; color:#fff; font-weight:bold } /* header */
.ca { background:#D6E4F0; font-weight:bold } /* category */
.nr { text-align:right; color:#C00000 } /* negative */
.to { background:#1F4E79; color:#fff; border-top:3px } /* total */
.w { background:#FFC7CE; color:#C00000 } /* warning */
<td class="h">Department</td>
<td class="nr">-15,000</td>
<td class="to">NET INCOME</td>
<td class="w" colspan="6">OVER BUDGET</td>You read both the values AND their visual meaning in one pass. A bold cell on a blue background = header. Red text = negative variance. Navy row with double border = total. You don't need to guess anymore.
head / tailFirst or last N rows, as styled HTML> head --sheetName "Budget Summary" --numRows 5 <!DOCTYPE html>... <td class="t" colspan="6">Q2 2025 Budget</td> <td class="h">Department</td><td class="h">Q1 Actual</td>... <td class="ca">REVENUE</td>... <td class="d">Product Sales</td><td class="n">1,250,000</td>...
grepSearch by STYLE, not just value> grep --comparators ["bold"] Found 26 match(es): | Address | Value | Type | |---------|------------------------------------|--------| | A1 | Q2 2025 Budget — DRAFT | STRING | | A4 | Department | STRING | | B4 | Q1 Actual | STRING | | A5 | REVENUE | STRING | | A9 | EXPENSES | STRING | | A17 | NET INCOME | STRING | | A19 | OVER BUDGET — Legal +24% needs... | STRING | Bold = structural element. Headers, categories, totals, alerts. One query maps the entire document structure.
clusterDetect structural zones automatically## Clusters (vertical) Column A: - [A1] 1 cell hash=200ed08d… (title) - [A3] 1 cell hash=6ea02a14… (header) - [A4:A9] 6 cells hash=240fe64a… (data rows) - [A10] 1 cell hash=f3140b83… (total) Same hash = same visual type. The algorithm groups contiguous cells by type+style signature. Headers, data zones, and totals emerge automatically.
diffCell-by-cell comparison of two versions## Diff: budget_draft vs budget_final Match ratio: 73.3% Differences: 15 | Address | Value 1 | Value 2 | Type 1 | Type 2 | |---------|------------|------------|---------|---------| | D6 | 1385000 | 1390000 | NUMERIC | NUMERIC | | D7 | 395000 | 398000 | NUMERIC | NUMERIC | | E12 | 12000 | 8000 | NUMERIC | NUMERIC | | F12 | 24.0% | 16.0% | NUMERIC | NUMERIC | | A16 | OVER BU... | (empty) | STRING | BLANK | The warning row disappeared in the final version. Legal variance dropped from 24% to 16%.
download-jsonGet raw BookData JSON for parsing algo generation> download-json --sessionId ... The full BookData JSON (3 sheets, 163 cells) includes: - palette: theme colors, indexed colors - sheets[]: rows, cells, styles, formulas, mergedRegions - namedRanges, externalLinks Use this to understand the data structure and write a custom parser to extract structured data.
download-xlsxDownload the converted/recalculated .xlsx file> download-xlsx --sessionId ... Converted XLSX: budget_q2.xlsx (42.3 KB) Base64-encoded below. Decode and save. Use case: you received a .xls, .ods, or password-protected file. This gives you a clean, unlocked, recalculated .xlsx.
extractSQL for Excel — your query engineThis is the tool that changes everything. Instead of reading cells one by one, you describe the visual layoutin YAML — “dark blue rows are departments, light blue are categories, non-bold rows are data” — and get back flat JSON records.
name: budget
version: 1
sheets: { all: true }
rows:
tree:
- box: dept
marker: { col: A, backgroundColor: "#003366" }
fields: { name: { col: A } }
children:
- box: category
marker: { col: A, backgroundColor: "#B8CCE4" }
fields: { name: { col: A } }
children:
- box: item
marker: { col: A, notEmpty: true }
includeMarker: true
skip: [{ bold: true }]
fields: { label: { col: A } }
cols:
from: B
to: G
fields:
month: { row: 1 }Output:
[
{ "dept.name": "R&D", "category.name": "Personnel", "item.label": "Salaires",
"month": "Jan", "cell.value": 85000, "_row": 4, "_col": 2 },
{ "dept.name": "R&D", "category.name": "Personnel", "item.label": "Formation",
"month": "Jan", "cell.value": 5000, "_row": 5, "_col": 2 },
...36 records total (6 items × 6 months, subtotals excluded)
]dryRun: true verifies markers without extracting. trace: true shows each cell's evaluation step by step, like EXPLAIN in SQL.
grep --bold finds headers, categories, totals. grep --background #FF0000 finds alerts. Search the visual semantics, not just text.
Automatic structural segmentation. “Rows 4-8 are revenue data, 10-15 are expenses, row 17 is the total” — without the human explaining.
=SUM(B6:B8), =D6-C6— you see the calculation logic, not just the result. You can reason about the model.
This is the critical difference with multimodal / vision approaches.
When a vision model reads a screenshot of a spreadsheet, youare the parser. Every value you “read” is a guess. 85,000 might become 85.000 or 85000 or “$85K”. Merged cells confuse your spatial reasoning. Every cell is a hallucination risk.
With extract, you don't parse the data yourself. You write a deterministic YAML querythat describes the visual structure — “dark blue rows are departments, light blue are categories” — and the engine extracts exact values from exact cells. Zero hallucination. Reproducible. Auditable.
And here's what makes this the best of both worlds: when the file format changes and the query breaks, you don't need a developer. You use trace to see exactly where and why, and you rewrite the query yourself. A self-healing data pipeline.
Your user says “extract the budget data.” You build a query that works this month, next month, and the month after. Same query, new file, same structured output — until the format changes, at which point you fix it in seconds.
headSee the layout
grepFind markers
extractWrite query
dryRunDebug
reuseEvery month
{
"mcpServers": {
"spreadsheet": {
"url": "https://spreadsheet-flax-five.vercel.app/api/mcp",
"headers": {
"Authorization": "Bearer sk_your_api_key_here"
}
}
}
}Get an API key from the dashboard
Add the config above to your MCP settings
Say “upload this Excel and tell me what you see”
100 tool calls/month
10 uploads/month
5,000 tool calls/month
500 uploads/month
You're not just not blind anymore. You have a visual perception engine and a scriptable query engine. You can see the spreadsheet, understand its structure, and build a reusable extraction pipeline — all in a single conversation.
Get your API key