Semlith

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

Editors

Desktop apps

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.

ClientRead fromPer requestCost
Claude Code~/.claude/projects/**/*.jsonl (CLAUDE_CONFIG_DIR)yesfrom the price table
OpenAI Codex~/.codex/sessions/**/rollout-*.jsonl (CODEX_HOME)yesfrom the price table
ChatGPT desktop (the Codex app)the same rollouts, told apart by originatoryesfrom the price table
OpenCode~/.local/share/opencode/opencode.dbyesOpenCode’s own
Gemini CLI~/.gemini/tmp/*/chats/session-*.jsonlyesfrom the price table
GitHub Copilot CLI~/.copilot/session-state/*/events.jsonl and session-store.dbyesfrom the price table, as the API price of the same tokens
GitHub Copilot in VS Codethe agent debug log, …/GitHub.copilot-chat/debug-logs/*/main.jsonlonly with chat.agentDebugLog.fileLogging.enabled on; otherwise not recordedfrom the price table
Cline~/.cline/data/sessions/*/*.messages.jsonyesCline’s own
Claude Desktopagent-mode sessions under ~/Library/Application Support/Claude/local-agent-mode-sessionsagent mode only; chat keeps no usage on the machine and says not recordedfrom the price table
IO CLI~/.io-cli/runs.dbyesfrom 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.