Skip to main content

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​

ToolWhat it does
gate_askOpen a decision gate with daemon-side ceremony (subject resolution, presentation, nudge). Returns the gate id and presentation.
gate_answerAnswer an open gate's questions as this pane. Values must match option values verbatim; nuance goes in a note field.
gate_listList 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.

ToolWhat it does
run_startStart a run from the skill's compiled run-start flags and its own directory; returns runDb.
run_stageRecord a stage transition: start, done, fail or redirect.
run_field_setWrite one run field.
run_field_getRead one run field; errors when it is not set.
run_decisionRecord a decision; selection is a JSON object.
run_statusSet the run's terminal status: done, failed or abandoned.
run_snapshotThe run's stages, fields and decisions.
run_listRuns the daemon knows, newest first, optionally for one repo.

MR writes​

ToolWhat it does
mr_reply_threadReply to an existing MR discussion thread; returns the reply's note id and the thread's resolved state (GitLab only).
mr_comment_inlinePost a new positioned inline comment on an MR diff line (GitLab only).
mr_commentPost a new top-level note: resolvable by default, or a plain note with resolvable: false (GitLab only).
mr_resolve_threadResolve a discussion thread, or reopen it with resolved: false (GitLab only).
mr_approveApprove an MR, or withdraw the approval with approved: false (GitLab only).
mr_readyMark a draft MR ready, or back to draft with ready: false (GitLab only).
mr_retryRetry one CI job or a whole pipeline (GitLab only).
mr_rebaseRequest a server-side rebase of the MR's source branch (GitLab only).
mr_createCreate an MR from a pushed branch, as a draft by default, with optional labels and squash (GitLab only).
mr_updateEdit an open MR's title, description, addLabels, removeLabels or squash; at least one (GitLab only).
mr_uploadUpload one local image or video to the project and get back the markdown that embeds it (GitLab only).
mr_mergeMerge 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_mapList 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​

ToolWhat it does
mr_viewOne MR by iid, read from GitLab: any author, opened, merged or closed (GitLab only).
mr_listA 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_branchThe open MR, or null, for each named source branch, by any author (GitLab only).
mr_threadsThe MR's discussion threads, fetched from GitLab on every call (GitLab only).
mr_pipelineThe MR's head pipeline, and with jobId that job's detail (GitLab only).
mr_job_tracePart 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_listPipelines for a branch, a commit or an MR, newest first; works with no MR (GitLab only).
project_labelsThe project's labels, optionally searched by name (GitLab only).
gitlab_getOne 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_stackWhether 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:

bash
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.

ToolWhat it does
git_pushPush the current branch to its same-named upstream, or as origin/<branch> with setUpstream: true.
git_pullFast-forward the current branch from its upstream (--ff-only).
git_rebaseRebase onto a named branch or ref, fetching a remote-tracking ref first; abort: true aborts a rebase in progress.
branch_syncBring 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_push pushes 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, master or 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: true pushes to origin/<branch> instead and makes that the upstream; the refusal of origin's default branch still applies.
  • git_pull never merges or rebases; a diverged branch is an error.
  • git_rebase takes only a branch or ref name for onto, never an option. On a conflict it returns the conflicted files and leaves the rebase paused for you to resolve and finish with git rebase --continue.
  • branch_sync refuses 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: run git_pull first), and names the commits when it can. It also refuses a rebase already in progress, a detached HEAD, main, master or the default branch, a branch name that is unsafe or ambiguous with a tag or other ref, a local ref shadowing origin/<branch> or origin/<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; use git_push to publish new commits.

git commit and git add are not tools; they stay in Bash.

Worktrees​

ToolWhat it does
worktree_provisionClaim 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_disposeDispose a worktree by its tree name; it goes to the restorable trash.
worktree_stop_holdersEnd 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):

ToolWhat it does
herd_gatesList a herd's open gates.
herd_askOpen a gate asking the herd operator questions, using this worker's identity.
herd_answerRead the answer to a gate previously opened with herd_ask.
herd_reportPost a status report to this worker's herd room.
herd_milestoneAnnounce an artifact (a spec, a plan, a PR) to the shepherd and open the milestone gate.

Shepherd side:

ToolWhat it does
herd_startStart a herd (room, workspace, gate subscription) for this session.
herd_briefAssemble a job brief from a template plus a strategy body or method file.
herd_spawnSpawn a worker pane for a job: provision its worktree and launch Claude with the brief. Takes minutes.
herd_closeClose one job's pane.
herd_follow_upReopen a done job for a follow-up round.
herd_attendOpen a job's pane in a tab of this shepherd's workspace.
herd_wrap_upClose panes, dispose worktrees, delete job dirs and archive the room in one pass.
herd_resumeRe-attach this session to a herd, taking it over as shepherd.
herd_statusOne herd: jobs, panes, gates, subscription, unread.
herd_listActive 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.)

ToolWhat it does
chat_sign_inSign this session in to rt chat: presence, a handle, and the repo room derived from cwd unless room or noRoom says otherwise.
chat_sign_outSign this session out; room memberships are kept.
chat_awaySet an away message without signing out.
chat_backClear the away message.
chat_joinJoin a room, with an optional wakeOn (mention, all, none).
chat_leaveLeave a room.
chat_roomsThe rooms this handle belongs to, with unread counts.
chat_whoA room's members and their presence.
chat_buddiesEvery chat handle on this machine and its presence.
chat_readRead unread messages (every room, or one), advancing the read cursor; since peeks without advancing, last returns a room's newest messages.
chat_markMark messages read: every room, one room, or one room up to a message id.
chat_postPost a message to a room.
chat_dmSend a direct message to another handle.
chat_ackAcknowledge a message by id.
chat_claimClaim a message so other agents skip answering it.
chat_releaseRelease a previously claimed message.
chat_archiveArchive a room this handle belongs to, or reopen it with reopen: true.
chat_inviteInvite 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​

ToolWhat it does
whoamiReport 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​

ToolWhat it does
rt_verbRun 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 PreToolUse hook on EnterWorktree tells the daemon which tree the session is about to enter (so does rt's worktree hook when it provisions a tree for EnterWorktree by 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> and rt events wait block past any tool timeout, so skills run them as background Bash or under Monitor. Install's allow list covers them with the prefix rules Bash(rt gate *) and Bash(rt events wait *).
  • The shepherd's gate answers. gate_answer always answers as the pane, so a shepherd answering a herd gate runs rt gate answer --by shepherd in Bash, under the same rt gate rule.
  • Commits and staging. git commit, git add, and git rebase --continue after 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 run gh in Bash.
  • Pane control. rt pane send, rt pane spawn, rt pane focus, rt pane accounts and rt pane directories (only pane list and pane peek run through rt_verb).
  • Chat after /clear. As above, rt chat sign-in and 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​