Skip to main content

text-spreadsheet for Python

Let a language model read a whole multi-sheet spreadsheet as text. The model calls one tool with the path of a file, or a folder of files, on your machine, and Excel, ODS, CSV, JSON, SQLite, Parquet, Arrow and MTSV come back as MTSV: one plain-text file where a tab separates fields, a line break separates records, and a form feed separates sheets.

A language model reads text, so a binary workbook is a dead end — without pandas or openpyxl in the loop, it cannot see inside an .xlsx at all. This converts the whole file, every sheet, in one call, so the model reads a spreadsheet the way it reads any other text file.

Install

In an environment of its own:

pipx install text-spreadsheet

Then tell your host to launch text-spreadsheet, with no arguments. It is one entry, once — after that the tool is in every session. Most hosts read a configuration file:

{
  "mcpServers": {
    "text-spreadsheet": {
      "command": "text-spreadsheet"
    }
  }
}

Some hosts use servers instead of mcpServers and add a type, and some have their own command that writes the file for you.

The version is the version field of pyproject.toml, and versions follow Semantic Versioning.

The tool

The model calls read with a path, of a file or of a folder:

read("/home/me/book.xlsx")

and gets a map: the files, their sheets, how many records each holds and how many a filter keeps, the columns of each, where each sheet's lines are, and what each conversion left behind.

<FF>file
file	source	artifact	converted	status
1	/home/me/book.xlsx	/home/me/.cache/text-spreadsheet/home/me/book.xlsx.mtsv	yes	read
<FF>sheets
sheet	file	sheet name	records	matches	first line	last line
1	1	People	4	4	1	6
2	1	Orders	200	200	7	208
<FF>columns
sheet	position	field name
1	1	Name
1	2	Age
<FF>left behind
file	what
1	cell type n

Adding a position returns that part instead of the map:

read("/home/me/book.xlsx", sheet="2")
read("/home/me/book.xlsx", sheet="2", rows="40-120")
read("/home/me/book.xlsx", sheet="2", rows="40-*", fields="2;4")

Each position counts from 1 and is written as 2, 1;3, 1-3 or 2-*, as RFC 7111 writes a selection of a tabular file. Positions that do not exist are left out, and a sheet left with no field comes back empty; an address not written that way is refused.

A filter keeps the records whose fields meet a condition, written as an RFC 9535 filter: @[0] is a record's first field.

read("/home/me/book.xlsx", filter="@[2] == 'open'")
read("/home/me/book.xlsx", sheet="2", rows="1-50", filter="search(@[1], 'Ltd')")

With no position, the map counts the matches of each sheet; with one, rows counts the records the filter keeps. Text is compared exactly, and patterns are I-Regexp: [0-9], not \d.

A folder is read without its subfolders and without names that begin with a dot, in name order, and its sheets are numbered across its files. A file with no format, or one that cannot be read, is named in the map with its status, and the others are still read.

A file that holds a tab or line break inside a value cannot be converted, as MTSV cannot hold one: named directly, it is refused with the reason, and in a folder it is named as failed.

What is kept

Every conversion leaves an MTSV copy of the file under your cache directory — ~/.cache/text-spreadsheet on Linux, ~/Library/Caches/text-spreadsheet on macOS — mirroring the path of the file it came from, with .mtsv added to its name, and what the conversion left behind beside it, with -metadata.mtsv added. A later call reads that copy unless the file has changed since, and the map names where it is, so it can also be read directly. A copy that cannot be written, as on a full disk, is skipped: the read goes on, and the map names no copy. An MTSV file is read where it is, and not copied.

Nothing else is kept, and nothing outside this machine is contacted.

As a library

from text_spreadsheet import read

text = read("/home/me/book.xlsx")

read returns MTSV text, whatever the format it came from, and takes the same sheet, rows, fields and filter as the tool. Whatever a spreadsheet holds that MTSV does not — formatting, formulas, types — is left behind, and named in the map.

Layout

Each module hides one decision, named beside it.

src/text_spreadsheet/
  _refusal       what a refusal is: a call the tool cannot answer, and why
  _field         what a field can hold, and how text it cannot is written
  _positions     how a list of positions is written, and what it names
  _filter        how a filter is written, and which records it keeps
  _selection     which sheets, records and fields a call names
  _left_behind   what an integration left behind, as it reported it
  _cache         where a copy and its metadata live, and when fresh
  _conversion    how one file becomes MTSV text
  _input         what a path names: a file, or a folder of files
  _map           what a group of files is made of
  __init__       the operation: input, selection, and the reply
  _server        the tool, the server, and how a refusal is reported
  __main__       the command a host launches
tests/           one test file per module above

Use points one way: __main__ to _server to read; read to _input, _filter, _selection and _map; _input to _conversion and _field; _conversion to _cache, _field and _left_behind; _map to _field; _selection to _positions; and _server, _input, _conversion, _filter and _positions to _refusal. Nothing points back up, and nothing but _server knows the protocol.

Test

From the root of the repository:

python3 -m venv .venv
.venv/bin/pip install ./python
.venv/bin/python -m unittest discover -s python/tests

License

MIT

Metadata

Release files for text-spreadsheet 0.3.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for text-spreadsheet 0.3.1
File Size Uploaded
text_spreadsheet-0.3.1.tar.gz 19.1 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for text-spreadsheet 0.3.1
File Interpreter ABI Platform
text_spreadsheet-0.3.1-py3-none-any.whl Python 3 none any Details

Total release size: 40.5 kB

Release files / text_spreadsheet-0.3.1.tar.gz

Download URL text_spreadsheet-0.3.1.tar.gz
Size 19.1 kB
Tags Source
SHA-256 checksum
How to use checksums
6702062eb1977e96c23cee851f0b33d2e9aef3fe90fcd8b46e71824e5351639c
BLAKE2b-256 checksum
How to use checksums
ebf255286d472d7bef5f528aa429399625ad08661ce0cd264c4ba5ac1bf01689
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release files / text_spreadsheet-0.3.1-py3-none-any.whl

Download URL text_spreadsheet-0.3.1-py3-none-any.whl
Size 21.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
224c5c0202f039fc2cc0ae300b3d9d8f2970d176e80e9d8bb614392c62d0bf8e
BLAKE2b-256 checksum
How to use checksums
c7c28e729f2fef48bca8a27b06ae942baae2a184a24ba11cd859ce34c0cc9b5d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.4

Release history Release notifications | RSS feed

0.5.0

2 release files

0.4.1

2 release files

0.4.0

2 release files

This release

0.3.1 This release

2 release files

0.3.0

2 release files

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page