MCP server
rt ships a plugin MCP server that gives coding agents (Claude Code, and anything else that speaks MCP) access to rt, the forge and git writes over structured tool calls instead of shell commands.
How it works
rt mcp serve starts a stdio MCP server named "mattstack." Claude Code reaches it through the mattstack plugin, and its tools appear under the namespace mcp__plugin_mattstack_mattstack__*. You never run rt mcp serve yourself; the plugin spawns it automatically. rt mcp tools --json prints the full roster: every tool's name, description and input schema.
The server's environment is fixed when the session starts: its working directory and session id do not follow cd, EnterWorktree or /clear. So every tool that depends on a directory, a tree or a run takes it as an argument (tree, cwd, runDb, repoName).
Why tools instead of Bash
A shell command skips Claude Code's permission check only when it matches an allow rule, and agents wrap commands (cd x && ..., $(...), pipes) in ways prefix rules miss. In auto mode every miss waits on the classifier, which blocks some calls outright (force-pushes, merges and rebases most often). A tool call has nothing to wrap, and the whole server is allowed at once, so a skill's routine rt calls, forge calls and git writes run without a prompt or a classifier wait. The human check stays in the skill's own gates: the ship go-ahead, a wrap-up form, a review disposition.
Setup
Setup's Install step (run by mattstack.app's setup wizard, or rt setup install) installs the mattstack plugin and seeds the MCP server's namespace (mcp__plugin_mattstack_mattstack) into permissions.allow in every Claude Code config dir, so tool calls don't trigger per-call permission prompts. A new tool arrives with rt itself and needs no permission change. There is nothing to configure by hand.
Tools
The server exposes the families below. Most call rt's daemon; the run-tracking writes run in the server process, the git tools run git (and rt sync) directly, and rt_verb, herd_brief and chat_sign_in run rt as a child process.
The full list, with every tool's description and input schema, is the MCP tools reference in the repo.
Gates
| Tool | What it does |
|---|---|
gate_ask | Open a decision gate with daemon-side ceremony (subject resolution, presentation, nudge). Returns the gate id and presentation. |
gate_answer | Answer an open gate's questions as this pane. Values must match option values verbatim; nuance goes in a note field. |
gate_list | List gates (every status, or only open ones), optionally filtered by subject prefix or kind. Supports cursor-based paging. |
Run tracking
Pipeline runs record their stages, fields and decisions through these tools. run_start returns a runDb; pass it to every run-targeted tool (all of them except run_start and run_list, which takes only an optional repo). Those tools also accept an absolute cwd in its place, which resolves to this session's run, else the newest running run whose worktree holds cwd, but passing runDb is the reliable form. A runDb outside rt's runs root is refused.
| Tool | What it does |
|---|---|
run_start | Start a run from the skill's compiled run-start flags and its own directory; returns runDb. |
run_stage | Record a stage transition: start, done, fail or redirect. |
run_field_set | Write one run field. |
run_field_get | Read one run field; errors when it is not set. |
run_decision | Record a decision; selection is a JSON object. |
run_status | Set the run's terminal status: done, failed or abandoned. |
run_snapshot | The run's stages, fields and decisions. |
run_list | Runs the daemon knows, newest first, optionally for one repo. |
MR writes
| Tool | What it does |
|---|---|
mr_reply_thread | Reply to an existing MR discussion thread; returns the reply's note id and the thread's resolved state (GitLab only). |
mr_comment_inline | Post a new positioned inline comment on an MR diff line (GitLab only). |
mr_comment | Post a new top-level note: resolvable by default, or a plain note with resolvable: false (GitLab only). |
mr_resolve_thread | Resolve a discussion thread, or reopen it with resolved: false (GitLab only). |
mr_approve | Approve an MR, or withdraw the approval with approved: false (GitLab only). |
mr_ready | Mark a draft MR ready, or back to draft with ready: false (GitLab only). |
mr_retry | Retry one CI job or a whole pipeline (GitLab only). |
mr_rebase | Request a server-side rebase of the MR's source branch (GitLab only). |
mr_create | Create an MR from a pushed branch, as a draft by default, with optional labels and squash (GitLab only). |
mr_update | Edit an open MR's title, description, addLabels, removeLabels or squash; at least one (GitLab only). |
mr_upload | Upload one local image or video to the project and get back the markdown that embeds it (GitLab only). |
mr_merge | Merge an MR now, optionally squashing and deleting the source branch, or enable auto-merge with whenPipelineSucceeds: true; reports what it observed after the request (GitLab only). |
mr_map | List open MRs joined to the local worktrees holding their branches. |
mr_merge goes through GitLab like a merge from its web page: GitLab still enforces the project's approval and pipeline rules. The skill that calls it holds its own ship gate, which is where you say go.
MR reads
| Tool | What it does |
|---|---|
mr_view | One MR by iid, read from GitLab: any author, opened, merged or closed (GitLab only). |
mr_list | A summary of each matching MR of a project, read from GitLab, filtered by author, source branch, target branch, state or text search; at most limit rows, with truncated when there were more (GitLab only). |
mr_for_branch | The open MR, or null, for each named source branch, by any author (GitLab only). |
mr_threads | The MR's discussion threads, fetched from GitLab on every call (GitLab only). |
mr_pipeline | The MR's head pipeline, and with jobId that job's detail (GitLab only). |
mr_job_trace | Part of one CI job's trace: the tail, the head, a line range, or lines matching text; ANSI escapes stripped, capped at 64 KiB (GitLab only). |
pipeline_list | Pipelines for a branch, a commit or an MR, newest first; works with no MR (GitLab only). |
project_labels | The project's labels, optionally searched by name (GitLab only). |
gitlab_get | One read-only GET against GitLab's REST API for a fact no other tool returns; paths that return credentials, query keys that carry credentials and impersonation are refused, the body is capped at 256 KiB, redirects are not followed, and token values are redacted (GitLab only). |
branch_stack | Whether a tree's branch is in a tracked stack, and its parent. Takes tree, the absolute root of a registered checkout or worktree; reads no forge. |
pipeline_list, project_labels and gitlab_get name their target with repoName, like the mr_* tools. mr_view, mr_list, mr_for_branch, mr_threads, mr_pipeline, mr_job_trace, pipeline_list, project_labels and gitlab_get ask GitLab on each call and none uses the daemon's open-MR cache, so an agent can read any MR its token can see. mr_map is the exception: it reads the daemon's synced MR list. A refusal comes back as GitLab's own status and text. mr_list with targetBranch also returns full, true when nothing was cut, so an empty full list proves the branch has no open MRs targeting it.
Every mr_* tool names its target with repoName (the repo's serialized identity such as remote:gitlab.com%2Fexample%2Fapp, an absolute path to a checkout or worktree, or a repo label that matches exactly one registered repo); the tools that act on one MR also take mrUrl (the MR's URL; its project must be registered with rt, and the URL supplies the iid). A URL is matched to a registered repo by identity first, then by the registered checkouts' origin remotes, so a repo pinned to another identity through rt.repoIdentityOverrides still resolves; a URL matching two checkouts is refused as ambiguous. Given both, they must agree. mr_map takes repo (identity or label).
mr_upload reads only regular files with no other hard links under the target repo's worktrees, your Claude Code temp root (/private/tmp/claude-<uid>/, where session scratchpads live), rt's evidence folder (~/.mattstack/evidence/, for screenshots you want to upload), a pipeline run's own evidence folder (~/.mattstack/work/<run id>/evidence/, for a run that exists on this machine), or a directory listed in the machine-scoped rt.mcp.uploadRoots setting, and only png, jpg, jpeg, gif, webp, mp4, mov or webm files up to 50 MB whose bytes match their extension. To allow another folder on this machine:
rt settings set rt.mcp.uploadRoots '["/Users/you/Screenshots"]' --scope machine
Git
Each git tool takes tree, the absolute path of the root of a checkout or worktree of a repo registered with rt. Anything else (an unregistered repo, a directory inside a tree) is refused before git runs.
| Tool | What it does |
|---|---|
git_push | Push the current branch to its same-named upstream, or as origin/<branch> with setUpstream: true. |
git_pull | Fast-forward the current branch from its upstream (--ff-only). |
git_rebase | Rebase onto a named branch or ref, fetching a remote-tracking ref first; abort: true aborts a rebase in progress. |
branch_sync | Bring the branch current in one call, the rt sync flow: fetch, reset to origin when GitLab rebased it, rebase onto the default branch, and push with a lease when the reset or rebase changed the branch. |
What they refuse:
git_pushpushes exactly one ref (no tags, no submodules) to the same-named branch on its upstream's remote, and forces only with--force-with-lease --force-if-includes. It never pushes a detached HEAD,main,masteror origin's default branch, and refuses outright when origin's default cannot be read. It refuses a branch with no upstream, an upstream with a different branch name, and an upstream that is its own remote's default branch or whose remote's default cannot be read.setUpstream: truepushes toorigin/<branch>instead and makes that the upstream; the refusal of origin's default branch still applies.git_pullnever merges or rebases; a diverged branch is an error.git_rebasetakes only a branch or ref name foronto, never an option. On a conflict it returns the conflicted files and leaves the rebase paused for you to resolve and finish withgit rebase --continue.branch_syncrefuses to reset over a local commit with no equivalent on origin (unpushed work), refuses to force-push over commits only origin has (a branch only behind origin: rungit_pullfirst), and names the commits when it can. It also refuses a rebase already in progress, a detached HEAD,main,masteror the default branch, a branch name that is unsafe or ambiguous with a tag or other ref, a local ref shadowingorigin/<branch>ororigin/<default>, a push that config would redirect to another ref, and a gitq stack member. A branch only ahead of origin is not refused, but it is pushed only when the reset or rebase changed it; usegit_pushto publish new commits.
git commit and git add are not tools; they stay in Bash.
Worktrees
| Tool | What it does |
|---|---|
worktree_provision | Claim a worktree for a ticket or branch (from the on-deck pool, or freshly created) and return its path; enter it with EnterWorktree in path mode. |
worktree_dispose | Dispose a worktree by its tree name; it goes to the restorable trash. |
worktree_stop_holders | End the processes rt ties to a worktree (dev servers, watchers), and only those. |
There is no general process-kill tool.
Herd
Worker side (a pane a herd spawned):
| Tool | What it does |
|---|---|
herd_gates | List a herd's open gates. |
herd_ask | Open a gate asking the herd operator questions, using this worker's identity. |
herd_answer | Read the answer to a gate previously opened with herd_ask. |
herd_report | Post a status report to this worker's herd room. |
herd_milestone | Announce an artifact (a spec, a plan, a PR) to the shepherd and open the milestone gate. |
Shepherd side:
| Tool | What it does |
|---|---|
herd_start | Start a herd (room, workspace, gate subscription) for this session. |
herd_brief | Assemble a job brief from a template plus a strategy body or method file. |
herd_spawn | Spawn a worker pane for a job: provision its worktree and launch Claude with the brief. Takes minutes. |
herd_close | Close one job's pane. |
herd_follow_up | Reopen a done job for a follow-up round. |
herd_attend | Open a job's pane in a tab of this shepherd's workspace. |
herd_wrap_up | Close panes, dispose worktrees, delete job dirs and archive the room in one pass. |
herd_resume | Re-attach this session to a herd, taking it over as shepherd. |
herd_status | One herd: jobs, panes, gates, subscription, unread. |
herd_list | Active herds, or every herd with all: true. |
herd_start refuses in a worker pane. herd_spawn, herd_close, herd_follow_up, herd_attend and herd_wrap_up run only from the session the daemon records as the herd's shepherd (the one that ran herd_start, or took the herd over with herd_resume), and never from a worker pane. The files these tools read or write are confined: herd_brief's out must sit in your Claude Code temp root, and a brief, template, strategies file or method file must be an existing .md file in the temp root or an installed plugin or pack root.
Chat
Every chat tool acts as this session's own chat handle; none takes the acting handle or a session id as input. (chat_sign_in's as only chooses this session's handle, and chat_dm's to names the recipient.)
| Tool | What it does |
|---|---|
chat_sign_in | Sign this session in to rt chat: presence, a handle, and the repo room derived from cwd unless room or noRoom says otherwise. |
chat_sign_out | Sign this session out; room memberships are kept. |
chat_away | Set an away message without signing out. |
chat_back | Clear the away message. |
chat_join | Join a room, with an optional wakeOn (mention, all, none). |
chat_leave | Leave a room. |
chat_rooms | The rooms this handle belongs to, with unread counts. |
chat_who | A room's members and their presence. |
chat_buddies | Every chat handle on this machine and its presence. |
chat_read | Read unread messages (every room, or one), advancing the read cursor; since peeks without advancing, last returns a room's newest messages. |
chat_mark | Mark messages read: every room, one room, or one room up to a message id. |
chat_post | Post a message to a room. |
chat_dm | Send a direct message to another handle. |
chat_ack | Acknowledge a message by id. |
chat_claim | Claim a message so other agents skip answering it. |
chat_release | Release a previously claimed message. |
chat_archive | Archive a room this handle belongs to, or reopen it with reopen: true. |
chat_invite | Invite another herdr pane into a room by typing /chat:join <room> into it, with an optional one-line note. |
Every chat tool except chat_sign_in, chat_away, chat_back, chat_sign_out, chat_who and chat_buddies needs a signed-in session. After /clear, the server still acts as the session from before the clear, so the tools report no signed-in session and chat_sign_in refuses; run rt chat sign-in and the other chat verbs in Bash for the rest of that session.
Identity
| Tool | What it does |
|---|---|
whoami | Report this session's identity as the other tools see it: its Claude Code session id, herdr pane, the chat handle the chat tools act as (null, with a sign-in hint, when this session is not signed in), and the herd id, job and room in a herd worker pane. |
whoami takes no input and reads only the server's environment and this session's chat session file. It makes no daemon call, so it shows what the tools would act as, not live presence.
rt_verb
| Tool | What it does |
|---|---|
rt_verb | Run one agent-safe rt verb and return its --json result. |
rt_verb does not itself connect to the daemon; it runs rt as a child process. The verb it runs may still talk to the daemon internally (worktree list, worktree triage, endpoint lookup and herd status are daemon-backed reads; skills writing-style show is not), so rt_verb is not a way to reach those commands while the daemon is down. A flag the verb does not declare is refused, and --json is added for you. Pass args without the leading rt (e.g. ["worktree", "list"]), and pass cwd (an absolute path) when the verb depends on the current repo.
Only verbs marked agent-safe run; anything else is refused, and the refusal lists the verbs that do. These are:
- Reads:
git status,git log,git branches,worktree list,worktree triage,daemon status,events list,gate list,gate subscriptions,herd gates,herd status,runs show,runs find,endpoint lookup,settings get,settings list,settings explain,pane list,pane peek,repos status,skills writing-style show,setup status,team status. - Routine writes and waits:
worktree await-ready,herd brief,skills compile,skills check,skills sync,skills surface,skills bind.
A run is capped at 30 seconds, except worktree await-ready (11 minutes) and skills compile, skills check and skills sync (10 minutes each). herd brief's file flags (--out, --template, --strategies, --method-file) are confined the same way herd_brief's are. skills compile, skills sync, skills surface and skills bind refuse a cwd through rt_verb (pass --pack instead), and --manifest (plus --pack-dir on skills compile) is refused there too; run those forms from a shell, where the permission prompt applies.
Credentials never reach the transcript
Forge payloads can carry live credentials: avatar URLs with a private_token query, remotes with a token in their user info, tokens printed in a job trace. Every tool result, success or error, passes through one redaction step before it reaches the agent. It replaces credentials in URL user info, credential query parameters (private_token, access_token, job_token, token, api_key and similar), GitLab and GitHub token shapes, and PRIVATE-TOKEN, JOB-TOKEN and Authorization headers with [redacted].
Entering an rt worktree
EnterWorktree into an rt pool tree raises Claude Code's "permission-root relocation" dialog, since rt's trees live outside the repo's .claude/worktrees/, and no allow rule suppresses it. In a herdr pane, rt answers it for you when the target is a worktree in rt's own registry:
- In a pane you are attending, the mattstack plugin's
PreToolUsehook onEnterWorktreetells the daemon which tree the session is about to enter (so does rt's worktree hook when it provisions a tree forEnterWorktreeby name). The daemon then accepts the dialog on that pane only if it appears within a few seconds and names that same registered tree. - Herd workers and other unattended panes rt watches are answered by the daemon's watchdog and reconciler, again only for a registered tree.
- Any other relocation waits for you, and outside herdr (a plain terminal) you always answer it yourself.
ExitWorktree raises no dialog on current Claude Code, so there is nothing to answer on the way out. The machine setting panes.relocationAutoAccept (on by default) turns all of this off; see rt settings set.
What stays on Bash
A few things skills still run in a shell, on purpose:
- Long waits.
rt gate wait <id>andrt events waitblock past any tool timeout, so skills run them as background Bash or underMonitor. Install's allow list covers them with the prefix rulesBash(rt gate *)andBash(rt events wait *). - The shepherd's gate answers.
gate_answeralways answers as the pane, so a shepherd answering a herd gate runsrt gate answer --by shepherdin Bash, under the samert gaterule. - Commits and staging.
git commit,git add, andgit rebase --continueafter a resolved conflict run in Bash; in auto mode the classifier approves routine commits. - Project tooling. A repo's own checks and scripts, and a pack's scripts.
- GitHub. Every
mr_*tool is GitLab only; GitHub flows runghin Bash. - Pane control.
rt pane send,rt pane spawn,rt pane focus,rt pane accountsandrt pane directories(onlypane listandpane peekrun throughrt_verb). - Chat after
/clear. As above,rt chat sign-inand the other chat verbs in Bash.
For pack authors
A pack's skills and fills should call these tools rather than the shell commands they cover. rt mcp tools --json prints the roster to write against. rt skills check lints a pack for shell calls a tool covers (--strict fails on a hit), and rt skills audit is a slower, advisory LLM pass that also catches wrapped calls and values handed between code blocks through shell variables.
When the daemon is down
Every daemon-backed tool connects over the daemon's Unix socket. When the daemon is not running, those tools return an error with a human-readable explanation. The git tools (which run git, or rt sync for branch_sync) and the run-tracking tools other than run_list (which write the run's own store in the server process) do not go through the socket. rt_verb does not connect to the daemon itself, but the verb it runs can still fail if that verb needs the daemon.
See also
- Gates guide for the gate lifecycle
- Chat guide for chat basics
- Plugins guide for rt's plugin system