Agents and MCP tools
A coding agent on your machine (Claude Code, Codex, Cursor or any other MCP client) works in your Ciresk dev session through MCP tools that ciresk dev serves locally. The agent starts the session, runs commands in it, waits for its edits to be served, reads errors and logs, drives the session's browser and asks you when it is blocked. This page covers the MCP servers, how to register them, every tool, the conventions agents rely on, the hooks and skill that do much of this without a tool call, and what an agent may not do.
The MCP servers
ciresk dev serves three MCP servers over stdio. Each one runs on your machine, uses your ciresk dev sign-in and acts on the session of the worktree it runs in.
| Command | Server name in initialize | What it gives the agent |
|---|---|---|
ciresk dev mcp | ciresk-dev | The dev_* tools listed below. |
ciresk dev mcp browser | proxied | The session's browser: the Playwright MCP tools of the image your template pins, plus ciresk_login and ciresk_evidence. |
ciresk dev mcp next [--service <name>] | proxied | Next.js's own MCP tools (errors, routes, logs and the rest) of one service, web by default. |
ciresk dev mcp starts without a session and offers dev_session_up to create one. ciresk dev mcp browser and ciresk dev mcp next attach to the worktree's running session when they start, so start the session first (ciresk dev up, or dev_session_up from the main server) and reconnect those two servers in your client if they failed to start.
The global flags apply, so ciresk dev mcp --profile dev uses the keychain profile dev, and so does setting CIRESK_DEV_PROFILE=dev in the server's environment.
There is no hosted MCP endpoint for Ciresk dev: the tools exist only in the local ciresk dev mcp processes.
Register the servers
ciresk dev setup --agents registers the servers, and installs the hooks and the skill for Claude Code and Codex (see Hooks and skill):
ciresk dev setup --agents # for you, in every repository
ciresk dev setup --agents --repo # for this repository, for everyone who clones it
ciresk dev setup --agents --remove # take it out again (add --repo for the repository)Run it again at any time: it replaces what an earlier run wrote and leaves everything else in the files alone. A file that is not valid JSON is refused with setup_failed, never overwritten. With --json, data.mcp carries the same registration as data (servers, claude_code, codex_toml, cursor_json).
For you
Without --repo the command writes no MCP configuration, since each client keeps its own. It prints, for each client, how to register ciresk-dev and ciresk-dev-browser:
mcp: register the servers in each client you use
Claude Code: claude mcp add --scope user ciresk-dev -- ciresk dev mcp && claude mcp add --scope user ciresk-dev-browser -- ciresk dev mcp browser
Codex, in ~/.codex/config.toml:
[mcp_servers.ciresk-dev]
command = "ciresk"
args = ["dev","mcp"]
[mcp_servers.ciresk-dev-browser]
command = "ciresk"
args = ["dev","mcp","browser"]
Cursor, in ~/.cursor/mcp.json: {"mcpServers":{"ciresk-dev":{"command":"ciresk","args":["dev","mcp"]},"ciresk-dev-browser":{"command":"ciresk","args":["dev","mcp","browser"]}}}Run the Claude Code line, add the Codex tables to ~/.codex/config.toml and merge the Cursor entries into ~/.cursor/mcp.json. --remove prints claude mcp remove --scope user ciresk-dev && claude mcp remove --scope user ciresk-dev-browser and names the Codex tables and Cursor entries to delete.
For a repository
With --repo the command writes the servers into the repository's .mcp.json, which Claude Code reads: ciresk-dev, ciresk-dev-browser and, when a service in .ciresk/dev.yaml has a next_mcp diagnostics source, ciresk-dev-next for the first such service. For a dev file whose web service has one:
{
"mcpServers": {
"ciresk-dev": {
"command": "ciresk",
"args": ["dev", "mcp"]
},
"ciresk-dev-browser": {
"command": "ciresk",
"args": ["dev", "mcp", "browser"]
},
"ciresk-dev-next": {
"command": "ciresk",
"args": ["dev", "mcp", "next", "--service", "web"]
}
}
}It does not write Codex's or Cursor's project files. It prints the same servers as [mcp_servers.<name>] tables for .codex/config.toml and as a {"mcpServers": ...} object for .cursor/mcp.json, in the form shown above. --repo --remove takes all three servers out of .mcp.json.
Start your client in the worktree and approve the servers when it asks. The client starts each server in the project directory, which is how the tools know which session to act on.
Which session a tool acts on
Every session tool acts on the session of one worktree: the one that holds the tool's cwd input, else the server's working directory. A worktree has at most one session, so two agents in the same worktree share it, and agents in different worktrees each get their own. dev_session_list and dev_schedule_show with all are the exceptions: they list across worktrees.
dev_session_exec's cwd is also the directory the command runs in, inside the worktree.
The server names itself to the session as mcp:<pid>:<client>, where <client> is the name your MCP client gives in initialize. You see that label among the session's clients in ciresk dev status.
Conventions
No call waits more than 55 seconds. A tool that can take longer takes wait_s (at most 55) and, when that runs out, answers with the operation's state and poll_after_ms. Call the tool named in next again after that delay: dev_session_up again, dev_session_exec_wait with the exec_id, or the same tool with the same idempotency_key. An operation keeps running while the agent waits; dev_session_exec runs until its own timeout_s.
Mutating tools take an idempotency_key (at most 128 characters). A retry with the same key returns the first answer instead of acting again, and while the operation is still running it re-attaches to it. Without a key, the server generates one and returns it in the result and in _meta.ciresk/idempotency_key. The mutating tools are dev_session_up, _down, _stop, _start, _restart, _exec, _reset, _login, _checkpoint, _push, _needs_you, _needs_dismiss, _ci_record, dev_dotfiles_set and _rm, and dev_schedule_set and _clear. The server keeps these answers for as long as it runs. Ciresk's API also keeps the success of a request with an Idempotency-Key for 24 hours per caller: the same key with the same request replays it, and the same key with another request body is refused with idempotency_key_reused.
Rate limits are retried for you. When Ciresk answers 429 rate_limited, the server sends a read, or a call that carries an idempotency key, again up to two times, but only when the wait the answer names is at most 5 seconds (1 second when it names none). A longer wait comes back as the rate_limited error with the wait in it. See rate-limits.md.
Every result names the next step. A result carries the answer as structuredContent and as text, and _meta.ciresk/next names the next tool call or command. An error has isError: true, the problem (with its code) as structuredContent, and the text ends with next: ....
Annotations. Each tool says whether it only reads (readOnlyHint), may stop or replace what the session holds (destructiveHint), or can be retried with no further effect (idempotentHint). Your client may ask you before a destructive call.
Generations. Your edits stay in your worktree and reach the session within a second; each synced batch is a numbered generation. After an edit, dev_session_wait with the generation answers once every running service serves it, or with the diagnostics that generation produced, so the agent never tests stale code. dev_session_status shows the generation each service serves. A remote session has no local tree, so after dev_session_up with remote: true the next step is dev_session_exec, not a wait.
Tools
The tools of ciresk dev mcp. "People, agents" means any credential may call the tool; see What an agent may not do for the rest. Every session tool also needs a role on the session: the owner, or a member with use to act and with view to read.
Session lifecycle
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_session_up | Starts this worktree's session, or returns the running one, and waits until it is ready. Returns the session with its URLs (http://<label>.localhost:<port>), ports and in_sync. | template, preset, parameters, recreate, remote, lazy, idle_secs (300 to 28800), wait_s (default 45) | dev:sessions.write |
dev_session_status | The session's state, services with the generation each serves, health with the next step for every unhealthy state, diagnostic counts, clients, in_sync. | wait_s (default 0: answer at once) | dev:sessions.read |
dev_session_list | Every live session you own. With scope: org, every live session of the organization (needs dev:audit.read on a person's or a service's credential). started_by keeps the sessions an agent or a person started. | scope (mine or org, default mine), started_by | dev:sessions.read |
dev_session_stop | Stops a remote session and keeps it: its files are checkpointed, then its compute released. Returns paused, last_checkpoint_at and volumes_not_kept: the checkpoint holds the workspace only, so seeded data volumes start from their seed again on dev_session_start. | none | dev:sessions.write |
dev_session_start | Starts a stopped remote session again from its checkpoint. Answers like dev_session_up. | template, wait_s | dev:sessions.write |
dev_session_down | Ends the session and deletes its volumes. | none | dev:sessions.write |
dev_schedule_show | The autostart schedule of this worktree's session, of a template name, or with all every schedule of your person's, each with next_start_at and what its last run did. An empty list: the session starts only when someone starts it. | template, all | dev:sessions.read |
dev_schedule_set | Has this worktree's stopped remote session (or, with template, every stopped session of your person's with that template name) ready at a local time on chosen days. It starts from its checkpoint 5 minutes before at, counted against the budget like dev_session_start; a session that is not stopped then is left as it is. Setting it again replaces it. A local session is refused with not_cloud_writer. | at (required, HH:MM), time_zone (required, IANA), days (default mon to fri), template | dev:sessions.write |
dev_schedule_clear | Removes the schedule; the sessions stay as they are. Succeeds when none was set. | template | dev:sessions.write |
dev_templates_list | The organization's published template versions, newest first, with the one active for each repository. Read it after template_not_published to tell the person what is promoted. | template | dev:templates.read |
Run and observe
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_session_wait | Waits until every running service serves generation gen, or reports a diagnostic for it. Answered by the local daemon. With page, the session's browser opens that path first, so Next.js's runtime errors for the page are in the answer. Returns serving, diagnostics, sync_ms and build_ms. | gen (required), page, wait_s (default 45) | answered locally |
dev_session_exec | Runs one command in a container of the session (the on-demand shell by default) on a tree that holds every local edit made before the call. Returns exit_code, signal, timed_out, duration_ms, generation, the head and tail of stdout and stderr, output_file (the whole output on your machine), failing_tests and warnings. A non-zero exit is an error with the same fields. | argv (required, never a shell string), cwd, container, timeout_s (1 to 3600, default 600), wait_s | dev:sessions.attach |
dev_session_exec_wait | The rest of an exec that dev_session_exec left running: its exit, or its state again. | exec_id (required), wait_s | answered locally |
dev_session_logs | A service's last log lines, at most 200, escaped. | service (required), tail (default 200) | dev:sessions.attach |
dev_session_restart | Restarts one service and answers once it is back. | service (required) | dev:sessions.attach |
dev_session_diagnostics | Compile and type errors as data, each with its next step. An empty answer says why (no diagnostics source, or one that is not running). | service | dev:sessions.read |
dev_session_watch | What happens in the session, read-only: execs, applies with the paths they changed, restarts, resets, switches, logins. Long-polls for events after since and returns events and next_since. | since, wait_s (at most 25) | dev:sessions.read |
dev_session_reset | Puts the session's data volumes back to the template's seed in about a second; rebuild runs the seed jobs again. | volumes, rebuild, wait_s | dev:sessions.attach |
dev_session_egress | The session's egress decisions, oldest first. A denied row's next is the policy change that would let it out: ask your person or an admin, since a denial never clears on retry. | decision (allowed or denied), since, until, cursor, limit (default 50, at most 200) | dev:sessions.read |
Browser and evidence
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_session_login | Logs in a test identity the template declares and returns its cookies for <label>.localhost, with expires_at. Works for identities whose login is a route or an http call; a template that declares a script login is refused with login_script_later. | identity (required) | dev:sessions.attach |
ciresk_login (browser server) | Logs the agent's browser context in as a test identity. The one-time login URL is minted and opened inside the session and never reaches your machine. Returns the path the app redirected to and when the login expires. | identity (required) | dev:sessions.attach |
ciresk_evidence (browser server) | Evidence of the agent's browser context for a pull request: a screenshot, console errors and warnings, failed requests and the session's tree root, redacted in the session. Needs your organization's download policy. | full_page, since_ms | dev:sessions.attach |
| Playwright tools (browser server) | browser_navigate, browser_click, browser_type and the rest of the pinned Playwright MCP server's tools. | as Playwright defines them | dev:sessions.attach |
Each ciresk dev mcp browser process has its own browser context, so two agents never share tabs or cookies. The browser starts on first use, in 2 to 4 seconds. ciresk_login and ciresk_evidence need a page open first (browser_navigate). When your organization's policy denies downloads, only the tools that act on a page run (navigate, click, type, press a key, hover, drag, select, fill a form, handle a dialog, resize, wait, mouse moves and clicks, tabs and close), their results are withheld, and every other tool, ciresk_evidence included, is refused with policy_download_denied.
Search
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_code_search | Searches the session's copy of the code, hidden paths never included. Returns hits with path, line, column and preview, total and truncated. | query (required), regex, case_sensitive, paths, max_results (default 50, at most 500) | dev:sessions.read |
Needs
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_session_needs_you | Tells your person you are blocked on them. Returns the need with its number. | kind (required: question, approval or failing_check), text (required, at most 280 characters), link | dev:sessions.attach |
dev_session_needs | The session's open needs; with number, one need in any state, with the person's reply once answered. With number and wait_s, waits for the answer. | number, all, wait_s (default 0, at most 55) | dev:sessions.read |
dev_session_needs_dismiss | Withdraws a need that went away. | number (required) | dev:sessions.attach |
CI records and git
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_session_ci_record | Attaches a finished Ciresk CI job to the session as evidence. Ciresk reads the job from Ciresk CI and keeps its verdict only when it ran on a tree this session applied (else tree_not_in_session); a running job is ci_job_running. | job_id (required) | dev:sessions.attach |
dev_session_ci_records | The session's verified CI records, newest first, with current when a record ran on the session's current tree. | none | dev:sessions.read |
dev_session_checkpoint | A remote session's workspace as a tree root now. A local session refuses it with not_cloud_writer. | none | dev:sessions.attach |
dev_session_push | A remote session's workspace as one commit on refs/heads/ciresk/<session>, fast-forward only; with pr, also its pull request into base. push_credential_missing: only a person stores the push credential. non_fast_forward: someone else pushed to the branch. change_requests_unsupported: the forge opens no pull requests, push without pr. push_unsafe_workflows: a workflow would run on the branch, ask your person. | base, pr, title, wait_s | dev:sessions.attach |
To record a CI run: after the edit is synced, run ciresk ci check --json (or test, build, lint) in the worktree, then call dev_session_ci_record with the data.id it printed. See git.md for push and its errors.
Usage, secrets, dotfiles and the connection log
| Tool | What it does | Main inputs | Scope |
|---|---|---|---|
dev_usage | What your sessions used today and the organization this month, with limits, remaining units and reset times. Read it before long work. | none | dev:policy.read |
dev_secrets_list | The organization's session secrets, never a value, with the CIRESK_SECRET_<NAME> placeholder to use. Only a person sets a secret. | none | dev:secrets.read |
dev_dotfiles_show | Your person's dotfiles repository, whether a token for a private repository is stored (credential), and whether a new session of theirs gets it. A session an agent's own credential starts skips a repository with a stored token (reason agent_session). | none | dev:sessions.read |
dev_dotfiles_set | Sets your person's dotfiles repository, an https:// URL. Sessions they start afterwards get it. Another host drops a stored token. The tool never takes a token: for a private repository your person runs ciresk dev dotfiles set <url> --token-stdin. | repo (required), install | dev:sessions.write |
dev_dotfiles_rm | Removes it. | none | dev:sessions.write |
dev_connections_list | The organization's connection log. Refused for an agent's own credential (see below). | session_id, principal, since, until, cursor, limit | dev:audit.read |
Needs you
When an agent is blocked on you, it raises a need instead of stopping silently or guessing:
question: something only you can answer.approval: a yes before an action you would want to decide, such as a destructive migration, a force push or spending money.failing_check: a check it cannot fix.
A need is one line of at most 280 characters with an optional link (exec:<id>, a CI job URL). It is not for progress or for saying the work is done. A session takes at most 10 needs an hour (rate_limited past that), and the same kind and text while that need is open returns the open need instead of raising it twice.
You see open needs at the top of ciresk dev status, in ciresk dev watch and in the "Needs you" panel of the session's page in the console, named by the agent and the person it works for (for example "Claude Code" and your name). Where your installation sends notifications, Ciresk also emails you, once per session while you have open needs there, unless you turned need mail off. Answer or close a need:
ciresk dev needs # the open needs
ciresk dev needs reply 3 "yes, drop it" # answer need 3
ciresk dev needs dismiss 3 # close it unansweredOnly a person answers a need: the one it is addressed to (the agent's owner) or the session's owner. The agent reads the answer with dev_session_needs with number and wait_s: 55, calling again while the need is still open, and withdraws a need that went away with dev_session_needs_dismiss.
Ciresk raises two needs itself: expiring, before the session reaches its maximum lifetime, and budget, when the budget will stop it. Neither counts towards the limit. An agent that sees one should finish or checkpoint its work.
Hooks and skill
ciresk dev setup --agents installs, for Claude Code and Codex:
- Hooks in
.claude/settings.jsonand.codex/hooks.json, each runningciresk dev hook <event>. - The
ciresk-devskill in.claude/skills/ciresk-dev/SKILL.mdand.agents/skills/ciresk-dev/SKILL.md: when to use the session, the commands and the rules (never start dev servers locally, check the generation before claiming a fix works, raise a need when blocked).
Without --repo these go under your home directory; with it, into the repository. Codex asks once to trust new hooks: open /hooks in Codex and accept them.
What the hooks do, in a worktree that has .ciresk/dev.yaml:
| Event | What happens |
|---|---|
| Session start | Creates or returns the worktree's session without waiting for it and tells the agent its URLs, generation and health. The only hook that calls Ciresk; the others ask the local daemon. |
| Before a shell command | A build, test or run line is rewritten to run in the session through ciresk dev exec. Commands that need your machine stay local: git, gh, cloud CLIs, ciresk, editors, file tools, formatters, package adds, and every line that writes a file, runs in the background or that the hook cannot read for sure. A line also stays local while the session has not applied your edits within 10 seconds. |
| After an edit | Waits up to 1.5 seconds for the session to serve the edit's generation, then tells the agent its errors when they changed, or that they are gone. |
| Before a turn ends | When the agent edited, holds the end of the turn once while the session has not applied the edits or the latest generation has errors. A second stop in a row always passes. |
A hook never blocks the agent: outside a worktree with a dev file, without a session, signed out or on any failure it stays silent and exits 0, with the reason on stderr.
Settings are read from .ciresk/dev-agents.yaml in the repository, then from agents.yaml in your ciresk-dev config directory (~/.config/ciresk-dev on Linux, ~/Library/Application Support/ciresk-dev on macOS, %APPDATA%\ciresk-dev on Windows). Your file wins; lists add up. A file that does not parse turns the hook off with the error on stderr.
| Key | Default | What it does |
|---|---|---|
up | true | Session start creates or returns the session. |
template | none | The template session start creates when the dev file holds several. |
route | true | Build, test and run commands go to the session. |
approve | allow | ask: you confirm each routed command (Claude Code; Codex then keeps the command local). |
local | empty | Programs (terraform) or a program and subcommand (cargo fmt) kept on your machine. |
remote | empty | Programs always sent to the session; wins over local. |
diagnostics | true | Report errors after an edit. |
wait_ms | 1500 | How long to wait after an edit, at most 10000. |
stop | true | The check before a turn ends. |
The hooks name the agent to Ciresk from CIRESK_AGENT (<tool>[/<version>]), else from the variables Claude Code and Codex set. The name is shown in records next to yours; it never grants anything.
What an agent may not do
The MCP servers offer no tool for the operations that are a person's decision, and the API refuses an agent's own credential for them with principal_not_allowed:
| Operation | Command | Who may do it |
|---|---|---|
| Answer a need | ciresk dev needs reply | People only |
| Share a session | ciresk dev share | People only |
| Kill sessions | ciresk dev kill | People and services |
| Read the connection log | ciresk dev connections | People and services |
| Change the policy | ciresk dev policy set, ciresk dev policy preset | People only |
| Set or remove a secret | ciresk dev secrets set, ciresk dev secrets rm | People only |
| Store a dotfiles token | ciresk dev dotfiles set --token-stdin | People only |
| Publish a template version | ciresk dev templates publish | People and services |
| Promote a template version | ciresk dev templates promote | People only |
| Grant another device a session | ciresk dev attach | People and services |
Organization exports and erasure, the audit log and its retention are also never an agent's (see admin.md).
Which rule applies depends on the credential, not on the program. An agent on your machine that uses your sign-in (the usual setup) acts as you: the API sees you, and the agent is only named next to you in records. The MCP servers still offer none of the operations above, and dev_connections_list works only when you hold dev:audit.read. An agent that runs ciresk dev commands in a shell with your sign-in can do what you can, so keep your client's command approvals on for ciresk dev kill, share, policy and secrets. With an agent's own credential:
- The operations above are refused,
dev_connections_listincluded, anddev_session_listwithscope: orgis refused withnot_allowed. - A session it starts gets only the egress rows of the policy's
egress.agentlist (empty when unset) on top of the other ceilings; see egress.md. - It gets a secret only when the secret allows agent sessions; see secrets.md.
Either way, a session belongs to the accountable person: an agent's owner, or the person who created the API key. It counts against that person's session limits and daily budget and the organization's monthly budget, and dev_usage reports that person's usage. A session the budget cannot pay for is refused with budget_exceeded, or stopped with reason budget. See budgets.md.