Files
sherlock/docs/agents.md
T
amacocianandCopilot d9ecb908b8
Charlie/project-charlie: Build image / config (push) Successful in 1s
Charlie/project-charlie: Build image / build (push) Skipped
Charlie/project-charlie: Deploy runners / config (push) Successful in 2s
Charlie/project-charlie: Deploy runners / deploy-morgott (push) Skipped
Charlie/project-charlie: Deploy runners / deploy-melina (push) Skipped
Charlie/project-charlie: Deploy stack / config (push) Successful in 1s
Charlie/project-charlie: Deploy stack / deploy (push) Skipped
Release / release (push) Successful in 21s
agent: support the codex, opencode and goose CLIs
Backends grow from "flag + env scrub" to four knobs — render, mcpArgs,
mcpEnv, forbidEnv — so a CLI can receive sherlock's MCP servers however
it accepts them, without ever editing the operator's own config:

  - codex:    repeated `-c mcp_servers.<name>....` TOML overrides, which
              layer over ~/.codex/config.toml. $CODEX_HOME is avoided on
              purpose: it also holds auth.json.
  - opencode: $OPENCODE_CONFIG pointing at a rendered file in opencode's
              own schema; it is one layer of that CLI's config merge.
  - goose:    repeated `--with-extension <name>:<env> <command line>`.
              Those belong to a subcommand, so MCP flags are appended
              after the operator's arguments and a bare `sherlock goose`
              defaults to `goose session`.

Verified against the real CLIs (codex 0.153.2, opencode 1.18.27, goose
1.49.0): codex registers both servers with args and env intact, opencode
and goose actually spawn them, and SHERLOCK_LOCAL_CONFIG reaches the MCP
process in every case.

Gemini and Cursor CLIs are deliberately left out: they read MCP servers
only from config files they own, so wrapping them would mean writing
into ~/.gemini, ~/.cursor or the checked-out repository.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: fdf86527-ca08-420d-89ee-47bcee60bebe
2026-09-04 11:18:56 +02:00

4.2 KiB

Agents

Sherlock dispatches built-in agent profiles from internal/agent/.

Agent CLI How MCPs are injected
copilot GitHub Copilot CLI --additional-mcp-config @<path>
claude Claude Code CLI --mcp-config <path>
codex OpenAI Codex CLI repeated -c mcp_servers.<name>....
opencode opencode CLI $OPENCODE_CONFIG=<path>
goose Block Goose CLI repeated --with-extension <name>:<cmd>

sherlock <agent> [args...] and sherlock run <agent> [args...] are equivalent. Unknown agent names exit with usage errors.

Every mechanism above is per-invocation and additive: sherlock never edits an operator's persistent CLI config, and the CLI's own servers keep working alongside sherlock's. That rules out CLIs which only read MCP servers from a config file they own (Gemini CLI, Cursor CLI), since supporting those would mean writing into ~/.gemini, ~/.cursor or the checked-out repository.

Spawn behavior

On spawn, sherlock resolves installed MCP binaries (gitea-mcp, grafana-mcp, gssh-mcp, searxng-mcp), skips missing and disabled ones, hands the rest to the agent by whichever mechanism that CLI supports, and execs it. For the file-based CLIs the file is written 0600 under $XDG_RUNTIME_DIR/sherlock/ (<agent>.mcp.json, or <agent>.opencode.json for opencode). Each MCP entry carries SHERLOCK_LOCAL_CONFIG when a project-local config is in effect, so the MCP resolves the same config sherlock did — goose takes that inline in the --with-extension value, which is [name:]ENV=val command args....

The child mostly inherits the parent environment. The Claude profile strips ANTHROPIC_API_KEY so a personal key does not override sherlock-managed behavior.

Argument order follows each CLI: codex's -c overrides are global, so they precede the operator's arguments, while goose's --with-extension belongs to a subcommand and therefore follows them (a bare sherlock goose defaults to goose session).

Changing agents

Adding a built-in agent is a code change under internal/agent/. Implement the small agent interface, register it in init, and use internal/mcp for config rendering. The exact API lives in internal/agent/agent.go; shared spawn helpers live in internal/agent/exec.go, and the per-CLI MCP/-env conventions ("backends") live in internal/agent/backend.go.

A backend has four knobs: render (write the CLI's config file, if it reads one), mcpArgs (flags, whether pointing at that file or carrying the servers inline), mcpEnv (env vars, such as a config path a CLI only takes that way), and forbidEnv (env to scrub).

Custom agents (config)

Operators can register their own agents from config.toml without touching code — handy for wrapper commands such as a copilot-local script that points the Copilot CLI at a self-hosted model:

[agents.copilot-local]
command = "copilot-local"   # binary to exec; defaults to the name if omitted
backend = "copilot"         # which CLI's MCP/-env conventions to use

backend must be one of the built-in backends (claude, codex, copilot, goose, opencode); it tells sherlock how to hand that CLI its MCP servers and what env to scrub. Any rendered file is named after the agent (<name>.mcp.json).

sherlock copilot-local [args...] then dispatches exactly like a built-in. A custom agent whose name collides with a built-in agent, or that names an unknown backend, is a hard startup error. (Names matching a reserved subcommand — status, logout, run, update, version — are shadowed by that subcommand; reach them via sherlock run <name>.)

Agent config is read from the global config plus any project-local .sherlock.toml (see configuration), so a repository can add its own agents. An empty section removes an agent from dispatch, including a built-in one:

[agents.claude]   # not offered in this project

Disabled services

Services switched off with an empty [services.<name>] section are left out of the rendered MCP config, so the agent never sees that MCP.