Shoddy · Fettler
The download, getting it on PATH, registering it with each assistant, and the file that holds facts about this machine. Read this once per machine.
This page covers the tools level, and you do most of it once per machine. First, put the fettle program on PATH. Then register it with each assistant you use. Guardrails are a choice each project makes, so this page does not set them up. The guardrails page does.
Each release has one file per operating system. You download one archive, unpack one file, and need no .NET runtime.
| Platform | Archive |
|---|---|
| Windows | fettle-X.Y.Z-win-x64.zip |
| macOS, Apple silicon | fettle-X.Y.Z-osx-arm64.tar.gz |
| macOS, Intel | fettle-X.Y.Z-osx-x64.tar.gz |
| Linux | fettle-X.Y.Z-linux-x64.tar.gz |
The macOS and Linux archives are tar files, because only a tar file keeps the execute bit. The execute bit is the marker that lets a file run as a program. Each archive also holds NOTICE and LICENSE. The program has PdfPig, a library that reads PDF files, built in. PdfPig’s Apache-2.0 licence asks for its notice to travel with the program. The MIT licence asks for this project’s copyright notice in every copy.
fettle and in nothing else this project ships. The only other program here is burler, which has no PDF part. Neither program loads the other, and they talk only through the messages they pass. The full statement is in THIRD-PARTY-NOTICES.md.PATHfettle. If no folder on PATH holds that name, the server never starts. To the assistant, that looks like having no file tools, not like an error.Where to put it. Use a folder that belongs to your account, so you need no administrator rights and no other account is affected.
$dir = "$env:LOCALAPPDATA\Programs\fettle"
New-Item -ItemType Directory -Force $dir | Out-Null
Expand-Archive fettle-X.Y.Z-win-x64.zip -DestinationPath $dir -ForceIf the zip came from a browser, unblock it. Windows marks a downloaded file as coming from the internet. It may refuse to run that file the first time.
Get-ChildItem $dir -Recurse | Unblock-FilePut it on PATH for this account. Read the user PATH, add the folder, and write it back. Do not use setx. It cuts the value off at 1,024 characters, and it copies the machine’s part of PATH into yours.
$user = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($user -notlike "*$dir*") {
[Environment]::SetEnvironmentVariable('Path', "$user;$dir", 'User')
}Then open a new terminal. A running terminal keeps the settings it started with. That is the most common reason fettle is “not on PATH” a minute after you put it there.
fettle --version
fettle 2.1.0Where to put it. Use ~/.local/bin if it is already on PATH. Otherwise use /usr/local/bin, which needs sudo.
mkdir -p ~/.local/bin
tar -xzf fettle-X.Y.Z-osx-arm64.tar.gz -C ~/.local/bin
fettle --versionIf the shell cannot find it, add the folder and start a new shell:
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrcmacOS refuses to run a downloaded program from an unknown developer. Clear the mark on the one file:
xattr -d com.apple.quarantine ~/.local/bin/fettleIf you would rather not use PATH, Fettler still works. fettle setup then writes the full path of the program you ran, and says so. The PATH form is better, because a registration that names fettle keeps working after you move or update the file.
You do not have to build anything else in this repository first. Fettler uses no other project here.
./scripts/fettler-build.ps1 publish X.Y.Z # X.Y.Z names the archives
./scripts/fettler-install.ps1 # build, then install over the fettle on PATHThe archives go in artifacts/publish/. A macOS or Linux archive made on Windows has no execute bit, so a release always makes those on a macOS or Linux machine.
fettler-install.ps1 replaces the fettle that is already on PATH. It works while that copy is running. A running copy keeps serving the old build until its client restarts it, so add -Stop to end those copies once the new file is in place.
Registering tells an assistant client how to start Fettler. A client is the program you talk to the assistant through, such as Claude Code. One command registers Fettler with one client:
fettle setup tools claude-code --local --dry-run # what would change
fettle setup tools claude-code --local # change itThe first word after setup is the level. setup tools writes the registration and nothing else. It writes no .fettler.json, no deny list and no instructions. The old spelling, fettle setup claude-code, is refused, and the message shows the new one.
| Flag | What it does |
|---|---|
--local | Register in this project only. This is the default. |
--global | Register for every project this account opens. |
--all | Use every client in place of one name. Clients that are not installed are skipped. |
--dry-run | Print the same report and write nothing. Run this first. |
--force | Replace a fettler entry that names a different command. Without it, setup refuses to change that entry. |
--command PATH | Register this program file in place of the name fettle. |
Each client keeps its registration in its own file. Some clients have one file for the whole account and one for each project:
| Client name | Global file | Project file |
|---|---|---|
claude-code (the command line and the VS Code extension, which share one list) | ~/.claude.json | .mcp.json |
claude-desktop | %APPDATA%\Claude\claude_desktop_config.json; on macOS, under ~/Library/Application Support/Claude/ | none. It has no project level |
vscode-copilot | %APPDATA%\Code\User\mcp.json | .vscode/mcp.json |
github-copilot (the coding agent) | none | none. It reads only instructions, which guardrails write |
Claude Code in VS Code is not a fifth client. It uses exactly the same list as the Claude Code command line. Reporting it separately would print every result twice, and the two copies could disagree.
A global registration covers every project this account opens. --all does it for every client on the machine:
fettle setup tools --all --global --dry-run
fettle setup tools --all --globalThis changes nothing else. Each project is at the tools level until it adds its own guardrails.
fettle setup writes the MCP registration for you. This section shows what it wrote, so you can register by hand or read a registration someone else made. The command line uses none of it.fettle serve starts the MCP server. It talks over its own input and output streams. A client starts it. A person does not. If you run it by hand, it waits for input, which looks like it has stopped.
The registration setup writes is this, and nothing more:
{
"mcpServers": {
"fettler": { "command": "fettle", "args": ["serve"] }
}
}VS Code keeps the same entry under servers in place of mcpServers. setup writes the key each client reads.
The registration names no folder. The server looks for a .fettler.json in the folder the client starts it in, and in the folders above. If it finds none, that folder is the tree work. It then looks again on every call, so a file you add later takes effect on the next call. No tool lets the model point the server at a different folder.
fettle VERB | fettle serve | |
|---|---|---|
| Started by | a person or a script | the client, from the registration above |
| Trees from | the folder it runs in, or --root and --config | the folder the client starts it in |
| Answers with | text, or JSON with --json | MCP tool results |
| Verbs | the same code. A verb behaves the same through either. | |
The tools are named mcp__fettler__VERB, such as mcp__fettler__read and mcp__fettler__edit. These names differ from the client’s built-in tools, so guardrails can turn off the built-in tools and leave Fettler’s on.
The assistant is not offered setup, and a test makes sure it never is. setup writes the file that says which program the client starts. A model that could run it could point the client at a different program. A person sets up a machine, in a terminal. doctor and roots are offered, so the assistant can check its own setup.
roots. It lists the levels, the trees, and what may be done in each. If the assistant has no such tool, the server did not start. Most often that means fettle is not on PATH.The machine file holds facts about this computer that every project shares. An example is where the screening models are kept. It is optional, and most machines do not need one.
| System | Location |
|---|---|
| Windows | %APPDATA%\fettler\fettler.json |
| macOS and Linux | ~/.config/fettler/fettler.json, or $XDG_CONFIG_HOME/fettler/fettler.json when that variable is set |
The name is not ~/.fettler.json. Fettler looks for .fettler.json in each folder above the one it starts in. A file with that name in your home folder would make the whole home folder a tree.
Fettler reads the machine file first, then the project’s files. For each section, the project’s version wins. The machine file may hold two sections:
tools holds settings for the tools, such as which folder names to skip.screenings holds screening settings, such as the models folder.{
"screenings": { "models": "C:\\screening-models" }
}A guardrails section in the machine file is refused. Fettler will not load its settings, and every command stops with an error. A tree, a task or an execute grant there would apply to every project, and no project would have reviewed it. Fettler never writes the machine file, in the same way it never writes .fettler.json. You edit it yourself. When it supplied a setting, roots and doctor name it.
fettle doctor checks how Fettler is installed and registered. Run it in a project folder. It prints the version and the PATH check first, then one section for each level, starting with the tools.
fettle doctorfettle 2.1.0 C:\Users\you\AppData\Local\Programs\fettle\fettle.exe
on PATH: C:\Users\you\AppData\Local\Programs\fettle\fettle.exe
tools on, always
claude-code global absent 'fettler' is not registered in this file
C:\Users\you\.claude.json
claude-code repo healthy registered; runs C:\Users\you\...\fettle\fettle.exe
C:\work\myproject\.mcp.json
claude-desktop repo n/a this client has no project level
scratch: C:\Users\you\AppData\Local\Temp\claude\c--work-myproject\1f0c...\scratchpad,
found as this session's folder, named by the environment variable NAME
tool-results: C:\Users\you\.claude\projects\c--work-myproject\1f0c...\tool-results,
found as in the client's folder for this project, in this session's folder; ...
memory: not found for this project. The tree appears once the client makes the folder
guardrails off
screenings offThe tools section shows four kinds of line:
global for the whole account or repo for this project. Each row ends in a verdict.--root PATH. Fettler opens that folder as a tree with the server’s name, so both can use the same files.| Verdict | Meaning |
|---|---|
healthy | Registered, and the command it names starts. |
n/a | Nothing to register at that reach for that client. This is not a fault. |
absent | The client is installed, and Fettler is not registered with it. |
broken | Registered, but wrong. Usually the command does not start. |
A level that is off is one line. Doctor reports findings only for a level that is on. At the tools level the one finding is 2.11: two entries in ~/.claude.json name one project folder with different spellings. Merge them by hand.
| Exit code | Meaning |
|---|---|
| 0 | Healthy, or nothing to check. |
| 1 | Warnings only, such as an absent row or a finding. |
| 2 | Something is broken, such as a registration whose command does not start. |
In the example, the global row is absent because only this project was registered. So doctor exits 1. If a row is broken, run setup tools again with --command naming the program, and --force to replace the old entry. Everything doctor can report lists every finding.