Converts structured data like JSON, CSV, and Parquet into the TOON format to reduce LLM token usage based on estimated savings.
Also known as andrii-su/datoon
- Stars
- 2
- Tools
- —
- Downloads, 30 days
- 154
- Latest version
- 3.0.0
- Last commit
- Nov 12, 2026
- License
- MIT
What is datoon MCP?
datoon converts structured data like JSON, CSV, and Parquet into the TOON format to reduce LLM token usage based on estimated savings.
Summary generated from the project's README
About datoon MCP server
datoon acts as an intelligent gateway for transforming structured data into the TOON format to minimize LLM token consumption. It is designed for developers building AI pipelines who need to reduce prompt costs without risking data corruption. The server evaluates whether a conversion will actually result in significant savings before applying it, ensuring that non-uniform or deeply nested structures remain in their original format.
Users can interact with the server through a command line interface, a Python API, or as an MCP server for clients like Claude Desktop, Cursor, and Windsurf. It supports a wide range of formats including JSON, CSV, YAML, XML, Parquet, Avro, Excel, and Apple Numbers. The system uses various tokenization methods to estimate savings, ranging from local counters like tiktoken to remote options like the Claude Anthropic API or Hugging Face tokenizers. Every conversion includes a detailed report explaining the decision and the estimated savings achieved.
What you can do with it
- 1Automate data pipeline optimizationIntegrate a decision layer into a data pipeline to automatically switch between JSON and TOON based on payload shape.
Written from the project's README. Every use case is checked against a quote from it.
How to install datoon MCP
Formats verified 10 Nov 2026Add to CursorAdd to GooseInstall datoon MCP in Claude Code
$ claude mcp add datoon -- uvx datoonInstall datoon MCP in Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"datoon": {
"command": "uvx",
"args": [
"datoon"
]
}
}
}Install datoon MCP in VS Code
.vscode/mcp.json
{
"servers": {
"datoon": {
"type": "stdio",
"command": "uvx",
"args": [
"datoon"
]
}
}
}Install datoon MCP in VS Code CLI
$ code --add-mcp '{"name":"datoon","type":"stdio","command":"uvx","args":["datoon"]}'Install datoon MCP in Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"datoon": {
"command": "uvx",
"args": [
"datoon"
]
}
}
}Install datoon MCP in Codex
$ codex mcp add datoon -- uvx datoonInstall datoon MCP in Codex config.toml
~/.codex/config.toml
[mcp_servers.datoon] command = "uvx" args = ["datoon"]
Install datoon MCP in Gemini CLI
$ gemini mcp add -s user datoon uvx -- datoonInstall datoon MCP in Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"datoon": {
"command": "uvx",
"args": [
"datoon"
]
}
}
}Automated checks
Ran Nov 13, 2026Full report →Passed our automated checks. That is not a security audit: read the code and give the server the narrowest access it needs. How checks work
- Source repository linked and reachablePass
- Open source license (OSI)Pass
- Commit or release in the last yearPass
- Package name not close to a popular packagePass
- Remote endpoint uses HTTPSNot checked
- Destructive tools are labelledNot checked
- No hidden instructions to the modelPass
- No critical advisory in the package or its dependenciesPass
README
View on GitHubRaw structured data is often verbose in LLM prompts. TOON can save tokens — but blind conversion can also make payloads worse. datoon adds a decision layer: convert when structure and savings justify it, skip when they don't, and always explain why.
Supports JSON, CSV, JSONL, YAML, XML, Parquet, Avro, ORC, Excel, and Apple Numbers — auto-detected from file extension.
Before / After
JSON in the prompt (43 tokens)
{"users":[
{"id":1,"name":"Ada","role":"admin"},
{"id":2,"name":"Lin","role":"analyst"},
{"id":3,"name":"Grace","role":"viewer"}
]}
datoon converts → TOON (24 tokens)
users[3]{id,name,role}:
1,Ada,admin
2,Lin,analyst
3,Grace,viewer
{"decision":"convert","reason":"Estimated savings 44.19% (threshold 15.00%)."}
CSV from a data pipeline (111 tokens as JSON)
id,name,role
1,Ada,admin
2,Lin,analyst
3,Grace,viewer
datoon auto-converts → TOON (24 tokens)
datoon data.csv --report-stdout
Same result. Zero JSON serialization in your code.
Non-uniform payload (26 tokens)
{"config":{"debug":true},"tags":["a","b"]}
datoon skips → keeps JSON
{"decision":"skip","reason":"No uniform object arrays found with at least 3 rows."}
No silent corruption.
Same data. Right format. Always explained.
┌──────────────────────────────────────────────────┐
│ PAYLOAD SAVINGS (auto avg) ████░░░░░░ 28% │
│ PAYLOAD SAVINGS (agent skill) ████████░░ 62% │
│ DECISION ACCURACY ██████████ 100% │
│ HARMFUL CONVERSIONS BLOCKED ██████████ 100% │
└──────────────────────────────────────────────────┘
[!IMPORTANT] datoon saves payload tokens — the structured data portion of your prompt. Token savings depend on payload shape: uniform tabular data converts well; deeply nested or non-uniform structures are skipped. Every decision includes a reason so pipelines can log, debug, and trust the outcome.
Install
# core (JSON, CSV, JSONL, XML — no extra deps)
uv add datoon
pip install datoon
# with YAML support
pip install "datoon[yaml]"
# with Excel support
pip install "datoon[excel]"
# with Parquet / ORC / Avro support
pip install "datoon[columnar]"
# with Apple Numbers support
pip install "datoon[numbers]"
# with tiktoken-based token counting
pip install "datoon[tokens]"
# with Claude token counting (Anthropic API) / Hugging Face tokenizers
pip install "datoon[claude]"
pip install "datoon[hf]"
# with MCP server
pip install "datoon[mcp]"
# everything
pip install "datoon[all]"
Requires Python 3.12+. TOON conversion is pure Python (official toon-format encoder, installed automatically) — no Node.js needed.
For Claude Code plugin, Codex, and MCP config → INSTALL.md.
What You Get
| What | |
|---|---|
datoon CLI |
Auto-gate any supported format → TOON from terminal or scripts |
| Python API | convert_json_for_llm() + read_tabular() for any LLM pipeline |
| MCP Server | convert_json, convert_text, analyze_json tools for Claude Desktop, Cursor, Windsurf |
| Claude Code Plugin | /datoon in-session trigger, installs from GitHub in one command |
| Codex Plugin | Marketplace plugin — structured-data mode for Codex |
Supported input formats
| Format | Extension | Extra needed |
|---|---|---|
| JSON | .json |
— |
| JSONL | .jsonl, .ndjson |
— |
| CSV | .csv |
— |
| XML | .xml |
— |
| YAML | .yaml, .yml |
datoon[yaml] |
| Excel | .xlsx, .xls |
datoon[excel] |
| Parquet | .parquet |
datoon[columnar] |
| Avro | .avro |
datoon[columnar] |
| ORC | .orc |
datoon[columnar] |
| Apple Numbers | .numbers |
datoon[numbers] |
How It Works
- Detect format — from
--formatflag, file extension, or default to JSON for stdin - Read + normalize — parse source into list of row dicts; serialize to compact JSON
- Analyze structure — uniform object arrays? acceptable depth? minimum rows?
- Gate early — non-candidates skip before any TOON encoding
- Convert + estimate — TOON encoder runs in-process, token savings calculated
- Gate savings — below threshold → return JSON; above → return TOON with report
Every path returns a ConversionReport with decision, reason, token estimates, and the tokenizer that produced them. Pipelines never get silent surprises.
Choosing a tokenizer
Token estimates drive the savings gate, so pick the counter that fits the job. Pass one spec with --tokenizer, the tokenizer MCP argument, ConversionConfig(tokenizer=...), or the DATOON_TOKENIZER environment variable; in Python any callable str -> int works too. The MCP tools accept only local counters (auto, chars, tiktoken) per call; claude: and hf: reach the network, so only the server operator can enable them through DATOON_TOKENIZER.
| Spec | Counts with | Needs |
|---|---|---|
auto (default) |
tiktoken:o200k_base when tiktoken is installed, else chars:3 |
— |
chars / chars:<N> |
ceil(len(text) / N), default N = 3 |
— |
tiktoken / tiktoken:<encoding> |
tiktoken, default o200k_base |
datoon[tokens] |
claude:<model-id> |
Anthropic token counting API for that model | datoon[claude], Anthropic credentials (e.g. ANTHROPIC_API_KEY) |
hf:<repo-id> |
Hugging Face tokenizers tokenizer from the Hub |
datoon[hf] |
| a callable (Python) | your own function, reported as custom |
— |
Use the local counters (auto, chars, tiktoken) to gate payloads at runtime: they are fast and run locally (tiktoken fetches its encoding file once on first use). Use claude: or hf: in benchmarks and evals where counts for the target model matter. claude: makes network calls, and Anthropic documents its counts as estimates. Only auto falls back silently: an explicit spec with a missing package fails with an error that names the extra to install, and claude: without Anthropic credentials fails too. Every report records the resolved name in tokenizer.
Quick Start
JSON (stdin):
echo '{"users":[{"id":1,"name":"Ada"},{"id":2,"name":"Lin"},{"id":3,"name":"Grace"}]}' | datoon --report-stdout
CSV (auto-detected from extension):
datoon data.csv --report-stdout
JSONL:
datoon data.jsonl -o output.toon
YAML (requires datoon[yaml]):
datoon data.yaml --report-stdout
Parquet (requires datoon[columnar]):
datoon data.parquet --report ./report.json
Explicit format override:
datoon --format csv < data.csv --report-stdout
Force conversion (bypass gating — for experiments):
datoon data.json --force --report-stdout
Python API
JSON conversion:
from datoon import convert_json_for_llm, ConversionConfig, DatoonError
config = ConversionConfig(min_savings_ratio=0.15, max_depth=6, min_uniform_rows=3)
try:
outcome = convert_json_for_llm(raw_json, config)
except DatoonError as exc:
raise
# outcome.payload_text — TOON or original JSON
# outcome.report.decision — "convert" | "skip"
# outcome.report.reason — human-readable explanation
send_to_model(outcome.payload_text)
Any format via read_tabular:
import json
from pathlib import Path
from datoon import read_tabular, convert_json_for_llm, ConversionConfig
# text formats: csv, jsonl, yaml, xml
rows = read_tabular("csv", text=csv_string)
# binary formats: excel, parquet, orc, avro, numbers
rows = read_tabular("parquet", path=Path("data.parquet"))
json_text = json.dumps(rows, separators=(",", ":"))
outcome = convert_json_for_llm(json_text, ConversionConfig())
send_to_model(outcome.payload_text)
Structure-only analysis:
from datoon.analyzer import analyze_payload
from datoon.models import ConversionConfig
analysis = analyze_payload(parsed_data, ConversionConfig())
print(analysis.is_candidate, analysis.reason)
MCP Server
datoon ships an MCP server with three tools:
| Tool | Description |
|---|---|
convert_json |
Full JSON conversion with policy gating |
convert_text |
Converts CSV, YAML, XML, or JSONL text with policy gating |
analyze_json |
Structure analysis only — no conversion |
Claude Desktop / Cursor / Windsurf config:
{
"mcpServers": {
"datoon": {
"command": "uvx",
"args": ["--from", "datoon[mcp]", "datoon", "mcp"]
}
}
}
Run locally:
datoon mcp # or the standalone script: datoon-mcp
Listed on the MCP Registry, Smithery, and Glama. See MARKETPLACES.md.
Claude Code Plugin
Install directly from GitHub:
claude plugin marketplace add andrii-su/datoon
claude plugin install datoon@datoon
Trigger in-session:
/datoon
convert this JSON to TOON if it saves tokens
use datoon mode for structured data
CLI Reference
| Flag | Default | Description |
|---|---|---|
--format |
auto | Input format: json, csv, jsonl, yaml, xml, excel, parquet, avro, orc, numbers |
--force |
false |
Bypass gating and minimum savings threshold |
--min-savings |
0.15 |
Minimum relative token savings required |
--max-depth |
6 |
Maximum nesting depth for auto-conversion |
--min-uniform-rows |
3 |
Minimum rows in uniform object arrays |
--tokenizer <spec> |
$DATOON_TOKENIZER, else auto |
Token counter for estimates — see Choosing a tokenizer |
--report <path> |
— | Write JSON conversion report to file |
--report-stdout |
— | Print JSON conversion report to stderr |
-o <path> |
stdout | Output file path |
--version |
— | Print version and exit |
Format is auto-detected from file extension. Use --format to override or when reading from stdin.
Benchmarks
PYTHONPATH=src python benchmarks/run.py --dry-run
PYTHONPATH=src python benchmarks/run.py
PYTHONPATH=src python benchmarks/run.py --update-readme
Why auto mode outperforms forced conversion
Auto mode avoids low-benefit and high-risk payloads (orders-nested, mixed-non-uniform) while matching forced TOON's average token count on suitable ones. Every decision comes with a reasoned report.
| Scenario | JSON Baseline | Forced TOON | datoon Auto |
|---|---|---|---|
| Average tokens | 77 | 50 | 50 |
| Avg token saved | 0.0% | 26.8% | 28.1% |
| Decision quality | n/a | Converts all | Converts 3/5, skips harmful cases |
| Dataset | JSON | TOON (forced) | Raw Saved | Auto | Auto Tokens | Auto Saved |
|---|---|---|---|---|---|---|
| users-small | 54 | 40 | 25.9% | convert | 40 | 25.9% |
| events-medium | 219 | 162 | 26.0% | convert | 162 | 26.0% |
| orders-nested | 106 | 116 | -9.4% | skip | 106 | 0.0% |
| mixed-non-uniform | 35 | 47 | -34.3% | skip | 35 | 0.0% |
| metrics-wide | 142 | 103 | 27.5% | convert | 103 | 27.5% |
| Average | 111 | 94 | 7.1% | 3/5 convert | 89 | 15.9% |
Forced conversion succeeded for 5/5 payloads.
Format conversion benchmark
Token savings when converting from common structured formats (CSV, JSONL, XML, YAML). Baseline is the JSON representation of the same data — what an LLM would receive without datoon.
| Dataset | Format | JSON Tokens | TOON (forced) | Auto | Auto Tokens | Auto Saved |
|---|---|---|---|---|---|---|
| users-csv | csv | 53 | 29 | convert | 29 | 45.3% |
| events-jsonl | jsonl | 194 | 109 | convert | 109 | 43.8% |
| catalog-xml | xml | 96 | 50 | convert | 50 | 47.9% |
| metrics-yaml | yaml | 129 | 61 | convert | 61 | 52.7% |
| Average | — | 118 | 62 | 4/4 convert | 62 | 47.4% |
Forced conversion succeeded for 4/4 payloads.
Agent skill evaluation
Artifact-based subagent comparison — identical analysis tasks, two modes:
with_skill: agent received thedatoonskill and followed the conversion workflow.without_skill: agent used JSON directly, no TOON ordatoon.
3 payload sizes × 3 iterations = 18 total agent runs. Both modes: 100% correct answers.
| Scenario | Avg JSON Tokens | Avg TOON Tokens | Avg Payload Saved |
|---|---|---|---|
| small | 225 | 118 | 47.6% |
| medium | 2,972 | 1,138 | 61.7% |
| large | 17,757 | 6,673 | 62.4% |
Full report and raw outputs: benchmarks/agent_skill_eval/. Savings are payload-token estimates, not full end-to-end model-token usage.
Development
Contributor workflow: CONTRIBUTING.md. Maintainer/agent notes: CLAUDE.md.
Setup:
uv sync --extra dev
uvx pre-commit install
Tests:
pytest
Skill sync + plugin metadata:
python scripts/validate_skill_sync.py
python scripts/validate_plugin_metadata.py
Links
- INSTALL.md — full install matrix, all targets, per-agent detail
- CONTRIBUTING.md — contributor workflow
- CLAUDE.md — maintainer guide for agents
- CHANGELOG.md — release history
- SECURITY.md — vulnerability reporting
- Live docs —
docs/ - Issues — bugs, features, questions
License
MIT
datoon MCP questions
How do I add datoon MCP to Claude Code?
Run claude mcp add datoon -- uvx datoon in your terminal. Add --scope user to make it available in every project.
How do I add datoon MCP to Cursor?
Open ~/.cursor/mcp.json and add the server under mcpServers, using the Cursor config shown in the install section above.
What formats does datoon support?
It supports JSON, CSV, JSONL, YAML, XML, Parquet, Avro, ORC, Excel, and Apple Numbers.
How does the decision to convert work?
The server analyzes the data structure and only converts to TOON if it meets a savings threshold and structural requirements, otherwise it keeps the original format.
Can I use different tokenizers for estimation?
Yes, you can use local counters like tiktoken or character counts, or network-based counters like Claude or Hugging Face tokenizers.