
Fedit
io.github.kehsiaocuba-ux由 GitHub 帳號 kehsiaocuba-ux 發布v1.12.0更新於 Nov 3, 2026
用於精準檔案編輯的 Go MCP 伺服器:插入、替換、移動、複製、試執行、經驗證的 replaceall。
AI 翻譯由 AI 模型根據下方的原文描述翻譯,可能有誤。
原文描述
Go MCP server for surgical file edits: insert, replace, move, copy, dry-run, verified replaceall.
概覽
Fedit 的本機 MCP 服務;無需登入;STDIO。
暫無 AI 概覽:以下資訊來自註冊中心條目和服務本身。
安裝
在 SourceWeft 中
- 開啟 儀表板中的 Fedit,將其新增到工作區。
- 為需要使用其工具的對話啟用該服務。
Desktop only,透過 STDIO。 STDIO 服務會啟動本機處理程序,因此需要 SourceWeft 桌面主機。
其他 MCP 客戶端
參照 儲存庫 中的啟動說明。
README
fedit — Fast File Editor for the Terminal
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.
Why fedit?
- One binary, zero dependencies — pure Go, runs everywhere
- 21 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
- Many files at once — a comma list or glob in -file, plus cross-file copy, move and replace with -dest (since v1.9.0)
- Safe — no in-place unless you say so, never touches files you did not name
Install
Go install (recommended):
Or download the binary from GitHub Releases and put it in your PATH.
Verify:
Quick Start
All Operations
show — Display file contents
find — Search for lines matching a substring
Pro tip: Run find first to get line numbers, then use replace or delete with exact lines.
insert — Insert content after a line number
insertafter — Insert after a matching line (RECOMMENDED)
insertbefore — Insert before a matching line (RECOMMENDED)
replace — Replace a line range with new content
replaceall — Global find-and-replace
- Every replaceall is verified by default: after the write, fedit re-reads the file from disk and prints
VERIFY: PASSorVERIFY: FAILfollowed by the changed lines. This covers a single file,-match-regex,-stream,-filesand a file list in-file, andfedit_replaceallover MCP.-vadds a context window for every match (not printed with-stream). - Exit codes for replaceall: 0 ok, 1 the edit did not happen, 3 the file was written but verification failed or could not be done. With several files, one FAIL file makes the exit code 3 even when the others succeeded, and the MCP result is an error.
fields -- Extract a column from delimited files (v1.4.0)
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.
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
Unlike write, writeraw treats \n as two characters (backslash + n), not a newline.
writelines — Write lines interactively from stdin
-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.
-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.
Invalid hex exits 1 with matchhex: invalid hex string: ... (or endmatchhex: ...).
Multiple files -- comma list or glob (show/find/map since v1.8.1, mutation and MCP since v1.9.0, -sourcefile and -sourceblock with a list since v1.11.0)
-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 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, replace, write) run on each file on its own:
- 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, SKIP or FAIL per file, then
N/M file(s) succeeded. A FAIL is a replaceall file that was written but did not pass the on-disk verification. Exit code 0 if at least one file succeeded and none failed, 1 if none succeeded, 3 if any file is FAIL (FAIL wins). - Only -match (plus -endmatch for delete and replace), -nth and -text/-textfile/-texthex are accepted, plus -sourcefile/-sourceblock/-lang on insertafter and insertbefore (since v1.11.0). -match-regex works with replaceall, and write needs no -match and writes the same content to every file. -block, -line, -cleanfirst, -stream and -files are rejected, and so are move and copy, all before any file is read.
- MCP: fedit_replaceall, fedit_insertafter, fedit_insertbefore, fedit_delete and fedit_replace take a comma list or glob in file, with the same rules and a per-file report; block, line, stream and files are rejected. fedit_write always treats file as one path. fedit_show, fedit_find and fedit_map also take a list.
Cross-file copy, move and replace -- -dest (v1.9.0, MCP since v1.10.0)
-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).
- 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.
- copy and move take -dest with an anchor flag, and replace takes -dest with -destblock (below). 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. Since v1.11.0, copy, move and replace with -dest add an advisory note when a Go destination lacks a standard library import that the transferred lines use (advisory only: results and exit codes are unchanged).
- MCP: fedit_copy, fedit_move and fedit_replace take a dest parameter (a comma list of files) with the same anchors, and destblock for replace; same rules and a per-destination report; file must be a single path. fedit_insertafter and fedit_insertbefore take sourcefile and sourceblock (with lang); there file may also be a comma list or glob.
Replace a block in other files -- replace -dest with -destblock
replace -dest overwrites a named block in each destination with the source block or range from -file. -destblock names the block to overwrite and is required. The source is chosen as for copy and move, and -lang lets block names resolve.
- A destination that does not have that block is a SKIP, never an insert. Each destination is handled on its own, like copy and move.
- -destblock is only accepted with replace -dest.
Insert a block from another file -- -sourcefile and -sourceblock
insertafter and insertbefore can take their content from a named block in another file. -file stays the file being edited and the anchor is chosen as usual.
- -sourcefile and -sourceblock must be used together and need -lang. They cannot be combined with -text, -textfile or -texthex.
- -file may be a comma list or glob (since v1.11.0). The block is read once from -sourcefile and inserted into every file at that file's own -match, with a per-file report. A file without the match is a SKIP and is never modified. This is not a -dest fan-out: each target is edited at its own -match.
- Since v1.11.0, when the source and the destination are both Go files and the inserted block uses a standard library package the destination does not import, a hint is printed (stderr on the CLI, the OK message on MCP; advisory only: the insert is still done and the exit code is unchanged). With a file list the hint is added to that file's report line.
- The MCP tools take sourcefile and sourceblock as parameters on fedit_insertafter and fedit_insertbefore (with lang).
guided -- Natural-order entry point (CLI, since v1.11.0)
fedit guided takes the source, the verb and the destination in plain order and turns them into the explicit -op command, which it prints to stderr before running it. It is a CLI shortcut only: there is no MCP tool for it, and the engine and the explicit flags are unchanged.
- The source is a named block (the language comes from the file extension) or
"line N-M". - Several destinations: quote them as one comma list (
"b.go,c.go"), because PowerShell would otherwise split them. - Each destination is handled on its own. A missing file is created, and a missing anchor (or no position at all) appends at the end, with a note. A move runs its copies first and the move last, so the source is erased only after a write succeeded.
- Block names are matched by substring, as everywhere else. If a name matches several blocks, a single exact match wins (a note says so); otherwise you get a numbered choice in a terminal, or the list of candidates and exit code 2 when there is no terminal. The chosen block is then addressed by line numbers.
replaceoverwrites TARGET (default: the same name as the source) in each destination and skips a destination that does not have it.silentruns the plan with-quiet: no echo, no notes, and no engine report when every file succeeded (skips and errors still print).report-onlyruns the plan without the verify output and without the echo line, but keeps the notes and the engine's own summary. Both modifiers go last and work with every verb.drygoes last, aftersilentorreport-onlywhen you use one. It adds-dryto every step, so each step prints its diff and writes nothing, and the parent log record carriesdry=true. A destination file that does not exist is not created: the plan saysguided: dry-run: would create FILE and put the block into itinstead (for a move it adds that the block would then be removed from the source), and no diff is shown for that file. The bare prompt acceptsdry,silent dryandreport-only dry. A target block literally named dry cannot be the last word.- Bare
fedit guided(no other arguments) asks the questions one at a time: the operation, the source file and block, the destination, the position, then the modifier. Operation and position can be answered by name or by number, and a wrong choice is asked again (three tries). The prompts go to stderr and the answers are read from stdin, one per line, so piped input works. If the input ends before the last answer, nothing is changed.
-dry -- Preview a change without writing it (since v1.12.0)
-dry turns an operation that changes files into a preview. Nothing is written, truncated or created. fedit prints a unified diff (three lines of context, and /dev/null for a file that does not exist yet) to stdout, and the report on stderr ends with dry-run: applied=false (no file was changed). Exit codes are the same as for a real run, so a script can try a change before it makes it.
- The verify step is skipped, because nothing was written: replaceall and
-vprintVERIFY: SKIP (dry-run)and the exit code is 0. There is no VERIFY window and no STATS block;moveandcopykeep their own STATS block, which is computed from the plan. - Multi-file and
-destruns print one diff per file. The batch total readsN/M file(s) would succeed, each file line is worded as a preview (would replace on 1 line(s),would insert 1 line(s) at match on line 3,block "X" would be replaced with N line(s)) and a replaceall line ends withVERIFY: SKIP (dry-run), and a-destmove reportsmove: would remove lines N-M from SRCinstead of removing them. -streamwith-dryreads the file, runs the replacement and prints counts only: no diff, no temporary file, nothing written.- On the command line the summary line of a single-file run is worded as a preview too, with the wording the MCP tools use:
Would insert 1 line(s) after line 1 (4 total after),Would delete lines 2-2 (2 total after),Would write 1 line(s) to FILE,Would regex-replace on 1 line(s) (3 total). -cleanfirstwith-drytruncates nothing; the diff shows the old content removed and the new content added.- A run that would fail still fails: no match or a missing block gives the usual error and exit code, with no diff.
-dryworks with-outputresult. It cannot be combined withfedit -configorfedit -log merge(usage error, exit 1), and-dry=falseis refused too.- A dry run is logged like any run: its end record carries
dry=true(see "Audit log" below). Infedit guidedadd the worddryat the end; over MCP passdry_run(see "MCP Server Mode" below). - Limits: a guided dry run shows no diff for a destination file that does not exist yet, and a guided target block literally named dry cannot be the last word.
-cleanfirst — Truncate before writing (v1.6.0)
-x — Machine-readable output (v1.6.0)
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.
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 = { }).
move — Move a line range to a new position
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.- To move the block into other files instead, use
-dest(see Cross-file copy, move and replace above).
copy — Copy a line range to a new position
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.
- To copy the block into other files instead, use
-dest(see Cross-file copy, move and replace above). - Net delta = blockSize × times.
delete — Remove lines
write — Create or overwrite a file
map — Structural overview of a file
Supported Map Languages (21)
Most mappers detect duplicates (functions, tables, keys, header columns, blocks, attributes, selectors, headings, Dockerfile stages) and flag them with warnings.
Flags Reference
How block ends are found (since v1.11.0)
With -block NAME (and -sourceblock, -destblock, -beforeblock, -afterblock) the range of a block comes from a scanner for the language. Python and Ruby blocks do not use brace counting and are not affected. Go, JS and TS, Rust, Java, C#, PHP, HCL and Nix count braces (and brackets in Nix), and the count ignores:
- Strings, including multi-line ones: backtick strings (Go raw strings, JS template literals), C# verbatim (
@"...") and raw (""") strings, Java text blocks, Rust raw strings (r"...",r#"..."#), HCL heredocs (<<EOT,<<-EOT), PHP heredocs and nowdocs (<<<EOT,<<<'EOT', with an indented closing marker allowed) and Nix indented strings (two single quotes). In a Java text block a backslash escapes the next character, so an escaped\"""does not end the block. - Rust char literals, lifetimes and loop labels.
- Comments:
//and/* */(nested in Rust), and#in PHP, HCL and Nix (in PHP,#[starts an attribute and is not a comment). Nix has no//comment, because//is its update operator.
Known gaps: a JS regular expression with an unescaped /*, and a block comment that opens outside any block. For a block like that, check the range with show first and fall back to -line.
Audit log (since v1.12.0, off by default)
fedit can write one record per operation to an audit log: who ran what, on which files, with what result. It is off unless you turn it on, and while it is off nothing in a run changes. It covers command-line runs, guided runs and the MCP server.
Modes (-log-mode or FEDIT_LOG_MODE):
off(default): nothing is written.permissive: the operation always runs. If the log cannot be written, the records are saved in a spool folder under the fedit user folder,The action was not loggedis printed to stderr (over MCP it is also returned in the tool result as an extra text item), and the next logged run (orfedit -log merge) appends them to the log withlate=true.strict: if the begin record cannot be written, the run is refused before anything changes (exit 4). If the operation succeeded but the end record cannot be written, the exit code is 5. A run that already failed keeps its own exit code.
Settings are read in this order: flag, then environment variable, then the settings file (see "Settings file" below), then default. An invalid value is an error (exit 1, nothing edited) even when logging is off.
Every record is one line with a UTC timestamp, a level ([INFO] for exit 0, [WARNING] for 1, [ERROR] otherwise) and key=value pairs. The begin and end records of a run share the same id.
- Begin record:
op,source(cli, guided or mcp),user(with the domain on Windows),host,pid,cwd, then, when given,file,filesanddestas absolute paths (a glob keeps its pattern),text_lenandtext_sha256(ortextwith-log-content on;-texthexis hashed after decoding) andtextfile(the path), thentextfile_lenandtextfile_sha256of that file when it can be read (the content itself is never logged, not even with-log-content on; since v1.12.0). Over MCP a call that usesoutput_filealso logs it (an absolute path, plusoutput_append=truewhen it is set; since v1.12.0), and every call carriesclientandclient_version. In permissive mode anot loggednotice (the audit log could not be written, so the record went to the spool) is also returned in the MCP tool result as an extra text item after the result, besides the server stderr (since v1.12.0). - End record:
phase=end,exitandms, plus the result when the operation reports one:lines_changedfor every plain write operation on a single file (lines inserted, deleted, replaced or written; for move and copy the block size times the number of copies; none for a dry run or a failed call) and for replaceall, which also getsreplacements(occurrences;lines_changedcounts lines, and both are noted the same way over MCP) andverify(PASS or FAIL), and for multi-file runsfiles_total,files_ok,files_skip,files_failand a per-fileresultslist. A write on a single file also getssha256_beforeandsha256_after. - Dry run: a run with
-dry(also guideddryand MCPdry_run) is logged like any other run, and its end record carriesdry=true. For an existing filesha256_afteris the same assha256_before, a dry multi-file run logsverify=none, and a dry run notes noreplacementsand nolines_changed. - A usage error (no
-op, unknown-op) is not an operation and is not logged.-outputresultnever receives log records.
fedit -log merge appends the spooled records to the main log now, in any mode, using the FEDIT_LOG_* environment and the settings file. It prints Merged N spooled record(s) into FILE or No spooled records to merge on stderr, and exits 1 on a usage error, an invalid setting or a merge that stops. It is not logged itself.
guided: a plain one-step command is logged as one run with source=guided. A run with several steps or a file to create has a parent record (op=guided, steps, create) and one child run per step, each with parent=<id of the parent>. Strict refuses before the parent creates any file.
MCP: every tool call is one logged run with source=mcp. The settings come from the FEDIT_LOG_* environment of the server process and from the settings file (there are no flags), so set the environment variables in the env block of your MCP client config; FEDIT_CONFIG there points the server at another settings file. An error result is logged as exit 1 and a failed verification as exit 3. In strict mode a call whose begin record cannot be written returns an error result and changes nothing; a call that succeeded but whose end record cannot be written returns an error result that says the operation completed.
Limits: multi-file runs have per-file results but no per-file hashes; a guided parent exits 1 when any step fails.
Settings file (since v1.12.0)
The logging settings can also be kept in a settings file, so you do not need environment variables on every machine or in every MCP client config. The file is optional: without it nothing changes.
- Location:
fedit\fedit.iniin the user config folder (on Windows%APPDATA%\fedit\fedit.ini, on Linux~/.config/fedit/fedit.ini).FEDIT_CONFIGnames another file;~and%VAR%are expanded and a relative path is made absolute. - Format: one
key = valueper line. Blank lines and lines that start with#or;are ignored. Keys are not case-sensitive, there are no sections, and a UTF-8 byte order mark (Notepad adds one) is ignored. If a key appears twice, the last one wins. - Keys:
log_mode,log_file,log_max_mb,log_content,log_levelandlog_scope, with the same values as the matching flags in the table above. - Order: flag, then environment variable, then settings file, then default.
- Errors: a missing file is not an error. A file that cannot be read, a line that is not
key = valueand an invalid value are errors that name the file and the line (exit 1, before anything is edited), even when logging is off. An unknown key is a warning on stderr and is ignored. -noconfig(command line only) ignores the file for that run.guidedruns and the MCP server always read it.fedit -log merge -noconfigskips it for that merge; without-noconfiga broken file stopsfedit -log merge, and the message names-noconfig(since v1.12.0).fedit -configreads and changes the file without opening it by hand:pathprints its location,showlists every key with its effective value and where it comes from (environment, ini or default),get KEYprints one effective value (exit 1 for an unknown key), andset KEY VALUEandunset KEYcheck the key and the value, then write the file atomically (the folder is created when needed; comments, blank lines and line endings are kept).setandunsetare logged like any write operation, withconfig_keyandconfig_valuein the begin record, and are not available over MCP. A broken settings file stopsshow,get,setandunset(exit 1, naming the line);pathstill works.
Example:
Real-World Examples
Patch an Nginx config in a deploy script
Add a cron job to crontab
Bulk update version in multiple files
Inspect a Dockerfile before editing
Remove a block from a config
PowerShell workflow (Windows sysadmins)
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
Top-line numbers
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 ofinsert -line N - Use
replaceall -match "old" -text "new"instead ofreplace -line N -end M - Use
find -matchandshowto confirm line numbers before any line-numbered op - Reserve line-numbered ops for cases where an MCP-connected LLM has just run
fedit_findorfedit_showand has verified the line in context - Use
-block/-langto target named functions and structs without any line numbers
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. Full results, ground truth files, test prompts and the 7-file corpus: amalexhandler.com/fedit#benchmark.
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.
This starts a JSON-RPC 2.0 server on stdin/stdout. The server exposes all 14 editing operations as MCP tools:
The ten tools that change files (fedit_insert, fedit_delete, fedit_replace, fedit_write, fedit_writeraw, fedit_replaceall, fedit_insertafter, fedit_insertbefore, fedit_move and fedit_copy) also take an optional boolean dry_run (since v1.12.0). With dry_run true nothing is written, truncated or created. The result text starts with dry-run: applied=false (no file was changed), followed by a unified diff of each file that would change and then the summary of the tool, worded as a preview (Would insert ..., Would replace ..., Would write ...) because nothing was done; a real call keeps its past tense. A multi-file total reads N/M file(s) would succeed. A call that would fail is still an error, and the read-only tools have no dry_run parameter. A dry call is logged like any call, with dry=true on its end record.
All 14 tools also take two optional parameters, output_file (string) and output_append (boolean) (since v1.12.0), for results that should not enter the conversation, such as a large fedit_show, fedit_find or fedit_map result. With output_file the full result text is written to that file (UTF-8, no BOM; overwritten, or appended when output_append is true) and the tool returns only one line, result written to FILE (N lines) or result appended to FILE (N lines). The operation itself runs as usual, so a changing tool still changes its files; with dry_run the dry-run text (the applied=false line and the diff) is what is written. Some calls are refused before the tool runs and write nothing: output_append without output_file, a result file that is also an edited file (file, dest or a glob match) and a result file whose folder does not exist. A call that fails returns its error in full and leaves the result file untouched. If the result file cannot be written after the operation succeeded, the error says so and carries the text. A relative path resolves against the working directory of the server, like every other path. This is the MCP counterpart of -outputresult and -outappend on the command line.
Configuring with Claude Desktop
Add to your claude_desktop_config.json:
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:
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:
-
Whole-file rewrites in long configs. When an LLM regenerates a 600-line
values.yamlto 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. -
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 since v1.11.0: fedit -op map on a .tf, .hcl or .nix file (or with -lang hcl, terraform, tf or nix) lists the top-level blocks (HCL) or attributes (Nix) and flags duplicates, and fedit_map does the same over MCP.
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:
If you are not sure how many matches exist, run find first — it lists every match with context so you can disambiguate before mutating:
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):
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.
License
MIT — see LICENSE
Built by Amalex — makers of Amalex Handler, the universal file transporter.
來源:README.md,提交 f558ac3
工具
0版本歷史
2- v1.12.0最新Nov 3, 2026
- v1.0.0Sep 17, 2026
評分
已滿足適用檢查項的 50%
該評分衡量此收錄條目的完整程度,依據註冊中心條目、端點與公開來源計算得出。它不是對伺服器本身的評價,也不表示伺服器安全或目前可用:可連通性是帶日期的檢查結果,評分也不包含安裝量或使用者評價。
- 近 30 天內可連通25 分
請保持端點能夠回應 MCP 握手;SourceWeft 每 30 天探測一次。
- 已宣告認證方式10 分
請在註冊中心條目中宣告憑證請求標頭,或保持端點能夠回應探測,以便判定其認證方式。
- 已列出工具15 分
請讓伺服器在無憑證時回應 tools/list,或發布可在沙箱中無憑證啟動的軟體包。
- 描述必需15 分
- 圖示10 分
- 文件15 分
- 授權條款10 分