Shoddy · Fettler

Installing It

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 is the long version. If you only want Fettler working, two commands do it: the quick start. Come back here for the reasons and the flags.

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.

The download

Each release has one file per operating system. You download one archive, unpack one file, and need no .NET runtime.

PlatformArchive
Windowsfettle-X.Y.Z-win-x64.zip
macOS, Apple siliconfettle-X.Y.Z-osx-arm64.tar.gz
macOS, Intelfettle-X.Y.Z-osx-x64.tar.gz
Linuxfettle-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.

PdfPig is in 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.

Putting it on PATH

Every other step assumes this one, and it is the step people skip. Every registration on this page starts a bare fettle. 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.
Windows · PowerShell

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 -Force

If 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-File

Put 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.0
macOS and Linux

Where 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 --version

If the shell cannot find it, add the folder and start a new shell:

echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.zshrc

macOS refuses to run a downloaded program from an unknown developer. Clear the mark on the one file:

xattr -d com.apple.quarantine ~/.local/bin/fettle

If 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.

From source

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 PATH

The 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 each client

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 it

The 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.

FlagWhat it does
--localRegister in this project only. This is the default.
--globalRegister for every project this account opens.
--allUse every client in place of one name. Clients that are not installed are skipped.
--dry-runPrint the same report and write nothing. Run this first.
--forceReplace a fettler entry that names a different command. Without it, setup refuses to change that entry.
--command PATHRegister this program file in place of the name fettle.

The clients, and the files behind them

Each client keeps its registration in its own file. Some clients have one file for the whole account and one for each project:

Client nameGlobal fileProject 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)nonenone. 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.

Registering it for every project

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 --global

This changes nothing else. Each project is at the tools level until it adds its own guardrails.

The MCP front end

MCP, the Model Context Protocol, is the standard way an assistant starts a tool and talks to it. 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 VERBfettle serve
Started bya person or a scriptthe client, from the registration above
Trees fromthe folder it runs in, or --root and --configthe folder the client starts it in
Answers withtext, or JSON with --jsonMCP tool results
Verbsthe 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.

To check the server is running, ask the assistant to call 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

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.

SystemLocation
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:

{
  "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.

Checking it with doctor

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 doctor
fettle 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  off

The tools section shows four kinds of line:

VerdictMeaning
healthyRegistered, and the command it names starts.
n/aNothing to register at that reach for that client. This is not a fault.
absentThe client is installed, and Fettler is not registered with it.
brokenRegistered, 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 codeMeaning
0Healthy, or nothing to check.
1Warnings only, such as an absent row or a finding.
2Something 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.

The machine is ready. The tools page covers each tool. If a project should limit what the assistant can reach, the guardrails page sets that up.