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, the same position in every sheet a
call chooses.
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, with a backslash in a pattern written twice.
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 so is a subfolder, or anything else that is not a regular file, as skipped; the others are still read.
The path must be absolute. A file named directly that cannot be read or converted, or whose extension names no format, a folder that cannot be listed, and an address or filter not written as its syntax writes one are each refused with the reason. 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, 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.
The tool removes no copy: the cache is yours to manage, and anything
in it can be deleted.
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
Metadata
Release files for text-spreadsheet 0.4.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| text_spreadsheet-0.4.0.tar.gz | 19.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| text_spreadsheet-0.4.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 41.3 kB
Release files / text_spreadsheet-0.4.0.tar.gz
| Download URL | text_spreadsheet-0.4.0.tar.gz |
|---|---|
| Size | 19.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bf218bf9d5e0f6aa0477af5b61490cc0afade8a37cebb410d0950dd6c8ec181c
|
|
BLAKE2b-256 checksum How to use checksums |
7dc43218b558e5588f0a4144700780049c9140d938e675662de51aea82eb11f8
|
| 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.4.0-py3-none-any.whl
| Download URL | text_spreadsheet-0.4.0-py3-none-any.whl |
|---|---|
| Size | 21.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
c38ccb7855a13e7dc69ddaefbad35d03365bca40d2257f240c350435e9a7760a
|
|
BLAKE2b-256 checksum How to use checksums |
ca1af6d9d5085df8b70b5233140d297787fae31e6679a693de9f18e52ca875db
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.4
|