Skip to main content
Glama

jsonloupe is a browser-based workbench for inspecting, diffing, editing, and querying JSON documents that are too big or too precise for ordinary viewers — and for turning them into spreadsheets: nested JSON goes in, linked tables come out, as one .xlsx or a zip of CSVs. The questions you ask of every file can be kept as checks in a playbook: one press judges them all over the open document, and the same file gates CI through jsonloupe check. Nothing leaves your machine: in the browser, documents are parsed in a web worker, stored in IndexedDB, and never uploaded anywhere. No backend, no account, no telemetry.

Run it

Use the hosted app at jsonloupe.dev, rebuilt from main on every merge — or run the latest npm release from your own machine, fully offline:

npx jsonloupe

That starts a loopback-only static server (127.0.0.1:5199, or the next free port) serving the prebuilt app from the package — no runtime dependencies, no network calls, one auditable file of about 200 lines with its comments: bin/jsonloupe.mjs. --port <n> and --no-open do what they say; --key-file <path> serves a model key from that file to the page, over loopback only. The same command carries the converter's verbs (inspect, draft, convert, below) and the CI gate (check, below); npx jsonloupe --help lists them.

Related MCP server: Universal JSON Agent MCP

Why another JSON viewer?

  • Lossless numbers. Int64 IDs beyond 2^53 (snowflake/order/entity IDs) and precise decimals survive paste → view → edit → copy → download with their exact digits. Nothing is ever coerced to a float.

  • Built for huge documents. The tree is virtualized and expansion is chunked ([0 … 9999] range rows), so opening a five-million-element array is instant. Parsing, diffing, search, and compression all run off the UI thread.

  • Compressed payloads are first-class. Paste or drop raw .zst, Base64-Zstd (standard or URL-safe), or PostgreSQL \x… bytea — it's detected, decoded in the worker, and opened as a document with a transformation trace. An encoder and a transport-size inspector (exact UTF-8 / Zstd / Base64 / envelope bytes against editable budgets) round-trip the other way.

  • Nested JSON becomes a spreadsheet, on your machine. Repeating arrays and object maps become their own linked tables — orders and order_items joined by order_id, never items/0/sku columns. You see real rows before anything is written, and the mapping saves as a small file that produces the same columns from next month's file, with no model involved.

  • Semantic diff. Side-by-side compare with identity-based array alignment (match by id, composite keys, or auto-detected), so reordered arrays don't drown you in false changes. Ignore noisy fields by key or path prefix.

  • A real query language. A JSONPath subset with predicates and aggregation pipes ($.tasks[?(@.status == 'FAILED')] | group(@.failureReason)), executed locally with live preview as you type. Numeric literals, predicates, sums, averages, minima, and maxima remain exact for int64 and precise decimals.

  • Checks you can gate on. Keep the questions you ask of every file as checks — a query and the count it should come to — and one press judges them all, naming the first paths behind each failure. A count it could not finish is inconclusive, never a pass it has not earned. The same playbook file gates CI through jsonloupe check and an agent through MCP's run_checks.

Quick start

git clone https://github.com/priyanshuN/jsonloupe && cd jsonloupe
npm ci
npm run dev        # http://localhost:5199

Paste JSON on the landing page — it parses instantly and is saved to local memory (Recents sidebar: reopen, pin, delete). Malformed input (trailing commas, single quotes, Python literals, truncated log extracts) is auto-repaired and flagged, with the original bytes preserved.

Feature tour

  • Tree / Code / Split views — virtualized tree, editable CodeMirror code view (fold, search, apply-with-⌘S), or both side by side with click-to-line sync. Inline-edit primitives in the tree; full undo/redo across edits. A small document opens unfolded to fill the screen, and scrolling deep keeps the parent keys you are inside pinned at the top of the tree.

  • Command palette — ⌘K / Ctrl K from anywhere: type a few letters of a view, an action, a theme or a recent document's name, and press Enter.

  • Colour schemes — Tokyo Night, Catppuccin Mocha, Dracula, Gruvbox and GitHub Dark for dark; Solarized Light, Catppuccin Latte, Rosé Pine Dawn and GitHub Light for light; plus jsonloupe's own pair. Hover one in the sidebar picker (or arrow through them in ⌘K) to try it on the whole app. Each mode keeps its own scheme, so "follow the system" swaps between your two at sunset. Every scheme meets WCAG AA contrast, checked in CI.

  • Search & filter — / to search (literal or /regex/i), filter mode prunes the tree to matching branches and restores your expansion state after.

  • Path & identity — breadcrumb with one-click copy as JSONPath, JSON Pointer, or JS accessor; "same value" jumps to every node holding a value (schema-free correlation-ID tracing).

  • Value lenses — epoch → your local date and "3 days ago", the same relative time on ISO timestamps, a colour chip on colour strings, latitude/longitude, uuid/url/base64 flags, and byte weight on collapsed containers so the heavy node is findable at a glance.

  • Tables & CSV — any array gets a sortable table view; export exact-digit CSV (RFC 4180) of tables or query results.

  • Playbook — your checks and functions on one sheet — a check is a query and the count it should come to (none, at most 3 groups, between 1 and 10); run all judges every one over the open document and points at the first nodes behind each failure. A function is JavaScript for the questions no query language should have to answer, run in a sandbox with the network removed; tick several and one press answers all of them as a single report. The same checks gate CI through jsonloupe check. (below.)

  • JSON → Excel / CSV converter — rename and reorder columns, choose source fields, add constants, set date and coordinate handling, take the column names from a target CSV header, and save or share the mapping for the next file. (below.)

  • File handles — drop .json/.jsonl/.zst files; reload re-reads from disk and shows what changed since the version you were looking at.

  • Query — run JSON queries locally, save useful ones, or turn a result into a reusable check. Optional English → query mode translates a question using only the document's shape (field names/types — never values), then stops so you can review the generated query before running it locally. Bring your own connection: authorize with OpenRouter without pasting a key, or use the advanced manual-key option — paste a key, or load it from a local file (a raw key or a .env line, read in the page and never uploaded). Until you explicitly connect, the feature is inert. A disclosure panel records exactly what was sent. See SECURITY.md.

Browser query cookbook

Open a document, press query, leave JSON query selected, and paste a query. It runs directly in this tab: no API key and nothing sent to a model.

Given this small document:

{"tasks":[
  {"id":9007199254740992,"status":"FAILED"},
  {"id":9007199254740993,"status":"READY"}
]}
  • Filter to the failed rows:

    $.tasks[?(@.status == 'FAILED')]

    Result: one match, $.tasks[0].

  • Count rows by status:

    $.tasks[*] | group(@.status)

    Result: FAILED 1, READY 1.

  • Compare an integer beyond JavaScript's safe range without rounding it:

    $.tasks[?(@.id > 9007199254740992)] | count

    Result: 1 — only the exact 9007199254740993 id is greater.

Nested JSON to linked tables

If a spreadsheet is the only thing you came for, jsonloupe.dev/json-to-excel.html is that job on a page of its own: paste, convert, done.

Open a document and press the table glyph in the toolbar's tools group (convert; every glyph there names itself on hover). Every repeating array becomes its own table, joined back to its parent by the id that was already in the data — so this:

{ "orders": [
    { "id": 7, "cust": "ACME",
      "items": [ { "sku": "A", "qty": 2 }, { "sku": "B", "qty": 1 } ] } ] }

comes out as two sheets rather than one row full of items/0/sku columns:

orders                    order_items
id  cust                  order_id  sku  qty
 7  ACME                         7  A      2
                                 7  B      1

jsonloupe finds those tables for you but never runs the guess blindly: the mapping and a preview of real rows stay on screen while you rename columns, choose which fields to keep, and say how dates and coordinates should be read. What you approve is saved as a small mapping file — keep it in this browser, export it, or use it from the command line:

jsonloupe inspect payload.json          # what tables are in here?
jsonloupe draft   payload.json -o payload.spec.json   # a first mapping, to read
jsonloupe convert payload.json --spec payload.spec.json -o payload.xlsx

The same mapping produces the same columns through the browser, the CLI, and the MCP server, with no model in the loop — that is the point of freezing it. What lands in the cells is what was in the document: dates arrive as real dates you can sort and subtract, numbers and coordinates as numbers, and text that only looks numeric stays text, so "1.10" keeps its trailing zero and an int64 id keeps every digit instead of being rounded. Excel's limits and values a column cannot read are reported or refused, never quietly written wrong.

Playbooks: your functions and checks as a file

The document changes daily; the handful of questions you ask of it does not. So playbook in the toolbar (the functions view before 1.8; ⌘K still finds it by that word) opens them beside the document, as one sheet: your checks — a query and the count it should come to — and your functions, each list in name order. run all (⌘⇧↵ / Ctrl ⇧↵ while the playbook is open; press it again to stop) measures every check over whatever is open and paints each row's verdict in place, then runs the ticked functions. The first row that did not pass opens by itself, showing the first nodes it matched; press a path and the tree beside the sheet selects that node, while the sheet stays where it is.

Checks

Press + check in the sheet, or turn a Query result into one with save as check. A check counts matches — groups for | group, distinct values for | distinct — and passes when the count meets its expectation:

Pass when

expectation

Playbook version

no matches are found

{"type": "no-matches"}

3

at least one match is found

{"type": "at-least-one"}

3

exactly N are found

{"type": "exact-count", "count": N}

3

at most N are found

{"type": "at-most", "count": N}

4

at least N are found (N ≥ 1)

{"type": "at-least", "count": N}

4

between M and N are found, both included

{"type": "between", "min": M, "max": N}

4

A count is a whole number within JavaScript's safe range; one past it is refused, never rounded. A query that ends in | sum, | avg, | min or | max yields one value rather than a count, so a check on it says it could not run instead of judging a number it was never meant to count.

Every check comes back as ✓ pass, ✕ fail, ? inconclusive, or ✗ could not run. Inconclusive is the honest third answer: | group and | distinct keep at most 100,000 groups or values (the cardinality cap). Past that, the count is only a lower bound: at least 100,001. A lower bound passes only when it proves the expectation by itself (at least one, or at least N already reached), fails when it is already over the limit (none, or at most 100,000), and is inconclusive otherwise — it is never reported as a pass it has not earned. Narrowing the query decides it.

A check whose query names a field the open document does not have says so on its row, and its panel adds "this reads status — this document has none of that, so a pass here may prove nothing". A no matches check passes just as well on a file whose field was renamed.

A saved check keeps one last verdict, with up to five matched paths and a short preview of each. It belongs to the document the check last ran on: run it on another document and that run replaces it. Reopen the document it ran on and the verdicts are still there; edit it and they fade, marked "document edited", until you run again. That record stays in this browser: it is never written into an exported playbook, and removing the document removes it.

Functions

A function learns what it reads. The first time one runs it is handed the document through a recording proxy, and the paths it actually touched are kept with it. That is what lets the sheet say

this reads orders, orders[].deliveredInHours — this document has none of that

before you press run, instead of leaving you to read an empty result as "none today". It is always a remark and never a gate: the reading knows only the branch that run took, so the run button still works. Tick several functions and run all answers all of them, as a single object keyed by function name. open report in the functions head shows it: today's report, which downloads and reopens as a document like any other. A function's result lasts while that document stays open. Open another document, or reload, and its row says it has not run. Which functions are ticked is remembered in this browser.

Scripts run in an ephemeral worker with fetch, XMLHttpRequest, WebSockets, IndexedDB, importScripts and nested workers removed before any user code, terminated on its result or after ten seconds (a ticked batch shares those ten seconds, and one that runs past them is stopped whole) — a pasted script cannot reach the network or the documents you have opened. JavaScript number mode preserves the behavior of existing functions and says when a document contains values it will round. exact text mode instead hands those unsafe literals to the function as their original digit strings ("9007199254740993"); ordinary values such as 3 and 1.5 remain numbers, so normal counting and arithmetic still work. The mode is saved and exported with the function rather than left as a browser preference.

The file

A playbook is that reusable work as a file — the questions and expectations, never the data:

{
  "playbookVersion": 4,
  "name": "carrier dumps",
  "functions": [
    {
      "name": "slow orders",
      "script": "data.orders.filter(o => o.deliveredInHours > 48)",
      "reads": ["orders", "orders[].deliveredInHours"],
      "numberMode": "exact-text"
    }
  ],
  "checks": [
    {
      "name": "no failed orders",
      "query": "$.orders[?(@.status == 'FAILED')]",
      "expectation": { "type": "no-matches" }
    },
    {
      "name": "a full day of orders",
      "query": "$.orders[*]",
      "expectation": { "type": "at-least", "count": 1000 }
    },
    {
      "name": "a handful of carriers",
      "query": "$.orders[*] | group(@.carrier)",
      "expectation": { "type": "between", "min": 1, "max": 5 }
    }
  ]
}

export in the playbook bar writes one, as version 3 unless a check needs version 4: a library with no at-most, at-least or between check exports as version 3, which jsonloupe 1.4 to 1.7 read. import, or dropping the file on the window, adds what it holds. Import merges and never replaces — a name you already use keeps yours and the incoming one lands as slow orders 2, because a duplicate is one forget away and an overwrite is not. Unknown fields are refused by name rather than dropped, so a file from a newer jsonloupe cannot import as a subset of itself and look like it worked. The library keeps 100 functions and 100 checks: saving a 101st is refused with a message saying so, and a playbook that would not fit is refused whole, rather than pushing your oldest ones out. Version 1, 2 and 3 playbooks still import; version 1 functions keep their original JavaScript-number mode.

In CI: jsonloupe check

The same checks gate a pipeline. jsonloupe check runs a playbook's checks against one or more files with the browser's own engine, and exits non-zero when one fails:

npx -y jsonloupe@^1.8.0 check carrier-dumps.playbook.json out/orders.json
out/orders.json · 3 checks · 4 ms
  ✕ no failed orders — Fail — 2 matches; expected no matches
      first: $.orders[312]
             $.orders[877]
  ✓ a full day of orders — Pass — 1,204 matches; expected at least 1,000 matches
  ✓ a handful of carriers — Pass — 3 groups; expected between 1 and 5 groups
1 function not run — functions run only in the browser
✕ 1 failed · ✓ 2 passed

Keep the @^1.8.0. jsonloupe before 1.8 has no check verb: it starts the viewer's local server instead, which never exits, so the job hangs until the runner times it out. From 1.8 on, a word jsonloupe does not know exits 1 rather than starting anything.

The playbook comes first, then any number of files: .json, .jsonl/.ndjson, raw Zstd, or a Base64-Zstd / bytea payload, detected as they are in the browser. A file that is not valid JSON fails the gate rather than being repaired behind your back; --allow-repair checks the repaired document instead. Every query is checked before any file is read, so a typo in the last check costs no parsing. Functions are JavaScript and never run outside the browser's sandbox, so check counts them and says it did not run them. The text report prints matched paths and never values, since CI logs are often public. A path still names the keys it passes through, so on a document keyed by e-mail address or account id, pass --no-evidence. --format json writes one object, with a short preview of each matched node, for a program to read; --max-evidence N (0–100, default 3) or --no-evidence sets how many paths a check that did not pass names.

Exit status

Meaning

0

every check passed on every file

1

usage error, a file or playbook that could not be read, or a file that is not valid JSON — including one that would need repairing

2

the playbook is not valid JSON or not a playbook, has no checks or more than 100 (what a library holds), or has a query no check can count; nothing was checked

3

at least one check failed

4

nothing failed, but a check was inconclusive or could not run

With several files the most actionable status wins: 3, then 1, then 4. Undecided has its own status so a pipeline can tolerate it on purpose (… || test $? -eq 4).

In GitHub Actions, --format github adds annotations after the text report: one on the playbook for each check that did not pass — a warning when it was inconclusive, an error otherwise — and an error on each file it could not check, or on the playbook when it is invalid:

jobs:
  checks:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v7
      - uses: actions/setup-node@v7
        with:
          node-version: 24
      # … the steps that write out/orders.json
      - run: npx -y jsonloupe@^1.8.0 check carrier-dumps.playbook.json out/orders.json --format github

An agent gets the same gate from the MCP server's run_checks (below), with a playbook's path or the checks inline. It refuses a document that is not valid JSON in the same way; allowRepair: true checks the repaired document and says so.

Use with AI agents

The same engine runs as an MCP server, so an agent can work on a document that would never fit in its context:

claude mcp add jsonloupe -- npx -y -p jsonloupe jsonloupe-mcp

If you have jq and plain JSON, use jq. This is for the cases jq is awkward about: compressed payloads (.zst, Base64-Zstd, PostgreSQL \x… bytea) that have to be decoded first, int64 and decimal digits that must survive the whole pipeline exactly, identity-keyed semantic diff between two documents, and MCP hosts that have no shell to run jq in.

Twelve tools (run_checks from 1.8): load_doc, get_schema, run_query, profile, sample, diff_docs, export_csv, export_result, plus inspect, draft_spec, and convert for the spreadsheet path above, and run_checks, which gates a document on a playbook's checks and never runs its functions. For one question, pass filePath straight to get_schema, run_query, profile, sample, export_csv, export_result or run_checks; it opens the document and returns a reusable docId in the same call. For a sequence of questions, and for diff_docs and the converter tools, which take a docId, call load_doc once and reuse that docId. Up to eight documents stay open; past that the least recently used is closed, and the next response says so. Query details default to 10 rows (use limit=0 for summary only, or offset + limit to page), and text and structured MCP results are both bounded. A typical run over a 37 MB routing payload:

run_query filePath=payload.json "$.tasks[?(@.status == 'FAILED')] | count"
                                  → docId: d1 · count: 17500
profile d1 "$.tasks[*]"          → auto-discovered field coverage, null/missing/types,
                                    exact sums/stats, lengths, distincts and top values
run_query d1 "$.tasks[?(@.status == 'FAILED')] | group(@.region, @.failureReason)"
                                  → composite breakdown, exact complete counts
run_query d1 "$.tasks[*] | top(@.delayMinutes, @.id, @.status)" limit=5
                                  → only the five highest rows; the full array never enters context
sample d1 "$.tasks[0]"           → the whole element, id 9007199254740993 intact
export_result d1 "…| pluck(@.id, @.failureReason)" format=csv outPath=failed.csv
                                  → complete, atomic, rows + UTF-8 bytes only
inspect d1                       → candidate tables, fields, types, deterministic suggestions
draft_spec d1 outPath=tasks.spec.json
                                  → reviewable mapping on disk
convert d1 specPath=tasks.spec.json outPath=tasks.xlsx
                                  → workbook path + row/skipped/warning counts, never its rows
run_checks d1 playbookPath=tasks.playbook.json
                                  → pass, fail or inconclusive per check (or could not run);
                                    a failed or inconclusive check names its first paths
                                    ($.tasks[3]); functions counted, never run

The common schema → profile → query loop is deliberately cheaper than loading JSON or writing a one-off Python script: counts and aggregates stream over every match without materializing the result list; present, missing, and isNull keep false/null/absent distinct; composite grouping and bounded top/bottom replace local sort scripts; and profiles inspect multiple or automatically discovered fields in one scan. Complete CSV/JSONL exports stream to a temporary file and publish atomically. Existing paths are refused unless the agent passes overwrite=true explicitly.

The server makes no network calls and accesses only explicitly supplied input/output paths. A bad query returns the grammar and a suggestion rather than an empty result. The reproducible agent-choice evaluation measures whether an agent selects these operations when Python is also available.

If an agent knows the MCP is installed but still writes one-off scripts, put this routing rule in its project instructions (for example AGENTS.md or CLAUDE.md):

For small plain JSON, use jq when it is already the shortest safe option. Otherwise prefer the JsonLoupe MCP over ad-hoc Python whenever it supports the operation; use custom shell code only when JsonLoupe cannot answer it.

In the browser: WebMCP

Where the browser exposes WebMCP (document.modelContext — a Chrome origin trial from Chrome 149, and the ChatGPT desktop app's built-in browser), jsonloupe.dev registers the same bounded verbs pointed at the document already on screen, so a browser agent and the person watching share one document instead of mailing copies back and forth. Eight tools: run_query, get_schema, profile and sample work as they do over stdio, and four act on the viewer itself, which no headless server can do: load_doc opens a document in it, reveal_path scrolls the tree to a row and flashes it, and highlight_matches/clear_highlights drive the toolbar's own filter state — visible, counted, and reversible by the human's own button. Every tool that returns document content is annotated untrustedContentHint: values are data, never instructions. Browsers without the API fetch nothing and run the same app they always did. Details, testing instructions, and provenance in WEBMCP.md.

Privacy & security

Documents never leave your machine. The complete network-call inventory (four fetch calls, all in opt-in model connection or English query translation), the shape-only LLM contract, and the XSS posture are documented in SECURITY.md. The corresponding threat model, trust boundaries, and assurance argument are in SECURITY-ASSURANCE.md.

Deploying

jsonloupe builds to a fully static site:

npm run build      # dist/

Serve dist/ from any static host (GitHub Pages, Cloudflare Pages, Netlify…). The app needs nothing server-side to work. To match jsonloupe.dev's protection, set the response headers in SECURITY-HEADERS.md at the host — above all the Run sandbox's own CSP on /assets/run-sandbox-*.js, without which a pasted script's import() can reach any host. The local /__api-key convenience endpoint (dev mode, or npx jsonloupe --key-file) simply doesn't exist in a static deploy, and English translation activates only when a visitor connects their own model account or key.

Development

npm run dev        # Vite on :5199, with the loopback-only /__api-key endpoint
npm test           # vitest — engine, worker, converter, playbook, CLI, MCP, WebMCP, NL and DOM suites
npm run lint:security  # FLOSS security analysis + checked TypeScript style
npm run lint:contract  # the UI contract in src/style.css: tokens, contrast, motion, type ramp
npm run lint:headers   # every source file carries the MIT SPDX and copyright notice
npm run coverage   # same suite with enforced 90% statement / 80% branch floors
npm run build      # type checks + vite build + the MCP and CLI bundles
npm run build:mcp  # or build:cli — one target's type check and bundle only
npm run test:a11y  # real Chromium: axe, keyboard/focus, playbook and WebMCP journeys (run after build)
npm run check:site-headers # verify the live OpenSSF hardening headers
npm run check:reproducible-build  # clean locked installs must produce identical bytes
npm run smoke:mcp  # drives the built MCP server over real stdio against a 37 MB fixture (run after build)
npm run eval:agent -- --help  # black-box MCP-vs-Python agent-choice benchmark
npm run eval:ask -- --dry-run  # Ask correctness/injection suite; --dry-run costs nothing

Start with CONTRIBUTING.md. Architecture and internals are documented in ARCHITECTURE.md and SPEC.md; the converter has its own SPEC-converter.md, including what it deliberately refuses to do. Project decisions and the next twelve months are in GOVERNANCE.md and ROADMAP.md. Official publishing and consumer verification are documented in RELEASING.md. What changed in each release is in CHANGELOG.md.

License

MIT. Bundled third-party packages are listed in THIRD_PARTY_LICENSES.md.

Software is provided "as is", without warranty of any kind — see the license text. If you connect a model for English queries, your use of the configured LLM provider is governed by that provider's terms.

Available Tools

12 tools
convertA
DestructiveIdempotent

Write a loaded document out as linked tables — one .xlsx workbook, or a .zip of CSVs — by running a frozen converter spec deterministically. The spec comes from draft_spec, reviewed or edited: pass it inline as spec or as a file in specPath, exactly one of the two. format overrides the format the spec names. Returns row/skipped/warning counts, never the output rows. An existing outPath is refused unless overwrite=true. For one query's result as a single CSV or JSONL file, use export_result instead.

NameRequiredDescriptionDefault
specNoInline specVersion 1 converter spec.
docIdYesA docId returned by load_doc.
formatNoOptional output-format override.
outPathYesDestination .xlsx or .zip path.
specPathNoPath to a converter spec; use either this or spec.
overwriteNoReplace outPath only when explicitly true.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already flag destructiveHint=true and idempotentHint=true, so the description's job is narrower; it adds that conversion is deterministic and frozen, that an existing outPath is refused unless overwrite=true (the actual destructive surface), and that only counts are returned. It stops short of stating permissions or what happens to partial output on failure, keeping it just under 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Every sentence carries load — spec source, exclusivity, override precedence, return shape, overwrite guard, and the sibling alternative — and the routing information is front-loaded rather than buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 6-parameter, nested-object tool with an output schema, this covers what the schema can't: where the spec comes from, how spec and specPath interact, overwrite safety, and what the return value is not. Nothing needed to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description exceeds it by adding the exclusivity constraint between spec and specPath and the precedence rule ('format overrides the format the spec names'), neither of which the schema states.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Names a specific verb and output artifacts: 'Write a loaded document out as linked tables — one .xlsx workbook, or a .zip of CSVs.' It also contrasts itself with export_result, so the agent can separate it from a sibling that also produces files.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the spec origin ('comes from draft_spec, reviewed or edited'), states the mutual-exclusion rule ('exactly one of the two'), and names the alternative for a different need: 'For one query's result as a single CSV or JSONL file, use export_result instead.'

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

diff_docsA
Read-onlyIdempotent

Compare two documents opened with load_doc and return change counts plus the first 25 changes. There is no filePath shortcut: load both first. docIdA is the older side: additions and removals read A → B. Give keySpec to align arrays by identity (e.g. "id,orderId") so reordered elements are not reported as wholesale changes.

NameRequiredDescriptionDefault
docIdAYesThe baseline (older) document.
docIdBYesThe current (newer) document.
keySpecNoComma-separated candidate identity keys for array alignment, e.g. "id,taskId".

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds genuine behavioral context: the result is truncated to the first 25 changes, direction of comparison is A → B, and array alignment depends on keySpec. Truncation disclosure is exactly the kind of trait annotations cannot express.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Four tight sentences, front-loaded with the action and return shape, followed by the prerequisite and the one non-obvious parameter tip. No sentence repeats structured data or wastes space.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool is simple (3 params, no nesting), an output schema exists for return shape, and the description still covers the non-obvious parts: prerequisite tool, comparison direction, result truncation, and keySpec rationale. Nothing needed to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds semantics the schema does not: docIdA is the baseline and additions/removals are read A → B, and keySpec's purpose is identity-based array alignment to avoid false wholesale changes. Only the exact keySpec format is left to the schema, which already covers it.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Compare two documents') plus what it returns ('change counts plus the first 25 changes'), and ties itself to the sibling that must precede it (load_doc). An agent can distinguish it from load_doc, inspect, or get_schema without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly states the prerequisite ('opened with load_doc') and rules out a shortcut ('There is no filePath shortcut: load both first'), then gives the condition for using keySpec ('so reordered elements are not reported as wholesale changes'). This is when-to-use, when-not, and alternative in three sentences.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

draft_specA
DestructiveIdempotent

Draft a reviewable converter spec for a loaded document, usually after inspect shows its tables. This does not convert anything: pass the spec to convert, inline or as the file you saved. Save it to outPath for human editing, or omit outPath to return the JSON under the response cap; an existing outPath is refused unless overwrite=true. format picks one .xlsx workbook or a .zip of CSVs; baseDate gives time-only columns a date.

NameRequiredDescriptionDefault
docIdYesA docId returned by load_doc.
formatNoDesired output format (default xlsx).
outPathNoOptional path for the drafted .json spec.
baseDateNoOptional yyyy-MM-dd or today for time-only columns.
overwriteNoReplace outPath only when explicitly true.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With destructiveHint=true already declared, the description adds the crucial detail that an existing outPath is refused unless overwrite=true, which explains exactly what the destructive flag protects against. It also discloses the response-cap behavior when outPath is omitted, a trait not visible in any structured field. Minor gap: it does not state whether a draft spec can fail validation or what a refused overwrite looks like in the response.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads purpose and the not-convert warning, then handles output destination and parameter meaning in tight clauses. Four sentences, every one carrying routing, safety, or parameter information with no filler.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return-shape explanation is unnecessary, yet the description still flags the response-cap constraint for the inline case. For a 5-parameter drafting tool with a destructive overwrite path, the agent has everything needed to call it correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline is 3, but the description goes further by mapping the enum to real artifacts ("one .xlsx workbook or a .zip of CSVs") and explaining baseDate's purpose for time-only columns. These are semantic interpretations the schema descriptions do not supply.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and artifact ("draft a reviewable converter spec for a loaded document") and immediately delimits scope with "This does not convert anything," which separates it from the sibling convert tool. The phrase "usually after inspect shows its tables" positions it precisely in the load_doc -> inspect -> draft_spec -> convert workflow.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly names the sequencing condition (after inspect reveals tables) and the downstream alternative ("pass the spec to convert, inline or as the file you saved"). It also gives an in-tool decision rule: save to outPath for human editing, or omit outPath to get JSON back under the response cap.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_csvA
DestructiveIdempotent

The CSV-only shortcut for export_result: run a query and write its complete table to a CSV file (RFC 4180, exact digits, formula-injection safe). Returns only the path, row count and byte size — the rows themselves never enter the conversation. The query must produce a table: use | pluck(…), | group(…) or | distinct. The file is streamed and published atomically, with the same 50 MB limit; an existing path is refused unless overwrite=true. Pass filePath to open and export an input file in one call, or docId to reuse an open document. For JSONL or nested values use export_result; for linked multi-table spreadsheets, convert.

NameRequiredDescriptionDefault
docIdNoA docId returned by load_doc.
queryYesA query producing rows or groups.
outPathYesWhere to write the CSV file.
filePathNoInput file to open now instead of supplying docId.
overwriteNoReplace an existing path atomically (default false).

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Adds substantial behavioral context beyond the annotations: returns only path, row count and byte size; rows never enter the conversation; file is streamed and published atomically; 50 MB limit; existing path refused unless overwrite=true. These details go well beyond the destructive/idempotent hints and clarify what the tool actually does.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loads the core purpose in the first sentence and then layers constraints, return behavior, and alternatives in a tight sequence. Despite covering several complex points, every sentence carries useful information.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Complete for a complex export tool: it covers output behavior, atomicity, size limits, overwrite semantics, query constraints, and sibling alternatives. Output schema exists, but the description reinforces the key return contract rather than omitting it.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description still adds meaning: filePath opens and exports an input file in one call, docId reuses an open document, and overwrite controls whether an existing path is replaced. It also explains the query shape required by the query parameter.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: run a query and write its complete table to a CSV file. It explicitly positions itself as the CSV-only shortcut for export_result and distinguishes CSV from JSONL/nested exports and multi-table spreadsheets.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives clear when-to-use guidance: use this for CSV, use export_result for JSONL or nested values, and convert for linked multi-table spreadsheets. It also specifies that the query must produce a table and names the operators that do so.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

export_resultA
DestructiveIdempotent

Write every query match to a local file without sending the data through the conversation. JSONL accepts bare filtered matches and preserves nested values; CSV requires | pluck(…), | group(…) or | distinct. The response contains only format, path, exact row count, byte size and complete=true. Exports refuse rather than silently truncate when the 50 MB output safety limit would be exceeded. Output is streamed to a temporary file and published atomically; an existing path is refused unless overwrite=true. Pass filePath to open and export an input file in one call, or docId to reuse an open document. export_csv is this tool fixed to CSV. Use convert for linked multi-table spreadsheets, and run_query when a bounded answer in the conversation is enough.

NameRequiredDescriptionDefault
docIdNoA docId returned by load_doc.
queryYesA path/predicate query, optionally with a table-producing pipe.
formatYesOutput format.
outPathYesWhere to write the result file.
filePathNoInput file to open now instead of supplying docId.
overwriteNoReplace an existing path atomically (default false).

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare destructiveHint=true, idempotentHint=true and readOnlyHint=false. The description goes beyond them in useful ways: the 50 MB safety limit causes refusal rather than silent truncation, output is streamed to a temp file and published atomically, and an existing path is refused unless overwrite=true. That said, it does not mention permissions, whether the temp file location is configurable, or the cost/time profile of large exports, so it adds context but not a full behavioral picture for an idempotent-but-destructive write.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Dense and front-loaded: the core promise lands in sentence one, then format rules, response guarantee, safety limit, atomicity, then sibling routing. Every sentence carries distinct information, though the enumeration of response fields ('format, path, exact row count, byte size, complete=true') partly duplicates the output schema and the paragraph is longer than strictly needed.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 6-parameter write tool with a real output schema, the description covers what matters: which input mode to use, format-specific query constraints, overwrite behavior, failure mode at the size limit, and when to pick a sibling instead. Nothing an agent needs in order to invoke it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline would be 3. The description earns above baseline by explaining the format/query coupling (JSONL takes bare filtered matches and preserves nesting; CSV requires a table-producing pipe) and the filePath-vs-docId trade-off ('open and export an input file in one call' vs 'reuse an open document'), which the schema one-liners do not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource+distinguishing property: 'Write every query match to a local file without sending the data through the conversation.' It then explicitly separates this tool from export_csv ('this tool fixed to CSV'), convert, and run_query, so an agent can route correctly without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit alternatives with selection conditions: convert for linked multi-table spreadsheets, run_query when a bounded in-conversation answer suffices, export_csv for the CSV-fixed variant. It also states the format precondition for CSV (requires `| pluck(…)`, `| group(…)` or `| distinct`), which tells the agent when this call would fail.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_schemaA
Read-onlyIdempotent

Field names and types of the document — never values. With no path, describes the whole document; with a path, describes just what it selects, merged across matches (so $.tasks[*] describes an element, not the first element). Array shapes are inferred from up to 30 elements; use profile for exact coverage/counts. Start here: it is how you learn which paths run_query can use. For one question, pass filePath directly; it opens the file and returns a reusable docId in the same call. A path that matches nothing returns an error naming that path.

NameRequiredDescriptionDefault
pathNoOptional query selecting the subtree to describe, e.g. $.tasks[*].
docIdNoA docId returned by load_doc.
filePathNoFile to open now instead of supplying docId.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover the safety profile (readOnly, idempotent, non-destructive), and the description adds real behavioral detail beyond them: array shapes are inferred from at most 30 elements (a sampling limitation), results are merged across all matches rather than taking the first, and an unmatched path returns an error naming that path. These are exactly the operational traits an agent cannot get from the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The most important claim (fields/types, never values) is front-loaded, followed by scoping, limitations, and routing in that order. Every clause carries distinct information — the merge behavior and 30-element cap are not restated elsewhere, so nothing is padding.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return structure need not be described, and the description instead fills the gaps that matter for a 3-param discovery tool: entry-point ordering, path merge semantics, sampling limits, and error behavior on no match. Nothing an agent needs to call it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description genuinely adds semantics: path results are 'merged across matches (so $.tasks[*] describes an element, not the first element)', and filePath both opens the file and returns a reusable docId in the same call. It stops short of documenting any path syntax beyond the single example already present in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific, differentiating statement of what it returns ('field names and types of the document — never values'), which immediately separates schema discovery from value-returning siblings like run_query or sample. It also names the scoped variants (whole document vs. path subtree), so an agent knows exactly what the resource is.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes the agent: 'Start here: it is how you learn which paths run_query can use' establishes ordering relative to run_query, and 'use profile for exact coverage/counts' names the alternative and the condition that selects it. 'For one question, pass filePath directly' resolves the docId-vs-filePath choice concretely.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

inspectA
Read-onlyIdempotent

First step toward a spreadsheet: find the repeatable arrays and object maps in a loaded document that can become linked tables. Returns table anchors, row counts, fields, types, and deterministic format suggestions; document values are not returned. Next, draft_spec writes a mapping and convert writes the file. For field names and types use get_schema, for coverage and statistics profile, and for real values sample.

NameRequiredDescriptionDefault
docIdYesA docId returned by load_doc.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered; the description still adds the non-obvious caveat that 'document values are not returned,' which prevents an agent from expecting raw data. It also sketches the returned shape (anchors, row counts, fields, types) beyond what annotations convey, though it omits cost/limits on large documents.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose, and every clause carries routing or output information. It is dense to the point of being run-on across three workflow/routing sentences, but nothing is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, yet the description still summarizes the return shape in one clause, and the workflow position plus sibling routing is fully specified. An agent has everything needed to call this correctly as step one of the pipeline.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% and the single docId parameter is documented in-schema as coming from load_doc. The description reinforces the prerequisite by saying 'in a loaded document,' but adds no format or edge-case guidance beyond the schema, so baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('find the repeatable arrays and object maps in a loaded document') plus the outcome ('can become linked tables'), a metaphor 'first step toward a spreadsheet' that frames the whole workflow. It is clearly distinguishable from siblings like profile or sample, which are named as alternatives for related but different needs.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly sequences the tool: 'Next, draft_spec writes a mapping and convert writes the file,' and routes other needs away ('for field names and types use get_schema, for coverage and statistics profile, and for real values sample'). No inference required to know when this tool applies vs. its siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

load_docA
Read-onlyIdempotent

Open a JSON document and return its size and top-level shape. Accepts a file path or inline text, and transparently decodes Zstd, Base64-Zstd and PostgreSQL bytea (\x…) payloads. Malformed JSON is auto-repaired and flagged. Returns a docId (d1, d2, …) that every other tool takes. The document itself is never returned — read it with get_schema, run_query, profile or sample instead. Prefer path for an existing or large document. For a single question, skip this step: those tools also take filePath.

NameRequiredDescriptionDefault
pathNoAbsolute or relative path to a .json/.jsonl/.zst file.
textNoInline document text, or a compressed payload string.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the readOnly/idempotent annotations, the description discloses decoding support (Zstd, Base64-Zstd, PostgreSQL bytea), auto-repair of malformed JSON with flagging, the docId output contract (d1, d2, …) that all other tools consume, and the key constraint that the document body is never returned. These are exactly the non-obvious behaviors an agent needs.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Six short sentences, front-loaded with what the tool does and what it returns, followed by encoding behavior, the docId contract, the negative constraint, and the param preference. No sentence is redundant with the title or the structured fields.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

With an output schema present, the description needn't explain return values, yet it summarizes the docId contract anyway; annotations cover safety; both params are fully described in schema. Nothing an agent needs to call this correctly or decide to skip it is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above that by explaining the choice between the two params ('Prefer path for an existing or large document', inline text may be 'a compressed payload string'). It does not add format or syntax detail beyond the schema's own path description, keeping it short of a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — opens a JSON document and returns its size and top-level shape — and immediately distinguishes itself from siblings by naming get_schema, run_query, profile and sample as the readers. It also makes explicit what it does NOT return (the document itself), which an agent could otherwise assume.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives an explicit routing rule: 'Prefer path for an existing or large document' and 'For a single question, skip this step: those tools also take filePath.' That is a concrete when-to-use, when-to-skip, and named-alternative statement, not an inference.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

profileA
Read-onlyIdempotent

Profile one or more fields across every selected record in one server-side scan; this replaces ad-hoc Python loops for discovering or summarizing unfamiliar JSON fields. Pass filePath for a one-call answer, or docId to reuse an open document. Returns present/missing/null counts, type counts, distinct count, exact numeric sum/min/max/average, lengths, and top values. Use fields like "status" or "capacity.used" relative to each selected record; omit fields to auto-discover up to 20 fields on selected records or profile selected scalar values. For a filtered count or a group breakdown use run_query; for whole records use sample.

NameRequiredDescriptionDefault
topNoTop values per field (default 10, max 50).
docIdNoA docId returned by load_doc.
queryYesPath/predicate selecting records, e.g. $.tasks[*]. Do not append a pipe.
fieldsNoOptional relative fields to profile together, e.g. ["status", "failureReason", "weightKg"].
filePathNoFile to open and profile now instead of supplying docId.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.6/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive and closed-world, so the safety profile is covered. The description adds real behavioral context beyond that: the work happens in a single server-side scan, omitting fields triggers auto-discovery capped at 20, and docId reuses an already-open document rather than re-reading. It does not mention size limits or performance characteristics for very large record sets, which keeps it from a 5.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three dense sentences, front-loaded with the core action and followed by routing guidance. The enumeration of return statistics is somewhat long and partly redundant with the output schema, but nothing is wasted or buried.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only analytics tool with full schema descriptions and an output schema covering the returned statistics, the description supplies everything else an agent needs: selection syntax pointer, field semantics, discovery behavior, and sibling routing. No material gap remains.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, and the description earns above that by explaining that fields are relative to each selected record ("status", "capacity.used") and that omitting them auto-discovers up to 20 fields or profiles selected scalar values. The filePath/docId tradeoff is also spelled out as a one-call answer vs reusing an open document, which is semantics the schema alone does not convey.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and scope: profiling one or more fields across every selected record in one server-side scan. It also distinguishes itself from siblings by naming run_query for filtered counts/group breakdowns and sample for whole records, so an agent can select it without opening the schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly frames the use case ('replaces ad-hoc Python loops for discovering or summarizing unfamiliar JSON fields'), gives the filePath-vs-docId decision rule, and names the exact alternatives for adjacent jobs. Both when-to-use and when-to-use-something-else are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_checksA
Read-onlyIdempotent

Run deterministic checks — a query plus the count it expects — against a document; one verdict per check: pass, fail, or inconclusive (a | group or | distinct hit its 100,000-value cap, so the count is only a lower bound; never reported as a pass). Pass playbookPath for a jsonloupe playbook file, or checks inline. Playbook functions are JavaScript and are never run here. Expectations: {"type":"no-matches"}, {"type":"at-least-one"}, {"type":"exact-count","count":N}, {"type":"at-most","count":N}, {"type":"at-least","count":N}, {"type":"between","min":M,"max":N}. A check counts matches (groups/distinct values for | group / | distinct); value pipes like | sum are refused. A document that is not valid JSON is refused unless allowRepair is true. Failed and inconclusive checks list their first matched paths. Pass filePath for a one-call answer, or docId to reuse an open document. Use it to gate a document on counts you expect; for an open-ended count or breakdown use run_query.

NameRequiredDescriptionDefault
docIdNoA docId returned by load_doc.
checksNoInline checks; use either this or playbookPath.
filePathNoFile to open and check now instead of supplying docId.
allowRepairNoCheck a document that is not valid JSON after repairing it (default false: refused, as a gate should not pass a broken file).
maxEvidenceNoPaths per failed/inconclusive check (default 3).
playbookPathNoA jsonloupe playbook (.json) whose checks to run.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.5/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

The annotations already cover the safety profile (readOnlyHint, idempotentHint, destructiveHint=false), and the description adds meaningful context beyond them: the 100,000-value cap that produces an inconclusive verdict, the rule that inconclusive is never reported as a pass, refusal of non-JSON documents unless allowRepair is true, and that playbook functions are JavaScript that is never executed here. It does not, however, describe the output shape or how many verdicts/fields come back for a multi-check run.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Front-loaded with the core purpose and verdict semantics, and most sentences carry load-bearing detail. It is dense to the point of being a run-on (semicolon chains, parentheticals), and the invalid-JSON refusal is stated both in the description and in the allowRepair schema description, which is mild duplication.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

An output schema exists, so return values need not be spelled out, and the description still covers verdict enumeration and evidence paths for failures. For a six-parameter gating tool with an empty expectation schema, everything an agent needs to construct a valid call is present.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so baseline would be 3, but the description goes well past the schema: the expectation object is an unconstrained object in the schema, and the description enumerates all six legal forms (no-matches, at-least-one, exact-count, at-most, at-least, between) with their fields. It also explains the docId vs filePath tradeoff, why allowRepair defaults to false, and that value pipes like | sum are refused in queries.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource: 'Run deterministic checks — a query plus the count it expects — against a document', plus the output shape (one verdict per check: pass, fail, inconclusive). It also names the sibling it is not, contrasting with run_query at the end, so an agent can separate the two gating/reporting tools without opening schemas.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use it ('to gate a document on counts you expect') and names the alternative plus the condition that selects it ('for an open-ended count or breakdown use run_query'). It also routes between input modes: playbookPath vs inline checks, and filePath for a one-call answer vs docId to reuse an open document.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

run_queryA
Read-onlyIdempotent

Use this instead of ad-hoc Python for JSON counts, filtered exact sums/averages/min/max, composite groups, distinct values, top/bottom ranking, and field projection. It scans server-side and returns only the bounded answer. Pass filePath for a one-call answer, or docId to reuse an open document. For field names and types use get_schema; for coverage and statistics of many fields at once, profile; to judge counts against expectations, run_checks. Grammar (a JSONPath subset with aggregation pipes):

Paths: $.key $['odd key'] $[3] $[-1] $[1:5] $.arr[] $..key (recursive) A [...] step attaches directly to what precedes it: $['odd key'], $.a['b c'] — never $.['odd key']. Any key that is not a plain identifier (space, '-', '.') must be bracketed: $['line-items'], @['unit price']. $.arr is the array itself; $.arr[] is its elements — $.orders[] | count counts the orders, $.orders | count is 1. Predicates filter the CHILDREN of the current node: $.tasks[?(...)] A [?(...)] step already yields the matched elements — never follow it with []. Operators: == != > >= < <= (numbers and strings) Boolean: && || ! Grouping: ( ) — there is no !~; negate with !( … ) Strings: @.name contains 'x' | startsWith | endsWith | @.name =~ /safe-regex/i Membership: @.status in ['A','B'] Arrays: @.tags contains 'x' Set: @.field — true unless the key is missing, null or false ('' and 0 count as set) Explicit: @.field present (key exists) | missing (no key) | isNull (value is null) Fields compare to fields: @.capacity.used > @.capacity.max A predicate holds one level only: no predicate inside a predicate, no $ path inside it Pipes: append AT MOST ONE, at the very end. Pipes never chain — | top(...) | pluck(...) is invalid. | count | sum(@.x) | avg(@.x) | min(@.x) | max(@.x) | distinct | group(@.x, @.y) | top(@.score, @.id) | bottom(@.score, @.id) | pluck(@.a, @.b.c) top/bottom/pluck already name their output columns — never add a second pipe to project them. A pipe argument is one plain field path (@.a.b) — no [], no arithmetic, no second query. A pipe without args operates on the matched values themselves: $.a[].n | sum

Examples: "how many tasks have no route" → $.tasks[?(!@.routeId)] | count "how many orders are there" → $.orders[] | count "failed tasks by reason" → $.tasks[?(@.status =~ /^failed$/i)] | group(@.failureReason) "tasks that are not cancelled" → $.tasks[?(!(@.status =~ /^cancelled$/i))] "top delayed tasks" → $.tasks[] | top(@.delayMinutes, @.id, @.status) "highest value orders showing just their id and amount" → $.orders[] | top(@.amount, @.id) "all statuses that appear" → $..status | distinct "ids and etas of pending tasks" → $.tasks[?(@.status =~ /^pending$/i)] | pluck(@.id, @.eta) "total line value of failed orders" → $.orders[?(@.status =~ /^failed$/i)].items[].lineTotal | sum "sum of the line-total column" → $['line-items'][*] | sum(@['line-total']) "the run id" → $['meta data']['run id'] "tasks outside india" → $.tasks[?(@.loc.lat < 8 || @.loc.lat > 37)]

Matches come back as path + preview; use | pluck(@.a, @.b) to project real fields into rows, or the sample tool for whole values. Only 10 detail rows return by default; set limit=0 for a count-only summary or page with offset+limit. Aggregates always scan every match. Numeric predicates and aggregates keep int64 and decimal digits exact. For only a count, append | count; the response is one scalar.

NameRequiredDescriptionDefault
docIdNoA docId returned by load_doc.
limitNoMaximum detail rows returned (default 10, 0 for summary only, max 100).
queryYesA query, e.g. $.tasks[?(@.status == 'FAILED')] | count; $.tasks[*] | group(@.region, @.status); $.tasks[*] | top(@.delay, @.id)
offsetNoDetail rows to skip (default 0).
filePathNoFile to open and query now instead of supplying docId.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.9/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Beyond the read-only annotations, the description discloses server-side scanning, bounded responses, default detail-row limits, count-only summaries, offset/limit paging, and exact int64/decimal handling. It also explains that matches return path plus preview and that pluck projects real fields into rows.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is front-loaded with the core use case and alternatives before diving into grammar, and its length is largely justified by the query language. However, it is very long and includes some repeated guidance, so it is comprehensive rather than maximally concise.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a complex query tool, the description covers selection, alternatives, parameter roles, grammar, pagination, projection, and aggregate behavior. With an output schema also present, no critical calling information appears to be missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters5/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The schema already covers all parameters, but the description adds meaningful semantics: filePath versus docId usage, limit=0 as a summary-only mode, offset/limit paging, and an extensive query grammar. This goes well beyond what the schema alone provides.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation on a specific resource: running JSONPath-style queries that count, group, rank, and aggregate JSON. It clearly distinguishes itself from siblings such as get_schema, profile, run_checks, and sample, so an agent can identify when this tool is the right one.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

It gives explicit routing guidance: use this instead of ad-hoc Python for counts and aggregations, pass filePath for a one-call answer or docId to reuse an open document. It also names alternatives for field discovery, profiling, expectation checking, and whole-value sampling.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

sampleA
Read-onlyIdempotent

Read n real values at a path, exactly as they were parsed (int64 and decimal digits intact). A path that selects one container samples its children; a path that selects many nodes samples those nodes. Pass filePath for a one-call sample, or docId to reuse an open document. Use it to see what records really hold; to count or aggregate use run_query, for statistics profile, and for names and types get_schema.

NameRequiredDescriptionDefault
nNoHow many values to return (default 5, max 50).
pathYesA query selecting what to sample, e.g. $.tasks or $.tasks[*].id
docIdNoA docId returned by load_doc.
filePathNoFile to open and sample now instead of supplying docId.

Output Schema

NameRequiredDescription
okYes
hintNo
toolYes
errorNo
evictedNo
structuredTruncatedNo

TDQS

A4.7/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnly, idempotent, non-destructive, so the safety profile is covered; the description adds real behavioral context the annotations cannot: values are returned without numeric precision loss, a container path samples children while a multi-node path samples those nodes. It does not discuss the cap of 50 or how empty selections behave, so it stops short of full disclosure.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Three sentences, no waste, and the core purpose and fidelity guarantee are front-loaded before the call-mode and sibling-routing guidance. Every clause carries information an agent needs.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a read-only sampling tool with a full output schema, full parameter coverage, and rich annotations, nothing needed to invoke it correctly is missing. The description fills the remaining gaps: precision fidelity, sampling semantics, and sibling disambiguation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3; the description earns above it by explaining the semantic tradeoff between filePath and docId (open-and-sample versus reuse an already-open document) rather than restating the schema's wording. The n and path parameters are only lightly touched in prose but are fully self-documenting in the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource ('Read n real values at a path') plus a distinguishing property ('exactly as they were parsed, int64 and decimal digits intact'). The second sentence explains the sampling unit, which further separates it from aggregate siblings like run_query.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly routes between the two calling modes ('Pass filePath for a one-call sample, or docId to reuse an open document') and names the alternative tools for adjacent jobs: run_query for counting/aggregation, profile for statistics, get_schema for names/types. Both when-to-use and when-not-to-use are covered.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 12 tool updatesv0.1.0
    • First observedconvert
    • First observeddiff_docs
    • First observeddraft_spec
    • First observedexport_csv
    • First observedexport_result
    • First observedget_schema
    • First observedinspect
    • First observedload_doc
    • First observedprofile
    • First observedrun_checks
    • First observedrun_query
    • First observedsample

TDQS

A4.6/5.0

Scored across 12 tools

Disambiguation4/5

Most tools target distinct operations (load, query, checks, profile, sample, export, convert), and descriptions explicitly cross-reference alternatives. However, get_schema/inspect/profile/sample/run_query have some overlapping schema/value/statistics territory, and export_csv is a near-duplicate shortcut of export_result, so an agent must read carefully to choose.

Naming Consistency4/5

All names use snake_case and are readable, with most following verb_noun (load_doc, run_query, get_schema, export_result). Occasional single-verb/noun names (inspect, convert, profile, sample) are minor deviations but still understandable.

Tool Count5/5

12 tools is appropriate for a JSON analysis/query/conversion/export surface; each capability (load, schema, query, checks, profile, sample, export, diff, conversion) has a clear role. No excessive duplication or trivial single-tool thinness.

Completeness5/5

The surface covers loading, schema discovery, querying, validation checks, profiling, sampling, diffing, exporting, and table conversion. For an analysis-focused JSON tool, this provides complete lifecycle coverage without obvious dead ends.

Maintenance

ActivityMaintained
ResponsivenessUnresponsive

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    D
    maintenance
    Enables efficient reading, analyzing, and querying of Excel, CSV, and JSON files with support for chunked processing, column/field filtering, and streaming for large datasets. Supports multiple transport protocols (stdio, HTTP, SSE) for flexible integration.
    39 npm
    4
    ISC
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables exploring and querying structured data (JSON, JSONL, CSV, TSV) via shape, read, find, and diff operations, with bounded resource usage.
    4 npm
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables AI agents to manage plain .jsonl files as tables, offering tools to list libraries and tables, query data, and upsert rows while preserving byte-for-byte fidelity on unchanged rows.
    5
    MIT