PluginBench
MCP Server
Active
MIT

io.github.kehsiaocuba-ux/fedit MCP Server

io.github.kehsiaocuba-ux/fedit

Go-based surgical file editor with anchored insert, delete, replace, move, and copy operations.

What is the io.github.kehsiaocuba-ux/fedit MCP server?

The fedit MCP server is a Go-based tool for precise, line-aware file editing from the command line. It provides anchored operations like insert, delete, replace, move, and copy without requiring interactive editors or complex regex syntax, with built-in verification and support for 17 language mappers.

fedit is a zero-dependency CLI tool designed for surgical file edits with predictable, verifiable operations. It excels at config file manipulation, log processing, and code refactoring tasks where you need exact line-based or content-matched edits. Built for sysadmins, DevOps engineers, and automation scripts.

How to install io.github.kehsiaocuba-ux/fedit

Copy-paste configuration for popular MCP clients.

No machine-readable install method is published for this server in the registry. Check the repository or website for setup instructions.

Tools & capabilities

Tools this server exposes to the agent.

  • show — Display file contents, optionally filtered by line range or content anchors
  • find — Search for lines matching a substring or regex pattern
  • insert — Insert content after a specified line number
  • insertafter — Insert content after a matching line (content-anchored)
  • insertbefore — Insert content before a matching line (content-anchored)
  • replace — Replace a line range or content-matched section with new content
  • replaceall — Global find-and-replace with literal or regex patterns, supports streaming for large files
  • delete — Delete a line range or content-matched section
  • move — Move a line range or block to a new position, with optional multi-paste
  • copy — Copy a line range or block to a new position, with optional multi-paste
  • fields — Extract a column from delimited files (CSV, TSV, etc.)
  • map — Display file structure for 17 language types (Go, Python, Terraform, Nix, HCL, etc.)
  • writeraw — Write content without escape expansion (preserves literal backslashes)
  • writelines — Write lines interactively from stdin

Use cases

  • Replace configuration values across multiple files with verification
  • Insert firewall rules or server blocks at precise locations in config files
  • Extract and refactor code blocks or Terraform resources by name without line numbers
  • Process multi-gigabyte log files with regex replacement using streaming mode
  • Copy and move configuration sections between files with atomic integrity

io.github.kehsiaocuba-ux/fedit MCP server FAQ

What is fedit?

fedit is a Go-based CLI tool for surgical file edits with anchored operations (insert, delete, replace, move, copy). It supports line-based and content-matched editing with built-in verification, language-aware block mapping, and streaming for large files.

Is fedit free?

Yes, fedit is open-source and free. It is a single Go binary with zero dependencies.

How do I install fedit?

Run `go install github.com/amalexico/fedit@latest` or download a binary from GitHub Releases and add it to your PATH.

Does fedit require authentication?

No, fedit is a local CLI tool that operates on files you specify. It requires no authentication or external services.

Can fedit handle large files?

Yes, fedit includes a `-stream` mode for `replaceall` and `find` that processes files line-by-line without loading them into memory, supporting multi-GB files.

What languages does fedit support for block mapping?

fedit supports 17 language mappers including Go, Python, JavaScript, Terraform/HCL, Nix, Rust, Java, C/C++, and others via the `map` operation with `-lang` flag.

README (reference)

Source of truth, from the repository.

fedit — Fast File Editor for the Terminal

Ko-fi Open Collective

A zero-dependency CLI tool for surgical file edits from the command line. No interactive editors. No sed/awk gymnastics. Just simple, predictable operations with built-in verification.

Built for sysadmins, DevOps engineers, and anyone who scripts config changes.

fedit demo

go install github.com/amalexico/fedit@latest

Why fedit?

  • One binary, zero dependencies — pure Go, runs everywhere
  • 17 language mappers — see the structure of any file before editing
  • -v flag — verify every mutation before moving on
  • Line-aware — no regex surprises, no "which match did it hit?"
  • Stream engine — process multi-GB files line-by-line with atomic integrity
  • Field extraction — pull CSV/TSV column N without awk
  • Safe — no in-place unless you say so, never touches files you did not name

Install

Go install (recommended):

go install github.com/amalexico/fedit@latest

Or download the binary from GitHub Releases and put it in your PATH.

Verify:

fedit -file /etc/hostname -op show

Quick Start

# See what is in a file
fedit -file config.yaml -op show

# See just lines 10-25
fedit -file config.yaml -op show -line 10 -end 25

# Find every line containing "timeout"
fedit -file config.yaml -op find -match "timeout"

# See the structure of a Go file
fedit -file main.go -op map -lang go

# Replace line 42 with new content
fedit -file config.yaml -op replace -line 42 -end 42 -text "timeout: 60s" -v

# Insert a line after every occurrence of "server {"
fedit -file nginx.conf -op insertafter -match "server {" -text "    include security.conf;" -v

# Move a function block before another (content-matched, atomic)
fedit -file main.go -op move -match "func OldHelper(" -end 45 -beforematch "func NewHelper(" -v

# Copy a config block and paste it 3 times at a new location
fedit -file values.yaml -op copy -line 50 -end 65 -after 200 -times 3 -v

# Extract column 2 from a TSV file (v1.4+)
fedit -file data.tsv -op fields -col 2

# Write content with literal backslashes — no \n escape expansion (v1.6.0)
fedit -file config.txt -op writeraw -text "path=C:\\Users\\admin"

# Hex-encode tricky text to sidestep shell quoting (v1.6.0)
# fwencode produces the hex; fedit decodes it before writing
fedit -file config.txt -op write -texthex 706174683d2f746d70

# Overwrite a file cleanly then insert new content (v1.6.0)
fedit -file config.txt -op insert -line 0 -cleanfirst -text "# regenerated"

# Get bare line numbers for scripting (v1.6.0)
fedit -file main.go -op find -match "TODO" -x 2>$null

# Regex replace on a multi-GB log without loading it into memory (v1.4+)
fedit -file huge.log -op replaceall -match 'ERROR' -text 'WARN' -stream

All Operations

show — Display file contents

# Entire file
fedit -file app.conf -op show

# Lines 50-75 only
fedit -file app.conf -op show -line 50 -end 75
# Last 10 lines
fedit -file app.conf -op show -line -10:

# Lines 50 to 5th from end
fedit -file app.conf -op show -line 50 -end -5

# Lines 100-103 (relative range)
fedit -file app.conf -op show -line 100:+3

# Show from one anchor to another (no line numbers needed)
fedit -file config.go -op show -match "func Start" -endmatch "func End"

find — Search for lines matching a substring

# Find all lines containing "ERROR"
fedit -file /var/log/app.log -op find -match "ERROR"

# Output includes context lines and occurrence numbers
# Use -nth to target a specific match in other operations

Pro tip: Run find first to get line numbers, then use replace or delete with exact lines.


insert — Insert content after a line number

# Insert a comment after line 1
fedit -file script.sh -op insert -line 1 -text "# Added by deploy script" -v

# Insert multiple lines from a file
fedit -file config.yaml -op insert -line 10 -textfile extra-config.yaml -v

insertafter — Insert after a matching line (RECOMMENDED)

# Add a firewall rule after the matching comment
fedit -file iptables.rules -op insertafter -match "# Custom rules" -text "-A INPUT -p tcp --dport 8080 -j ACCEPT" -v

# Target the 2nd occurrence
fedit -file nginx.conf -op insertafter -match "server {" -nth 2 -text "    listen 8443 ssl;" -v

# Target the last occurrence
fedit -file docker-compose.yml -op insertafter -match "volumes:" -nth -1 -text "      - /data:/data" -v

insertbefore — Insert before a matching line (RECOMMENDED)

# Add a header before the first route definition
fedit -file routes.rb -op insertbefore -match "get '/'" -text "  # === Public Routes ===" -v

# Insert a dependency before the closing bracket
fedit -file package.json -op insertbefore -match "}" -nth -1 -textfile new-deps.txt -v

replace — Replace a line range with new content

# Replace a single line
fedit -file config.ini -op replace -line 15 -end 15 -text "max_connections = 200" -v

# Replace lines 30-35 with content from a patch file
fedit -file server.conf -op replace -line 30 -end 35 -textfile patched-block.txt -v
# Replace a section by content anchors (no line numbers needed)
fedit -file CHANGELOG.md -op replace -match "## v1.6" -endmatch "## v1.5" -textfile new-section.txt -v

replaceall — Global find-and-replace

# Change all occurrences of old domain to new
fedit -file nginx.conf -op replaceall -match "old.example.com" -text "new.example.com" -v

# Update a version string everywhere
fedit -file Makefile -op replaceall -match "1.1.0" -text "1.2.0" -v

---


### fields -- Extract a column from delimited files (v1.4.0)

```bash
# Extract column 2 from a tab-separated file (default delimiter: tab)
fedit -file data.tsv -op fields -col 2

# Extract the third field from a CSV
fedit -file report.csv -op fields -col 3 -delim ","

# Extract usernames from /etc/passwd (colon-delimited)
fedit -file /etc/passwd -op fields -col 1 -delim ":"

Output goes to stdout for piping. Lines shorter than -col are skipped silently. Always streaming -- no memory limit regardless of file size.

-stream -- Large-file streaming mode (v1.4.0)

Add -stream to replaceall or find to process files line-by-line without loading them into memory. 10 MB per-line buffer handles JSON blobs and minified files. Atomic integrity: writes to a temp file then renames -- original is untouched on interruption.

# Replace a pattern in a multi-GB log file
fedit -file server.log -op replaceall -match "10.0.0.1" -text "10.0.0.2" -stream

# Regex replace in a huge file with capture groups
fedit -file big.csv -op replaceall -match-regex 'id_(\d+)' -text 'ID_$1' -stream

# Streaming find -- grep-style output to stdout
fedit -file huge.log -op find -match "FATAL" -stream

Supported with -stream: replaceall (literal and regex), find. Not supported: move, copy, map (these require full file structure in memory).

writeraw — Write without escape expansion

# Write a Windows path without double-escaping backslashes
fedit -file config.ini -op writeraw -text "basedir=C:\\Program Files\\App"

# Write content from a file as-is
fedit -file output.txt -op writeraw -textfile template.txt

Unlike write, writeraw treats \n as two characters (backslash + n), not a newline.


writelines — Write lines interactively from stdin

fedit -file notes.txt -op writelines
# Type lines at the > prompt, Ctrl+Z (Windows) or Ctrl+D (Unix) to finish

-texthex — Hex-encoded input (v1.6.0)

Encode text to hex first (e.g. with fwencode), then pass the hex string as -text. Eliminates all shell-quoting issues with special characters.

# Decode hex string and write — no quoting gymnastics needed
fedit -file deploy.sh -op write -texthex 23212f62696e2f62617368

-matchhex / -endmatchhex — Hex-encoded anchors (v1.8.1)

Same idea as -texthex, but for the search anchors. PowerShell mangles a double quote inside -match before fedit sees it, so case "replace": silently becomes case replace: and matches nothing. Pass the anchor as hex instead; fedit decodes it into -match (or -endmatch) once, so every op that takes an anchor works unchanged.

# Find a line containing double quotes (hex of: case "replace":)
fedit -file main.go -op find -matchhex 6361736520227265706c616365223a

Invalid hex exits 1 with matchhex: invalid hex string: ... (or endmatchhex: ...).


Multiple files -- comma list or glob (show/find/map v1.8.1, mutation unreleased)

-file accepts a comma-separated list, a glob, or both. A real file with that exact name wins over glob expansion.

Read-only ops (show, find, map) print a ==> path <== header per file:

# Find TODO in every Go file in the current folder
fedit -file '*.go' -op find -match 'TODO'

# Map two files at once
fedit -file main.go,mcp.go -op map -lang go
  • find skips files with no hits (grep-style) and exits 1 only if nothing matched in any file.
  • map skips unrecognized extensions with a SKIP line.
  • A summary line reports how many files had results.

Mutating ops (replaceall, insertafter, insertbefore, delete) run on each file on its own:

# Replace in every matching file
fedit -file '*.conf' -op replaceall -match 'old.example.com' -text 'new.example.com'

# Insert after the first match in each file
fedit -file a.yaml,b.yaml -op insertafter -match 'servers:' -text '  - new'

# Delete from a start line through an end line in each file (-endmatch is inclusive)
fedit -file '*.md' -op delete -match '<!-- start -->' -endmatch '<!-- end -->'
  • A problem in one file (no match, unreadable, read-only, write failed) is reported as SKIP and never stops the batch. A skipped file is never modified.
  • The report lists OK or SKIP per file, then N/M file(s) succeeded. Exit code 0 if at least one file succeeded, 1 if none did.
  • Only -match (plus -endmatch for delete), -nth and -text/-textfile/-texthex are accepted. -match-regex, -block, -line, -cleanfirst, -stream and -files are rejected, and so are the other mutating ops (replace, write, move, copy), all before any file is read. For a global rename with regex across files, use -files GLOB with replaceall.
  • CLI only: the MCP tools still take a single file.

Cross-file copy and move -- -dest (unreleased)

-dest copies or moves a block from -file into one or more other files. It takes a comma-separated list of existing files (no globs). The source is chosen the usual way: -line/-end, -match/-endmatch, or -block with -lang. The destination anchor is resolved separately inside each destination file, using one of -after, -before, -aftermatch, -beforematch, -afterblock or -beforeblock (the block anchors need -lang).

# Copy a function into two other files, right after a named block in each
fedit -file a.go -op copy -block Helper -lang go -dest b.go,c.go -afterblock Init

# Move a config section into two files, before a marker line in each
fedit -file base.conf -op move -match '[cache]' -endmatch '[/cache]' -dest x.conf,y.conf -beforematch '[logging]'

# Paste 2 copies in each destination
fedit -file src.txt -op copy -line 5:+3 -dest a.txt,b.txt -aftermatch ANCHOR -times 2
  • Each destination is handled on its own. A problem in one (missing file, anchor not found, read-only, write failed) is reported as SKIP and that file is never modified. The batch never aborts.
  • move writes all the destinations first. The source text is removed only if at least one destination write succeeded. If every destination fails, the source is left intact, so text is never lost.
  • The report lists OK or SKIP per destination, then N/M file(s) succeeded. Exit code 0 if at least one destination succeeded, 1 if none did.
  • A destination that is the source file itself is a SKIP, and a file listed twice is used once.
  • Only copy and move accept -dest. It is rejected with any other op, and with -match-regex, -cleanfirst, -stream or -files, before any file is read. It does not combine with a multi -file list: -file must be the single source file.
  • Cross-file transfer moves text, not dependencies. A Go function that calls fmt.Println lands textually perfect in a file with no fmt import, and that file will not compile until you add the import.
  • CLI only: the MCP tools still take a single file.

-cleanfirst — Truncate before writing (v1.6.0)

# Clear the file then insert fresh content at line 0
fedit -file output.txt -op insert -line 0 -cleanfirst -text "# regenerated"

-x — Machine-readable output (v1.6.0)

# Get bare line numbers from find (stdout only, no context noise)
fedit -file main.go -op find -match "TODO" -x 2>$null

# Extract CSV column with no stats footer
fedit -file data.csv -op fields -col 2 -delim "," -x

v1.5.0: HCL/Terraform block mapper (-lang hcl)

Move, copy, and refactor Terraform blocks by name — no line numbers needed. Accepts -lang hcl, -lang tf, or -lang terraform (all equivalent).

Supported block types: resource, data, module, provider, variable, output, locals, terraform, moved, import, check.

# Move a resource block before another
fedit -file main.tf -op move -block 'resource "aws_instance" "web"' \
      -beforeblock 'resource "aws_s3_bucket" "data"' -lang hcl -v

# Copy a variable definition (scaffold new variable from existing)
fedit -file variables.tf -op copy -block 'variable "instance_type"' \
      -after 20 -lang hcl -v

# Reorder provider blocks
fedit -file providers.tf -op move -block 'provider "google"' \
      -beforeblock 'provider "aws"' -lang hcl -v

Nested blocks (e.g. ingress {} inside a resource) are correctly ignored — only top-level blocks are matched.

v1.5.0: Nix block mapper (-lang nix)

Move and copy top-level attribute bindings in Nix expression files. Handles attribute sets (name = { }), lists (name = [ ]), and dotted attributes (programs.git = { }).

# Reorder home-manager program configs
fedit -file home.nix -op move -block "programs.git" \
      -beforeblock "programs.ssh" -lang nix -v

# Copy a service config as a scaffold
fedit -file configuration.nix -op copy -block "services.nginx" \
      -after 50 -lang nix -v

move — Move a line range to a new position

# Move lines 100-120 to after line 200 (explicit range)
fedit -file server.go -op move -line 100 -end 120 -after 200 -v

# Move a function block to before another function (content-matched)
fedit -file routes.go -op move -match "func OldHelper(" -end 45 -beforematch "func NewHelper(" -v

# Swap two nginx server blocks
fedit -file nginx.conf -op move -match "server {" -endmatch "# end server 1" -aftermatch "# end server 2" -v

# Cut once, scaffold 3 copies at destination
fedit -file main.go -op move -line 5 -end 12 -after 100 -times 3 -v

Rules:

  • Destination may not overlap the source range — fedit reports a precise error with line numbers.
  • -times N: cut once, paste N times. Net delta = blockSize × (N−1). Default times=1 = zero delta.

copy — Copy a line range to a new position

# Copy a config block to after a section header
fedit -file values.yaml -op copy -line 50 -end 65 -aftermatch "# staging" -v

# Duplicate a test fixture 10 times for parameterised tests
fedit -file fixtures_test.go -op copy -match "func TestCase(" -end 30 -after 200 -times 10 -v

# Reorder Python classes (copy source before target; overlap is allowed)
fedit -file processor.py -op copy -match "class ModuleProcessor_15" -endmatch "class ModuleProcessor_16" -beforematch "class ModuleProcessor_13" -v

Rules:

  • Snapshot semantics: source range is read once before any writes. All N copies are identical clones of the original, even when destination overlaps source.
  • Net delta = blockSize × times.

---

### delete — Remove lines

```bash
# Delete a single line
fedit -file hosts -op delete -line 12 -end 12 -v

# Delete a block (lines 40-55)
fedit -file config.yaml -op delete -line 40 -end 55 -v

write — Create or overwrite a file

# Create a new file
fedit -file /tmp/note.txt -op write -text "Deployment started" -v

# Write multi-line content from another file
fedit -file /etc/motd -op write -textfile new-motd.txt -v

map — Structural overview of a file

# Map a Go file — see all functions, types, imports
fedit -file main.go -op map -lang go

# Map a Dockerfile — see stages and instructions
fedit -file Dockerfile -op map -lang dockerfile

# Map a Makefile — see variables, targets, duplicates
fedit -file Makefile -op map -lang makefile

Supported Map Languages (17)

LanguageKey Structures Detected
gopackage, imports, types, interfaces, functions
pythonimports, classes, functions, decorators
javascriptimports, exports, classes, functions, arrows
typescript(same as javascript)
cssimports, custom properties, selectors, @media, @keyframes
rustuse, mod, struct, enum, trait, impl, fn, macro_rules!
javapackage, imports, classes, interfaces, enums, methods
csharpusing, namespace, classes, interfaces, records, properties
htmldoctype, head/body, headings, scripts, links, forms, ids
sqlCREATE, ALTER, DROP, INSERT, SELECT, indexes, triggers
yamldocument separators, top-level keys
tomltables, array tables, top-level keys
markdownheadings, code blocks, links
rubyrequires, modules, classes, methods, attributes
phpnamespace, use, classes, interfaces, traits, functions
dockerfileFROM stages, all instructions
makefileincludes, variables, .PHONY, targets

All mappers detect duplicates and flag them with warnings.


Flags Reference

FlagDescription
-file PATHTarget file (required); comma list or glob for show/find/map and replaceall/insertafter/insertbefore/delete (mutation unreleased)
-dest PATH,PATHCross-file copy/move: comma list of destination files, each needs one anchor flag (-after, -before, -aftermatch, -beforematch, -afterblock, -beforeblock); unreleased
-op OPOperation to perform (required)
-line NStarting line number (1-based); N:+M=range, -N=from end, -N:=last N lines, :=EOF
-end NEnding line (for ranges); -N counts from end of file
-text "s"Inline text content
-textfile FRead content from a file
-match "s"Substring to search for
-nth NWhich occurrence (default 1, -1 = last)
-lang LANGLanguage for map operation
-vVerify: show affected lines after edit
-endmatch "s"Content-anchor end of range (show/replace/delete/move/copy)
-quietSuppress stdout on success; exit code signals result

Real-World Examples

Patch an Nginx config in a deploy script

#!/bin/bash
# Update upstream server and reload

fedit -file /etc/nginx/conf.d/app.conf \
  -op replaceall \
  -match "server 10.0.1.50:8080" \
  -text "server 10.0.1.51:8080" -v

nginx -t && systemctl reload nginx

Add a cron job to crontab

fedit -file /etc/crontab \
  -op insertafter \
  -match "# Custom jobs" \
  -text "0 2 * * * root /opt/backup.sh" -v

Bulk update version in multiple files

for f in Makefile config.yaml package.json; do
  fedit -file "$f" -op replaceall -match "1.1.0" -text "1.2.0" -v
done

Inspect a Dockerfile before editing

fedit -file Dockerfile -op map -lang dockerfile
# See the structure, then surgically edit:
fedit -file Dockerfile -op insertafter -match "FROM alpine" -text "RUN apk add --no-cache curl" -v

Remove a block from a config

# First, find the lines
fedit -file app.conf -op find -match "deprecated-feature"
# Output says lines 44-49, so:
fedit -file app.conf -op delete -line 44 -end 49 -v

PowerShell workflow (Windows sysadmins)

# Set an alias
$f = "C:\tools\fedit.exe"

# Find all TODO comments in Go code
& $f -file main.go -op find -match "TODO"

# Replace a config value
& $f -file config.toml -op replaceall -match 'debug = true' -text 'debug = false' -v

# Map a file to understand its structure
& $f -file main.go -op map -lang go

Tips

  • Always use -v on mutations — it costs nothing and saves you from blind edits
  • Use find before replace — get the exact line numbers first
  • Prefer insertafter/insertbefore over insert — matching is more resilient than hardcoded line numbers
  • Use -nth -1 to target the last occurrence of a match
  • Use -textfile for multi-line inserts — avoids shell quoting headaches
  • map before editing unfamiliar files — see the structure first

LLM Benchmark

How well do current LLMs use fedit vs. rewriting whole files?

We tested Claude (Sonnet 4.6), ChatGPT (GPT-4o), and Gemini (2.5 Pro) on 7 realistic editing tasks across files of 565-1206 lines. Each model was tested twice per task: once asked to output the whole file ("raw"), once asked to output fedit commands only ("fedit").

Results

Legend: PASS+ = optimal one-line solution | PASS = correct | PASS* = correct content, formatting artifact | PARTIAL = correct intent, off-by-one or fragile | FAIL = wrong output

TestFileTaskClaude rawClaude feditGPT rawGPT feditGemini rawGemini fedit
T1processor_600.go (575 L)Insert method after specific struct methodPASSPARTIALFAILPARTIALPASSFAIL
T2config_800.yaml (1059 L)Replace 24-line deployment blockPASSPASSFAILFAILPASSFAIL
T3styles_500.css (565 L)Find & delete CSS rule blockPASSPASSPASS*FAILPASS*FAIL
T4system_1000.go (980 L)Global rename (36 occurrences)PASSPASS+FAILPASS+PASS*PASS+
T5analytics_700.py (682 L)3-step chain (insert + delete + replace)PASSPARTIALFAILFAILPASSFAIL
T6dashboard_900.html (891 L)Insert before 3rd matching button (-nth)PASSPARTIALFAILPASS+PASSFAIL
T7engine_1200.go (1196 L)Map + targeted insert after methodPASSPASSFAILFAILPASSFAIL

Top-line numbers

ModelRaw modeFedit mode
Claude7/7 PASS4 PASS, 3 PARTIAL
ChatGPT1/7 PASS*2 PASS+, 1 PARTIAL, 4 FAIL
Gemini7/7 PASS*1 PASS+, 6 FAIL

Key findings

1. ChatGPT cannot output large files reliably. 6 of 7 raw tests truncated. The most striking failure was T6, where it inserted the literal placeholder line [... TRUNCATED FOR BREVITY ...] into otherwise valid HTML — a uniquely dangerous failure mode where output looks structurally complete but contains placeholder strings. T7 truncated 1206 lines down to 166.

2. LLMs hallucinate line numbers — and it gets worse with chain length. Gemini's line-number errors grew across the suite: off by 36-56 lines on T2 (single replace), 45 lines on T3, then 73 lines on T5 (3-step chain). ChatGPT showed similar drift on T5. Claude was the only model that produced runnable line-numbered commands consistently.

3. Content-matching ops are immune to the hallucination class. Gemini failed every fedit test that required line numbers (T1, T2, T3, T5, T6, T7), but PASSED T4 — which used replaceall with a content match. Same model, same task complexity, dramatically different reliability. The bottleneck is counting, not understanding.

4. insertafter on a function declaration matches the OPENING line. Models repeatedly tried insertafter -match "func MyFunc()" to insert content AFTER the function ended. This is correct fedit behavior (it inserts after the matched line) but inserts the new code INSIDE the function body. Workaround: insertbefore the NEXT structural element. Confirmed in T1 and T7.

5. Even Claude flubs line-number direction. T6: Claude used insert -line 30 to add a banner before the 3rd button at line 30. But insert -line N adds content AFTER line N. The correct command was insertbefore -match "View Details" -nth 3. Even the strongest model gets direction wrong about 1 in 6 single-step ops when reaching for line numbers.

6. -match is single-line only — and that's a feature. ChatGPT (T3 fedit) tried to pass a multi-line CSS block as -match with literal \n escapes. fedit doesn't interpret escapes in -match and matches only single lines. This kept the operation safe (zero matches → no edit) rather than allowing a fragile multi-line pattern that could easily mismatch.

7. Markdown rendering is a hidden adversary. ChatGPT and Gemini outputs lost __name__ → name, __init__ → **init**, and stripped CSS/Python indentation when rendered in chat UIs. The underlying files (when downloaded directly) were correct. This affects copy-paste workflows but not API integrations. For best results, use the model's "copy code" button or download links — never select-and-copy from rendered output.

8. Three different models converged on the same one-liner for T4. Claude, ChatGPT, and Gemini all independently produced fedit -op replaceall -match "FetchUser" -text "GetAccount". When the right tool is obvious, models reach for it. fedit's design surface makes the right tool obvious for content-driven edits.

Recommendations for LLM-driven workflows

Based on these results, prefer content-matching operations over line-number operations when generating fedit commands from an LLM:

  • Use insertbefore -match "next anchor" instead of insert -line N
  • Use replaceall -match "old" -text "new" instead of replace -line N -end M
  • Use find -match and show to confirm line numbers before any line-numbered op
  • Reserve line-numbered ops for cases where an MCP-connected LLM has just run fedit_find or fedit_show and has verified the line in context

For best results, give the LLM an MCP connection to fedit (see MCP Server Mode) so it can fedit_find and fedit_show before mutating. This eliminates the line-number hallucination class entirely and was the path Claude consistently took when it had recon available.

Methodology

  • Each test run in a fresh chat with the original file uploaded
  • Prompts identical across models; only "raw" vs "fedit" framing differed
  • Output saved verbatim, then diffed against ground truth via Compare-Object
  • Ground truth verified by running fedit commands locally and re-reading output
  • All 7 raw outputs that PASSED Compare-Object empty (byte-identical to ground truth): Claude T2, T3, T4, T5, T6, T7 + Gemini T5, T6, T7
  • PASS* indicates byte-difference from indentation-stripping in chat UI render, with content structurally correct (verified by re-running fedit ops on the saved file)

Test corpus

7 synthetic files (565-1206 lines) covering Go, YAML, CSS, Python, and HTML. All test files, prompts, and ground-truth outputs are available in the bench/ directory of this repository.

MCP Server Mode

fedit includes a built-in Model Context Protocol (MCP) server, so AI coding assistants can use fedit as a tool for precise file edits.

fedit mcp

This starts a JSON-RPC 2.0 server on stdin/stdout. The server exposes all 14 editing operations as MCP tools:

ToolDescription
fedit_showDisplay file contents (full or line range)
fedit_insertInsert content after a line number
fedit_deleteDelete one or more lines
fedit_replaceReplace a line range with new content
fedit_replaceallGlobal find-and-replace; regex capture groups; multi-file glob; -stream for large files
fedit_writeCreate or overwrite a file
fedit_writerawCreate or overwrite a file with no escape expansion (backslashes literal)
fedit_mapStructural overview (17 languages)
fedit_findFind lines matching a substring
fedit_insertafterInsert after a matching line
fedit_insertbeforeInsert before a matching line
fedit_moveMove a line range to a new position; destination-overlap rejected
fedit_copyCopy a line range; snapshot semantics, overlap allowed, -times N
fedit_fieldsExtract column N from CSV/TSV/delimited file (-col N -delim CHAR); always streaming

Configuring with Claude Desktop

Add to your claude_desktop_config.json:

{
  "mcpServers": {
    "fedit": {
      "command": "fedit",
      "args": ["mcp"]
    }
  }
}

For a full setup guide (Windows paths, troubleshooting, Cursor/Cline/Windsurf snippets): CLAUDE_DESKTOP.md

Configuring with Cursor

Add to .cursor/mcp.json in your project root:

{
  "mcpServers": {
    "fedit": {
      "command": "fedit",
      "args": ["mcp"]
    }
  }
}

Why MCP?

Without fedit, LLMs rewrite entire files — burning tokens and introducing drift. With fedit as an MCP tool, the model calls fedit_map to see structure, fedit_find to locate targets, and fedit_replace or fedit_insertafter to make surgical edits. Every mutation returns stats (line delta, elapsed time) so the model can verify its work.


FAQ

Do I need fedit if my team has a solid PR review process?

Probably not for human-driven edits where you review every diff. fedit shines in two specific cases:

  1. Whole-file rewrites in long configs. When an LLM regenerates a 600-line values.yaml to change one key, the diff is technically reviewable but practically nobody scrolls to line 412 to confirm nothing else moved. fedit makes the diff exactly N lines for an N-line change.

  2. Agent loops without a human in the middle. If you run agents semi-autonomously (issue → branch → PR opened, you review at the end), giving the agent surgical tools (fedit_replace, fedit_insertafter) instead of "rewrite the whole file" measurably reduces review surface area.

If your workflow already catches these, the CLI is overkill. The MCP server may still be useful as an agent primitive.

Does fedit support streaming for all operations?

Currently replaceall and find support -stream mode. move and copy require the full file in memory because they need to read both source and destination ranges. map requires structure analysis. fields is always streaming.

What is the maximum file size fedit can handle?

In default (in-memory) mode: limited by available RAM, typically fine up to a few hundred MB. With -stream: unlimited -- fedit processes one line at a time with a 10 MB per-line buffer.

Does fedit support Terraform / HCL?

Yes, as of v1.5.0. Use -lang hcl (or -lang tf/terraform) with -block, -beforeblock, -afterblock to move and copy Terraform blocks by name. All 11 top-level block types are supported. The map op supports HCL/Terraform and Nix as of v1.5.0 -- fedit_map -lang hcl returns all top-level block names and line ranges.

Does fedit preserve comments and formatting?

Yes, by design. fedit does not parse-and-reformat — it operates on raw text bytes at line granularity.

If you replace -line 47 -end 49, lines 1–46 and 50–EOF are untouched byte-for-byte: comments, trailing whitespace, mixed indentation, BOM markers, all preserved exactly. This is the main reason fedit is line-addressable instead of AST-based: AST round-trips lose too much (trailing commas, comment positioning, key ordering, blank-line spacing).

This matters most for nginx configs (inline comments documenting why a directive exists), Ansible playbooks (YAML anchors and merge keys), and any file where a human formatting choice carries semantic weight.

Is the MCP server Claude-specific or Cursor-specific?

Neither — it targets the open MCP spec. Plain JSON-RPC 2.0 over stdin/stdout against the public Model Context Protocol schema. No vendor-specific bits.

Tested with Claude Desktop; should work with any compliant MCP client (Cursor, Continue, Cline, custom integrations). Tool definitions and JSON Schema live in mcp.go if you want to inspect the surface area before wiring it up.

What if the target string appears multiple times in the file?

Use the -nth N flag:

  • -nth 1 (default) — first match
  • -nth 2 — second match
  • -nth -1 — last match
  • Any positive integer picks that occurrence

Example targeting the third http.Redirect call:

fedit -file router.go -op insertbefore -match "http.Redirect" -nth 3 -textfile patch.txt -v

If you are not sure how many matches exist, run find first — it lists every match with context so you can disambiguate before mutating:

fedit -file router.go -op find -match "http.Redirect"

Does chaining commands cause line-number drift?

No. Each fedit invocation reads the file fresh, so line numbers always reflect the current state on disk. You can chain operations safely with ; (PowerShell) or && (bash):

fedit -op replaceall -file f.conf -match "old.com" -text "new.com" -v ; fedit -op replace -file f.conf -line 42 -end 42 -text "fixed" -v

The tradeoff is one disk read per operation, which is negligible for editing workflows.

If you want drift-immunity by design, prefer content-based targeting (insertafter / insertbefore / replaceall with -match) over line numbers — those resolve the target on each invocation regardless of what previous ops did.

LLM Benchmark

fedit is the missing layer between LLMs and your codebase. LLMs excel at generating correct code — but applying that code to the right line of a 900-line file, across a 3-step chain, without hallucinating line numbers? That is a different problem.

We tested Claude Sonnet 4.6, ChatGPT GPT-4o, and Gemini 2.5 Pro on 7 realistic editing tasks against files of 565–1206 lines. Each task ran twice: once asking the model to output the whole rewritten file ("raw"), once asking it to output fedit commands only ("fedit"). All outputs were diffed byte-for-byte against ground truth.

Top-line results

ModelRawfedit
Claude Sonnet 4.67/7 PASS4/7 PASS · 3 PARTIAL
ChatGPT GPT-4o1/7 PASS (6 truncated)2/7 PASS · 1 PARTIAL · 4 FAIL
Gemini 2.5 Pro7/7 PASS*1/7 PASS · 6 FAIL

*PASS* = correct content, formatting artifact from chat UI render.

Per-test results

TestFileTaskCL rawCL feditGPT rawGPT feditGM rawGM fedit
T1processor.go (575 L)Insert method after struct methodPASSPARTIALFAILPARTIALPASSFAIL
T2config.yaml (1059 L)Replace 24-line deployment blockPASSPASSFAILFAILPASSFAIL
T3styles.css (565 L)Find and delete CSS rulePASSPASSPASS*FAILPASS*FAIL
T4system.go (980 L)Global rename (36 occurrences)PASSPASS+FAILPASS+PASS*PASS+
T5analytics.py (682 L)3-step chain (insert + delete + replace)PASSPARTIALFAILFAILPASSFAIL
T6dashboard.html (891 L)Insert before 3rd match (-nth)PASSPARTIALFAILPASS+PASSFAIL
T7engine.go (1196 L)Map + targeted insert after methodPASSPASSFAILFAILPASSFAIL

PASS+ = optimal one-liner  ·  PASS = correct  ·  PARTIAL = correct intent, fragile execution  ·  FAIL = wrong output

Key findings

1. ChatGPT cannot reliably output large files. 6 of 7 raw tests were truncated. T6 inserted the literal placeholder [... TRUNCATED FOR BREVITY ...] into otherwise valid HTML. T7 compressed 1196 lines down to 166.

2. LLMs hallucinate line numbers — and it compounds with chain length. Gemini drifted by 36–56 lines on T2, 45 on T3, then 73 lines on a 3-step chain (T5). Only Claude produced correct line-numbered commands consistently.

3. Content-matching ops are immune to the drift problem. Gemini failed every fedit test that required line numbers — but PASSED T4 with replaceall -match. Same model, same task complexity, dramatically different reliability. The bottleneck is counting, not understanding.

4. All three models converged on the same one-liner for T4. Claude, ChatGPT, and Gemini independently produced fedit -op replaceall -match "FetchUser" -text "GetAccount". When the right tool is obvious, models reach for it.

Recommendations

Prefer content-matching operations over line-number operations when generating fedit commands from an LLM:

  • Use insertbefore -match "next anchor" instead of insert -line N
  • Use replaceall -match "old" -text "new" instead of replace -line N -end M
  • Use find -match and show to confirm position before any line-numbered op
  • Use -block/-lang to target named functions and structs without any line numbers

For best results, give the LLM an MCP connection to fedit (fedit mcp). This eliminates the line-number hallucination class entirely — the path Claude consistently took when recon was available.

Full results, ground truth files, test prompts, and the 7-file corpus: amalexhandler.com/fedit#benchmark

License

MIT — see LICENSE


Built by Amalex — makers of Amalex Handler, the universal file transporter.

Related MCP servers

Japanese court-run real-estate auctions (BIT). 5 tools, ~1,480 active listings. CC BY 4.0.

0
MIT
View repository →

Look up any Australian company by ABN, ACN or name. USD 0.01 per lookup, paid in USDC via x402.

0
TypeScript
View repository →

Local, offline transcription, speakers, keyframes, on-screen text and review of any audio or video.

0
TypeScript
Apache-2.0
View repository →

MCP server for FonParam API - Turkish mutual funds data

3
TypeScript
MIT
View repository →

Compiles structured specs into SCORM 1.2/2004 e-learning packages. 30 tools, quality gate, no LLM.

5
Python
MIT
View repository →

Read-only MCP server for querying Kenda token spend and waste

View repository →