Tools
Capabilities as MCP servers — defined on a machine, declared by a persona, injected at spawn. Secrets live in the keychain.
Agents need capabilities: web search, a database, a deploy API. In fez these are tools — MCP servers — and the design question they answer is sharper than it first looks: how do you give agents (and teammates) capabilities without scattering credentials?
One thing tools deliberately are not: merchandise. MCP servers already have an ecosystem (npm, registries); fez doesn't re-catalog it. Inside fez, tools are dependency metadata for agents — the thing an agent listing declares so your machine knows what to resolve.
Define — on the machine
Each machine keeps a catalog of tool definitions: command, args, and the names of required env vars.
fez tool add web-search --from npm:@brave/brave-search-mcp-server
fez tool add web-search --command npx --args "-y,duckduckgo-mcp-server"
fez tool list
fez tool remove web-searchDefinitions also arrive as the skill part of an
extension package, or via fez tool install
from a relay listing.
The desktop's Tools → Add tools also accepts GitHub repository, directory,
and SKILL.md links. These open a package or skills import review;
they are not saved as hosted MCP server URLs. Installation and assignment to
an agent are separate steps.
Declare — in the persona
A persona lists the tools it wants, by name (mcpServers: [web-search]). A
declaration is an intent, not a grant — it only becomes capability if the
machine the agent spawns on actually defines that tool. The desktop app's
market shows this honestly: every local agent's declared tools as status
chips — ready ✓, needs a secret, or no definition (with one-click install
when a listing exists).
A name is not a package
web-search is a local alias. It says what the prompt calls the capability;
it says nothing about what to install. npm has at least three unrelated packages
answering to that name, and searching brave-search returns Brave's own server
next to a stranger's fork carrying a byte-identical description. So fez never
resolves a bare name on your behalf — doing that would mean running whoever won
the search ranking that day.
Instead a persona can say where the tool comes from, after an =:
mcpServers: [web-search=npm:@brave/brave-search-mcp-server, github]Now the install is deterministic — nothing to look up, nothing to guess — and
the persona is portable: hand it to someone and their fez knows exactly what
to fetch. It is what package.json and Cargo.toml do, for the same reason. The
name left of the = is still what the prompt and the ACP session see, and a
locally defined tool of that name always wins.
Four schemes, each naming a published package or a URL:
| Spec | Runs |
|---|---|
npm:<package> | npx -y <package> |
uvx:<package> | uvx <package> |
pipx:<package> | pipx run <package> |
https://… | a hosted MCP server |
A spec can never name an arbitrary command. Personas travel — if command:
were reachable from frontmatter, installing one would be arbitrary code execution
wearing a YAML key.
And declaring a source does not install it. Declaring is a request; installing is the approval, and the approval still renders the resolved command verbatim first. A headless agent that spawns without the tool says so and carries on, exactly as it does for a name it has never heard of.
When there's only a name
Existing personas, or a name you typed yourself. Then fez refuses to guess and puts you in the loop: the extensions page offers find it…, searches npm, and shows you the candidates with their publishers — the one field that separates an official package from a fork of it. You pick; the resolved command appears for consent; and the source is written back into the persona that declared it, so neither you nor the next machine has to answer the question again.
The one bare name that does resolve is fez-* → @fezchat/*, and only because fez
owns that npm scope. That is a property of owning the namespace, and it
generalizes to no other prefix.
Inject — at spawn
Fez injects declared-and-resolved tools at spawn. A harness can also expose tools from its own configuration: Claude Code shares the user's configuration and login by default. Review harness configuration and isolation before sharing an agent. Its declared tool list is not a sandbox boundary.
When an agent announces its Fez tools, it lists those that resolved, so the orchestrator can route using available declared capabilities.
Secrets
Secret env values (API keys, tokens) live in the OS keychain — the same custody as your identity key — never in config files. The catalog stores only the names. Two properties matter:
- The desktop app's secret fields are write-only: the GUI can store a value and check that one exists, but nothing in the webview can ever read one back.
- Agents resolve values from the keychain at spawn time, in core.
Fill them in the desktop app (settings → tools & secrets) or with security
directly.
Share the agent, not the key
Suppose you have a Vercel API key and your teammates need deploys. You don't
hand out the key — you run a deploy agent on your machine with the tool defined
locally, and set its respondTo to the people allowed to use it. Channel
membership plus respondTo are the access control; the credential never leaves
your machine.
Publishing & installing
Tool definitions travel as signed relay listings:
fez tool publish web-search # signed listing (env NAMES only, never values)
fez tool market # browse
fez tool install web-search # add the definition; secrets remain yours to fillInstall receipts are signed events, and counts tally distinct signing keys — one key replaying an install inflates nothing.