NAME

MCP::Run::Compress::Filters - Command Output Compression Reference

VERSION

version 0.107

DESCRIPTION

This document lists all commands that MCP::Run::Compress filters and how they are compressed. Each filter removes noise, truncates verbose output, and limits lines to reduce token count while preserving essential information.

The file holds no code. It is a promise about another file's behaviour, and kept honest by t/60-filters-doc.t, which compares every command named in an =item below against the filters MCP::Run::Compress actually registers, and fails on drift in either direction. Two rules follow from that, and both matter when adding an entry:

  • The command named in an =item is the filter's identity -- the shortest command line that makes it match -- and nothing else. A filter registered for the program iptables is documented as iptables, not as iptables -L, even though -L is what one usually types; the -L belongs in the prose. Flags appear in the name only when the filter genuinely requires them, as git diff --stat does.

  • An entry documents a filter that exists. A command listed here is a command the compressor really compresses. Anything else makes this reference worse than silence: someone checking whether their tool is supported reads exactly this file.

COMMANDS

File System Commands

ls

Long listing format (-l, -la, etc.) is detected automatically from the output. When detected, the filter strips permissions, owner, group, size, date, inode, and device information. Only the file type letter and the filename are preserved.

Every type ls -l prints is recognised (- b c d D l p s), as is the character some systems append to the mode column for the file's alternate access method: . for an SELinux context, + for a POSIX ACL, @ for macOS extended attributes. For a symlink the preserved name is the whole link -> target, because the target is the only thing the entry actually states.

The date column is recognised in every shape ls --time-style= produces: the locale default (Aug 14 08:44, and Mar 12 2024 for an older file), iso (08-23 10:00), long-iso (2026-08-23 10:00), full-iso (2026-08-23 10:00:00.123456789 +0200) and the date alone, as in +%Y-%m-%d (2026-08-23). A strftime format that prints neither an ISO date nor an ISO time is not recognised, and such a line stays uncollapsed.

For non-long listings, common noise directories are filtered: node_modules, .git, .target, .next, .nuxt, .cache, __pycache__, .DS_Store, vendor/bundle.

# Before: drwxr-xr-x. 14 getty getty 4096 Apr 24 02:32 .build
# After:  d .build
# Before: lrwxrwxrwx   1 getty getty    9 Apr 24 02:32 link -> target
# After:  l link -> target
stat

Strips Device, Inode, and Birth lines.

# Before:
#   Device: 801h/2049d      Inode: 1234567     Links: 1
#   Birth: 2026-03-09 10:00:00.000000000 +0100
# After: (lines removed)
find

Strips permission denied errors and limits results.

df

Truncates columns at 80 characters, limits to 20 lines.

du

Strips .git and node_modules directories, truncates at 150 characters, max 50 lines.

Git Commands

git status

Strips branch information, keeps changed/untracked files.

git diff

Strips diff headers (diff --git, index, ---, +++). Keeps actual - and + lines with content.

git diff --stat

Transforms to compact "N+M- filename" format (additions+deletions-filename). Strips summary lines (X files changed, insertions(+), deletions(-)).

# Before:
#  file1.txt | 5 +++ --- 2 deletions(-)
#  file2.rb  | 3 +++ --- 1 deletion(-)
#  2 files changed, 8 insertions(+), 3 deletions(-)
# After:
#  5+2-file1.txt
#  3+1-file2.rb
git log

Shows first 20 and last 10 lines (with total count), strips commit hashes, author, and date noise.

git branch

Strips blank lines, max 30 lines.

git stash

Strips blank lines, max 30 lines.

git commit

The one filter that rewrites the command instead of its output. When CO_AUTHORED_BY or ANTHROPIC_MODEL is set, the Co-Authored-By trailer in the commit message is replaced, or added to the last -m/--message argument when none is present. Set MCP_RUN_COMPRESS_NO_CO_AUTHORED to disable it entirely.

Unlike every other git entry here this one matches inside compound commands, so cd project && git add . && git commit -m "..." is rewritten too. The output of git commit itself is left untouched.

Build & Compile Commands

make

Strips make[N]: Entering directory, Leaving directory, and Nothing to be done messages.

# On empty output: "make: ok"
cargo build

Strips Compiling, Fresh and Finished lines. Keeps errors.

cargo test

Strips compilation and running noise. Max 100 lines.

gcc, g++

Merges stderr into stdout -- a compiler says everything there -- then strips the include chain, the note: lines and the echoed source with its caret markers. The diagnostics themselves are kept: file, line, column, message.

# Before:
#   In file included from /usr/include/stdio.h:42:
#   main.c:10:5: error: use of undeclared identifier 'foo'
#      10 |     foo();
#         |     ^
# After:
#   main.c:10:5: error: use of undeclared identifier 'foo'
swift build

Strips the build chatter ([N/M], Compiling, Fetching, resolution and Build complete!), so that what remains is exactly the warnings and errors. On a clean build nothing remains and the output becomes swift build: ok (build complete).

mix compile

Elixir Mix compiler. Strips Compiling N files, Generated ... app and Hex dependency resolution lines. On empty output: mix compile: ok.

pio run

PlatformIO build. Strips the environment report (CONFIGURATION, PLATFORM, HARDWARE, DEBUG, PACKAGES), the library dependency finder, and the compiling, archiving, linking and size checking lines. The RAM:/Flash: usage lines are kept. On empty output: pio run: ok.

mvn

Strips the [INFO] bookkeeping -- separators, plugin banners, downloads, Total time, Finished at -- but not every [INFO] line: surefire reports its test results there. [WARNING] and [ERROR] are untouched.

gradle

Strips > Task : lines for tasks that succeeded, were up to date, skipped or came from the cache, plus daemon and download noise. A task line carrying FAILED is kept.

Container Commands

docker ps, docker images

Truncates columns at 120 characters, max 30 lines.

docker build

Strips build progress (# N [M/N], Step N/M:).

docker run

Strips image pulling and status messages.

kubectl get

Truncates columns at 150 characters, max 50 lines.

kubectl describe

Strips name, namespace, labels, annotations noise. Max 100 lines.

docker-compose, docker compose

Both the v1 binary and the v2 subcommand. Strips network, volume and container lifecycle lines (Creating, Started, Healthy, ...), Pulling and Attaching to. Service log output is kept.

Cloud & Infrastructure Commands

terraform plan, terraform apply

Strips Refreshing state... and progress messages.

helm install, helm upgrade

Strips NAME, NAMESPACE, STATUS, REVISION, NOTES noise.

ansible-playbook

Strips PLAY/TASK banners, running handlers, and recap headers. Max 100 lines.

tofu plan, tofu apply

OpenTofu is a Terraform fork and prints the same lines; these filters mirror the terraform ones above.

tofu init

Short-circuits a successful init to tofu init: ok -- there is nothing else in it. A failing init keeps its output, minus provider discovery (- Finding, - Installing, - Installed).

Package Managers

cpanm

Strips -- Working on, OK, FAIL noise. Shows only essential progress.

npm install

Strips added/found packages confirmation and warnings.

yarn install, yarn add, pnpm install, pnpm add

Strips Done in..., Resolving completed, Linking completed.

pip install

Strips Collecting, Downloading, Installing collected packages.

cpan

Strips the CPAN shell's narration: Reading '...', Database was generated on, Fetching with, Checksum for, Running make/test/install for.

cpm

Strips the per-distribution DONE ... lines and keeps the summary. On empty output: cpm: ok.

composer install

Strips repository loading, dependency updating, the per-package operation lines, lock file writing and autoload generation. On empty output: composer install: ok.

brew install

Strips ==> Downloading, ==> Pouring, ==> Fetching, auto-update noise and Already downloaded:. ==> Installing dependencies for ... goes, ==> Installing ... stays.

poetry install

Strips Installing dependencies from lock file, Package operations: and the per-package bullet lines. On empty output: poetry install: ok.

uv sync

Strips the Resolved/Prepared/Audited N packages in ... timings and the +/- package list; Installed N packages is kept. On empty output: uv sync: ok.

System Commands

ps

Truncates columns at 120 characters, max 30 lines.

iptables

Any invocation, iptables -L in particular: truncates columns at 150 characters, max 50 lines.

ping

Strips PING header, replies, and statistics.

rsync

Strips sent/received bytes and total size summary.

netstat

Truncates columns at 150 characters, max 50 lines.

ip addr truncates at 150 characters, max 50 lines; ip route and ip link strip blank lines, max 30 lines.

mount

Truncates columns at 200 characters, max 50 lines.

lsblk

Lists block devices in tree format. Truncates at 150 characters, max 50 lines.

blkid

Lists block device attributes. Truncates at 200 characters, max 50 lines.

systemctl status

Removes the state glyph from the unit line and strips the accounting block: Loaded, Docs, Main PID, Tasks, Memory, CPU, CGroup and the process tree under it. Active: and the recent log lines are kept -- Active: also carries the state the glyph stood for.

journalctl

Strips -- Reboot --, -- Logs begin at ..., -- Journal begins at ..., -- No entries -- and boot markers. Max 100 lines.

fail2ban-client

Strips blank lines, max 30 lines.

Development Tools

grep

Truncates lines at 150 characters, max 100 lines.

cat

Strips blank lines, truncates at 500 characters, max 100 lines.

curl

Merges stderr into stdout, strips progress (% Total, Resolving, Connected to, HTTP/... responses).

wget

Strips timestamp, resolving, connecting, length, saving, and progress bar lines.

pytest

Strips coverage and HTML report generation lines.

ollama run

Strips ANSI escapes and the braille spinner frames. Nothing else: the output is the model's answer, so no line of it is dropped, truncated or counted.

quarto render

Short-circuits on Output created: to quarto render: ok (output created). Otherwise strips processing, validating and resolving messages and the percentage progress lines.

Version Control Systems

jj

Jujutsu VCS. Strips Hint: lines, Working copy now at:, Parent commit: and rebase counts, all of which arrive on stderr. Max 30 lines.

shopify theme push, shopify theme pull

Keeps only the last 5 lines -- the result and the preview URL. Deliberately not registered for shopify theme as a whole: shopify theme list is nothing but its answer, and a tail of 5 would cut it.

FILTER STAGES

Each filter applies output through this pipeline, in this order:

  1. filter_stderr - Merges stderr into stdout if configured

  2. strip_ansi - Removes ANSI escape codes (colors, cursor control)

  3. collapse_cr - Reduces carriage-return overwrites to the last state on each line, which is what a terminal would have shown. Unconditional, with no filter attribute: a progress bar carries no newlines at all, so to every stage below it is a single line that max_lines cannot shorten. Runs for commands no filter matches, too: normalising \r is display semantics rather than filtering.

  4. match_output - Short-circuits on pattern match, replacing the entire output with a fixed message. Only ever honest for success, when there is nothing left to say: a diagnostic short-circuit deletes precisely what the output was being read for. Patterns want /m, since they are matched against the whole text rather than line by line.

  5. transform - Line-by-line transformation (e.g., strip ls permissions)

  6. strip_lines_matching - Removes lines matching regex patterns

  7. keep_lines_matching - Keeps only lines matching patterns

  8. truncate_lines_at - Truncates each line to N characters, appending ...

  9. head_lines / tail_lines - Keeps first/last N lines, and says so: ... N lines omitted ... goes after the kept lines when only the head is kept, and before them when only the tail is kept. Truncating without a marker would leave the model holding a fragment it has no reason to doubt.

  10. max_lines - Absolute line limit, appending ... N more lines .... This counts separately from the marker above: two markers in one output are normal and their numbers do not add up to a single total.

  11. on_empty - Fallback message when output is empty after filtering

ADDING CUSTOM FILTERS

use MCP::Run::Compress;

my $compressor = MCP::Run::Compress->new;

# Legacy regex-based matching
$compressor->register_filter(
    command => '^my-command\b',
    strip_lines_matching => [
        qr(^\s*$),
        qr(^Verbose:),
    ],
    truncate_lines_at => 100,
    max_lines => 30,
    on_empty => 'my-command: ok',
);

# New parsed_command matching (order-independent flags)
$compressor->register_filter(
    parsed_command => {
        program    => 'my-tool',
        subcommand => 'process',
        flags      => { verbose => 1, output => 1 },
    },
    strip_lines_matching => [qr(^\s*$)],
    max_lines => 50,
);

A filter added to the default table in MCP::Run::Compress belongs in the COMMANDS section above; t/60-filters-doc.t fails until it is there.

PARSED COMMAND APPROACH

Filters can use either regex-based command matching (legacy) or the new parsed_command approach. The parsed approach extracts program, subcommand, and flags using Getopt::Long, enabling order-independent flag matching.

# Matches: git diff --stat, git diff --stat -w 5, git -C /path diff --stat
$compressor->register_filter(
    parsed_command => {
        program    => 'git',
        subcommand => 'diff',
        flags      => { stat => 1 },
    },
    transform => sub { ... },
);

The flags hash specifies which flags must be present. Flag values are ignored - only presence matters for matching.

Matching a program without a subcommand matches every invocation of that program, so such a filter shadows any program+subcommand filter for the same program: the compressor stops at the first matching parsed filter, and which one that is depends on hash order. kubectl get and a bare kubectl cannot both exist as long as that is true.

SEE ALSO

MCP::Run::Compress, MCP::Run

SUPPORT

Issues

Please report bugs and feature requests on GitHub at https://github.com/Getty/p5-mcp-run/issues.

CONTRIBUTING

Contributions are welcome! Please fork the repository and submit a pull request.

AUTHOR

Torsten Raudssus <getty@cpan.org>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by Torsten Raudssus <torsten@raudssus.de> https://raudssus.de/.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.