miller/pkg/terminals/mcp/SKILL.md
John Kerl 41f5188bd0
Add mlr mcp MCP server + agent playbook, with a --no-shell gate (#2098 PR7) (#2133)
* Plan: flesh out PR7 (MCP server + Agent Skill) design

stdio transport (no HTTP port), mlr mcp terminal in the main binary,
SDK-vs-handroll decision, tool list, in-process vs subprocess split,
run-tool safety (--no-shell prerequisite), single-sourced skill, tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Add mlr mcp: MCP server + agent playbook; --no-shell gate (#2098 PR7)

New terminal `mlr mcp` runs a Model Context Protocol server over stdio
(spawned by MCP clients; no network port), exposing five tools --
list_capabilities, which, validate_dsl, describe_data, run -- plus an
agent playbook as MCP prompt/resource. Catalog tools are served
in-process from the help registries; the rest subprocess this same
binary with MLR_ERRORS_JSON=1, a timeout, and an output cap.

Prerequisite: a new --no-shell flag / MLR_NO_SHELL env var (one-way
gate) disables the DSL system/exec functions, piped redirects, and
--prepipe/--prepipex; the MCP server sets it on the commands it runs
unless started with --allow-shell.

Adds the github.com/modelcontextprotocol/go-sdk dependency.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Force LF checkout for the embedded SKILL.md (Windows CI fix)

go:embed embeds checkout bytes, so a CRLF checkout on Windows made the
embedded playbook differ per platform and failed
TestPlaybookHasFrontmatter. Pin the file to eol=lf in .gitattributes.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* Move no-shell test DSL into per-case mlr files (Windows CI fix)

Inline single-quoted DSL in cmd files is mangled by the Windows shell
(single quotes are not quote characters there); the harness's
put -f ${CASEDIR}/mlr pattern avoids shell quoting entirely.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
2026-07-03 19:41:49 -04:00

3.8 KiB

name description
miller Drive Miller (mlr) to process CSV/TSV/JSON/etc. data. Use when constructing mlr command lines: discover capabilities from the catalog rather than guessing, learn the data's shape before writing expressions, validate DSL before running, and recover from failures via structured errors.

Miller agent playbook

Miller (mlr) is a command-line data processor for CSV, TSV, JSON, JSON Lines, and other tabular/record formats, with SQL-like verbs (cut, sort, join, stats1, ...) and an awk-like DSL (put, filter).

Work this loop. Each step exists to prevent a specific, common failure.

1. Discover — never invent names

Everything valid is in the catalog; anything not in the catalog does not exist. Hallucinated flag/function names are the top failure mode.

  • Route an intent: which with e.g. "join two files on a key" → ranked candidates. confident: true means a name matched; trust the top hit.
  • Browse cheaply: list_capabilities with index: true → every verb/function/flag/keyword with one-line summaries.
  • Drill in: list_capabilities with kind: "verb", names: ["join"] → the full entry. Prefer the structured options list (flag, arg, type, enum values) when present; usage_text is the prose fallback.
  • The whole catalog is cacheable against (mlr_version, catalog_schema_version) — re-fetch only when either changes.

2. Constrain — learn the data before touching it

Call describe_data on the input first. It returns, per field: name, types seen with counts, occurrence count, null count, cardinality, min/max, and — for low-cardinality fields — every distinct value.

  • Copy field names exactly from describe_data; never guess casing or spelling.
  • For flags like -g (group-by) and DSL comparisons, use values from the values array, not values you expect to exist.
  • Fields whose count is less than other fields' are absent in some records: guard DSL with is_present($field).

3. Validate — check DSL before spending a run

Before any run that includes put or filter, call validate_dsl with the expression. Cost: parse-only, no data read. On valid: false, the error document has kind, hint, and did_you_mean — apply the hint, don't re-guess syntax.

4. Run — and read errors structurally

Call run with argv as a list, one element per shell word (no shell quoting):

{"args": ["--icsv", "--ojson", "cat", "data.csv"]}

Command-line shape rules that prevent most argv errors:

  • Main flags (I/O formats etc.) come before the verb: mlr --icsv sort -f name f.csv.
  • Format shorthands: --icsv --ojson (separate in/out), --csv/--c2j etc. (combined).
  • Chain verbs with then: ["--icsv", "sort", "-f", "k", "then", "head", "-n", "3", "f.csv"].
  • If a field value being compared in filter might collide with a verb flag, end verb flags with -- before filenames.
  • Inline data goes in stdin_text; files go at the end of args.

On failure, exit_code is nonzero and error (when present) carries kind, hint, and did_you_meanhint is often a corrected command line; prefer executing it over reasoning from the message. stdout_truncated: true means the output exceeded the server's cap: narrow the query (e.g. head, cut) rather than re-running the same command.

Notes

  • run cannot execute external commands (DSL system/exec, piped redirects, --prepipe) unless the server was started with --allow-shell; such calls fail cleanly. It can write files via tee, split, and DSL output redirects — treat it as a write-capable tool.
  • Long inputs: prefer describe_data + targeted verbs over dumping whole files through run.
  • One record format in, another out: Miller is format-to-format; there is no separate conversion step.