Set up your agent client
How to connect twelve agent clients to Semlith over MCP, the commands and files semlith setup uses, and how to check the connection works.
How to connect each supported agent client to semlith, and how to check the
connection works. semlith setup does most of this for you; the stanzas below
are what it runs or writes, and what to paste by hand when you need to.
Setting it up in your client
You do not paste anything by hand for a client that has a registration CLI.
semlith setup runs that CLI itself, at the scope that means every project, and
always in the form where the client launches semlith mcp as a subprocess. Six
of the twelve clients below register that way. The rest are a file you edit
once, and semlith setup --register-all writes the user-level one for you,
including for an installed client whose CLI is not on PATH.
Every client below runs on macOS, Linux and Windows. ~ is your home directory
(%USERPROFILE% on Windows); where a client keeps its file elsewhere on one
system, the stanza is marked with that system. On Windows a CLI installed by npm
is a .cmd shim (gemini.cmd, copilot.cmd, and VS Code’s code.cmd), and
semlith setup runs the shim.
A subprocess registration carries no credential: semlith mcp reads the agent
key from ~/.semlith/agent.key itself when it needs one, so there is nothing in
it to rotate or leak. semlith setup writes no shell startup block exporting
SEMLITH_AGENT_KEY.
The HTTP stanzas are for a daemon on another machine or a client that speaks
only HTTP. They name ${SEMLITH_AGENT_KEY} rather than the key, so the client
expands it from the environment and the credential stays out of every
configuration file; export it yourself from semlith key show. The endpoint
answers a request without that header with 401, so a stanza missing it fails to
connect rather than finding nothing.
No stanza names a store. Either form opens every store in
~/.semlith/registry.json (every store semlith index has made) plus a
.semlith beside the directory the agent started in, if there is one. Index
another repository and an agent already configured can search it.
--store still wins when given: repeat it to open a chosen set of stores, or set
SEMLITH_STORE to a path-separator-delimited list. A store semlith did not
create is not opened until you trust it once, because a .semlith directory can
arrive inside a repository you cloned: semlith trust ./.semlith keeps it where
it is, and semlith adopt ./.semlith moves it into the home so these stanzas
reach it with no flags.
Every stanza writes ${SEMLITH_BIN} where the binary goes. It stands for the
absolute path of the semlith that is running: semlith setup substitutes it, and
by hand it is whatever which semlith prints (~/.cargo/bin/semlith after a
cargo install). The bare word semlith is not enough. It is on your PATH in
a shell, but usually not in an editor launched from a desktop icon, a launchd or
systemd agent, or a desktop app, none of which sources a profile; the client
then reports that the server exited and says nothing about why.
Every client that reads the MCP initialize reply’s instructions gets the
routing sentence semlith sends there, which names the folders semlith indexes.
--register-all also writes the rule block for clients with a rules file.
A .semlithignore file in gitignore syntax, anywhere in an indexed tree, keeps
its paths out of the store the way .gitignore does, for what you commit but do
not want searched, such as a fixture copy of the repository. The walk, the
watcher and the daemon’s catch-up all honour it, and the portal’s Files ▸ Not
indexed tab counts what it left out.
Terminal
Claude Code
claude mcp add, or a committed .mcp.json in the project root.OpenAI Codex
~/.codex/config.toml, shared by the CLI, the IDE extension and the desktop app.OpenCode
opencode.json in the project root, or the same file under ~/.config/opencode/.IO CLI
io.local.toml in the project root.GitHub Copilot CLI
~/.copilot/mcp-config.json, or /mcp add in a session.Gemini CLI
~/.gemini/settings.json.Editors
GitHub Copilot in VS Code
.vscode/mcp.json for a workspace, or the profile copy that MCP: Open User Configuration opens.Cursor
~/.cursor/mcp.json everywhere, or .cursor/mcp.json in one repo.Zed
~/.config/zed/settings.json on macOS and Linux ($XDG_CONFIG_HOME/zed when that is set, and ~/.var/app/dev.zed.Zed/config/zed for the Flatpak), and %APPDATA%\Zed\settings.json on Windows; the zed: open settings file command opens whichever it is.Cline
~/.cline/data/settings/cline_mcp_settings.json, the one file both the VS Code extension (4.x) and the CLI (3.x) read, and the one the MCP Servers panel’s Configure button opens.Desktop apps
Claude Desktop
~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows (the Microsoft Store build reads its own copy under %LOCALAPPDATA%\Packages\Claude_pzs8sxrjxfjjc\LocalCache\Roaming\Claude, so semlith writes both where each exists), and ~/.config/Claude/claude_desktop_config.json on Linux, where Anthropic’s build is a beta for Debian and Ubuntu.ChatGPT desktop (the Codex app)
Connecting over HTTP
semlith start also answers MCP at http://127.0.0.1:7365/mcp, so a client that
speaks HTTP needs no subprocess and no path. The endpoint is authenticated by the
agent key in ~/.semlith/agent.key: 32 bytes from the OS random source, written
sml_ and hex at mode 0600, created on first start and never reminted unless you
ask. It survives daemon restarts, upgrades and rotation of the portal’s session
token, so a stanza carrying it keeps working. semlith key show prints the live
key and the stanza around it.
claude mcp add --scope user --transport http semlith http://127.0.0.1:7365/mcp --header "Authorization: Bearer ${SEMLITH_AGENT_KEY}"{
"mcpServers": {
"semlith": {
"url": "http://127.0.0.1:7365/mcp",
"headers": { "Authorization": "Bearer ${SEMLITH_AGENT_KEY}" }
}
}
}semlith key rotate, or the portal’s Rotate key button, mints a new key and
carries it forward: it rewrites every client configuration file on this machine
that held the old key — each client’s documented path above, plus the
project-scoped files beside wherever the daemon was started. A file is touched
only when the exact old key appears in it, nothing is created, and both the
command and the page list what changed. The previous key keeps working for
fifteen minutes, and never past the running daemon’s exit, so an open session
finishes; --now drops it immediately. A client registered through its own CLI
is unaffected, because those are subprocess registrations holding no key. A
client configured anywhere else needs the new stanza pasted in, and is named so
you know.
The stdio stanzas stay as they are: semlith mcp forwards to a running daemon
and opens the stores itself when none is running, so it works whether or not
semlith start is up. Close the HTTP endpoint with semlith start --no-mcp-http, or from the portal’s Agents page while the daemon runs; that
drops the route, not the daemon.
Knowing it is there
A registration that reads correctly is not a server that answers. semlith doctor runs the steps rather than reading the file: what a client would run,
whether that launches with the environment a service manager gives a job, how
many tools it lists, and, for Claude Code from a directory that is nobody’s
project, what the client itself says. It names the first step that failed and
the command that shows it.
It also catches a server registered at user scope but switched off for one
directory. ~/.claude.json can carry
"projects": {
"/path/to/your/repo": { "disabledMcpjsonServers": ["semlith"] }
}and every check run from any other directory says the server is connected,
because it is. semlith doctor run in that directory names the file, the key
and the directory, and prints the command that turns it back on. It never turns
it back on itself: switching a server off is a choice.
semlith doctor --brief is one line and an exit code, non-zero when an agent
opening a session in this directory would not find semlith. It only reads, so
it is safe in a hook that runs on every session. For Claude Code, in
~/.claude/settings.json:
{
"hooks": {
"SessionStart": [
{ "hooks": [{ "type": "command", "command": "semlith doctor --brief" }] }
]
}
}semlith does not write that hook for you: a hook runs on every session, and adding one unasked is a choice you should make.
Model, tokens and cost in the ledger
MCP tells semlith which tool a client called and with what, but nothing about
the model behind the call. With Usage from client logs on (the Privacy page,
or semlith ledger --usage on; off by default), semlith reads each client’s own
session log for the model request that issued each call, and keeps that
request’s model, input, output, cache-read and cache-write tokens, and the cost
where the client records one. Logs are opened read-only and only those numbers
are kept. Paths below are macOS; Linux and Windows follow each client’s own home
or application-data folder.
| Client | Read from | Per request | Cost |
|---|---|---|---|
| Claude Code | ~/.claude/projects/**/*.jsonl (CLAUDE_CONFIG_DIR) | yes | from the price table |
| OpenAI Codex | ~/.codex/sessions/**/rollout-*.jsonl (CODEX_HOME) | yes | from the price table |
| ChatGPT desktop (the Codex app) | the same rollouts, told apart by originator | yes | from the price table |
| OpenCode | ~/.local/share/opencode/opencode.db | yes | OpenCode’s own |
| Gemini CLI | ~/.gemini/tmp/*/chats/session-*.jsonl | yes | from the price table |
| GitHub Copilot CLI | ~/.copilot/session-state/*/events.jsonl and session-store.db | yes | from the price table, as the API price of the same tokens |
| GitHub Copilot in VS Code | the agent debug log, …/GitHub.copilot-chat/debug-logs/*/main.jsonl | only with chat.agentDebugLog.fileLogging.enabled on; otherwise not recorded | from the price table |
| Cline | ~/.cline/data/sessions/*/*.messages.json | yes | Cline’s own |
| Claude Desktop | agent-mode sessions under ~/Library/Application Support/Claude/local-agent-mode-sessions | agent mode only; chat keeps no usage on the machine and says not recorded | from the price table |
| IO CLI | ~/.io-cli/runs.db | yes | from the price table |
| Zed | — | not recorded: Zed keeps a per-thread total, not the request behind each call | — |
| Cursor | — | not recorded: Cursor stores no token counts on the machine | — |
The price table is a models.dev snapshot built into the binary; semlith prices update refreshes it. A call a subscription paid for, such as Copilot or Claude
on a plan, is shown at the API price of the same tokens.