Skip to content
MCP Server

MCP Server

MCP (Model Context Protocol) server that runs inside agent containers, providing tools for task scheduling, communication, and memory search.

Package: internal/mcpserver

Overview

The MCP server is started as a subprocess via loop mcp inside each agent container. It communicates over stdio transport and proxies all operations through the daemon’s HTTP API.

Agent (Claude Code)  ←→  MCP Protocol (stdio)  ←→  loop mcp  ←→  HTTP API  ←→  Daemon

CLI Flags

FlagDescription
--channel-idChannel or thread ID
--dirProject working directory (for memory lookups)
--api-urlDaemon HTTP API URL
--author-idUser who triggered the request
--platformPlatform identifier
--memoryEnable memory search tools
--agent-idAgent ID for inter-agent tools and MCP Channels
--logCustom log file path

Registered Tools

Always Available (19 tools)

Task Management

ToolDescription
schedule_taskCreate a scheduled task (cron, interval, once, or manual) running a prompt, a workflow (workflow_name), or a shell script (bash_script). Supports template_name for deduplication and auto_delete_sec for thread cleanup.
list_tasksList all scheduled tasks for the current channel and its worktree threads; threads: true adds its plain threads
show_taskShow full task details by ID including complete prompt
cancel_taskCancel a scheduled task by ID
toggle_taskEnable or disable a task by ID
edit_taskEdit a task’s schedule, type, prompt, workflow, bash script, or auto_delete_sec
move_taskMove a task to another thread, worktree thread or the root channel of its project (default: the current channel), keeping its settings; its thread moves too

Communication

ToolDescription
send_messageSend a message to a channel or thread. channel_id is optional — omit it to target the current channel/thread. Supports @BotName mentions (auto-converted to proper mentions).
queue_messageQueue a follow-up prompt for yourself in the current channel/thread/worktree. Enqueues a new turn behind any running/queued work (shows in the chat’s queued-messages list); interrupt=true cancels the active run and jumps the queue to run next. delay_seconds (optional, non-negative) holds the prompt back until the delay elapses — it stays queued with a live countdown in the UI and a background poller drains it once due; a delay forces interrupt off. A delayed prompt’s result includes its msg_id, for delete_queued_message.
list_queued_messagesList the messages waiting in the current channel/thread/worktree’s queue (GET /api/channels/{id}/queued): each one’s msg_id, whether it’s running, queued or delayed (with the UTC time it runs), and its first 120 characters.
delete_queued_messageRemove a waiting message from the current channel/thread/worktree’s queue by msg_id (DELETE /api/messages/{id}), like the chat’s Remove from queue. The message is looked up in the queue first: one already running, or one not in the agent’s own channel, is refused. Denied in read-only runs (learn and explain).
monitorWait for something in the background without holding the channel (POST /api/channels/{id}/monitors ): command runs with /bin/sh -c in a fresh agent container while the agent’s turn ends, and when it exits or times out (timeout_sec, default 3600, at most 86400) a [monitor <id>] message with its status and output tail wakes the agent. That message queues behind a run in progress; with interrupt it cancels that run instead. Claude Code’s own Monitor tool is denied in one-shot runs, since it would keep the run, and the channel, busy until the watch ends; a live session allows it, since its process outlives the turn. At most 10 per channel. Own channel only; denied in read-only runs.
list_monitorsList the current channel’s running monitors: id, description, start time (UTC), timeout and command.
cancel_monitorStop one of the current channel’s monitors by monitor_id, without a wake-up message. Denied in read-only runs.
list_sessionsList the Claude Code sessions in a channel’s project dir (GET /api/channels/{id}/sessions), newest first: each one’s session_id, last write time (UTC) and the first 120 characters of its last message. The channel’s current session is marked *, one a channel or thread already holds [thread]. channel_id is optional — defaults to the current channel/thread.
resume_sessionMake another of the project’s sessions the channel’s (PUT /api/channels/{id}/session), like the Sessions panel’s resume here, so its next message resumes that conversation. channel_id is optional — defaults to the current channel/thread. The agent’s own channel has a run in progress, so its switch takes effect when that run ends (after the run saves its own session). A session a thread also holds is forked on the next run. Denied in read-only runs (learn and explain).
create_channelCreate a new channel (bot auto-joins)
create_threadCreate a thread in the current channel. If message provided, triggers an agent immediately.
create_worktree_threadCreate a thread backed by a fresh git worktree (mirrors the UI +wt button). branch is the existing base to fork from (e.g. main); a new worktree/<name> branch is created and checked out off it. Optional name for the worktree directory; optional message triggers an agent immediately.
rename_threadRename a thread, worktree thread or channel’s display name. Only updates the name — the directory, git branch and Claude sessions are preserved.
set_thread_descriptionSet a thread or worktree thread’s description (at most 500 characters), shown in the sidebar’s hover popup. thread_id defaults to the agent’s own thread; an empty description clears it.
set_ticket_urlLink a channel, thread or worktree thread to its ticket: the absolute http(s) URL of a Jira issue, GitHub issue or PR, or any other tracker’s page. Shown in the sidebar’s hover popup, and opened from the row’s Open Ticket menu item. channel_id defaults to the agent’s own channel or thread; an empty ticket_url clears it.
fork_threadFork a thread by ID (mirrors the sidebar +fork action): creates a sibling that continues the source’s conversation on a forked Claude session, leaving the source untouched. Worktree threads also get a fresh worktree branched from the source’s committed state. No agent is triggered — follow up with send_message to task the fork.
delete_threadDelete a thread by ID
search_channelsSearch channels and threads by optional query. Returns IDs, names, directory paths, parent IDs, and active status.
channel_statusShow a channel’s tree (GET /api/channels): the channel, its threads and worktree threads and theirs, indented, each with the lines of its sidebar hover popup : description, ticket, path, branch (and a worktree’s base), its PR/MR’s number, checks and URL (GET /api/channels/{id}/pr/status, for the rows on a branch), why its ci, cnf and cmt pills are lit, commit, distance from its base and upstream, model/effort overrides, and whether its agent is running or it’s locked. channel_id is optional — defaults to the tree the current channel/thread is in, marked this one.

Review

ToolDescription
run_reviewStart a PR review as the Review panel’s Run button does (POST /api/channels/{id}/review/start): pr_number, optional model and effort (the channel’s own by default), mode (review or review-fix), max_iterations (1–10, default the project’s review.max_iterations, 3 unless set), fork (chat, fresh or a session id to fork; the Review panel’s choice by default, chat for a newly loaded PR), scope (auto, full or incremental, see Incremental re-review ; the Review panel’s choice by default) and channel_id (this channel by default; another channel or thread’s id runs the review there, so reviews can run in several threads at once). Loads the PR unless already loaded, starts review-loop or review-fix-loop and returns the run id. Refused while that channel has a review loading or running. Denied in read-only runs.
report_review_findingsAdd findings to the channel’s PR review session (POST /api/channels/{id}/review/comments): each one’s repo-relative path, 1-based line, side (RIGHT, the default, or LEFT) and body. Duplicates are skipped. Denied in read-only runs.
get_review_commentsRead the channel’s review session (GET /api/channels/{id}/review?diff=false): the PR number, title and URL, head SHA, status, the PR outcome with the comments that decide it, the commit it was approved at when it was, and every comment as [id] path:line SIDE, its source (agent, or github by <author> with the GitHub comment id), whether an agent comment was pushed (with its GitHub id), outdated and resolved, its category, verdict and impact when it has them, a GitHub thread’s assessment (thread addressed or thread open with its impact), then the body, the verdict’s or assessment’s reason, the paths of the transcripts of the agent passes that reported or changed it, and the GitHub replies in its thread, each with its author and GitHub id. The count leaves replies out. unpushed_only keeps the agent comments not yet pushed; path keeps one file. The PR diff is left out.
dedup_review_findingsRun the dedup pass over the channel’s review session (POST /api/channels/{id}/review/dedup), as loop review:dedup does: removes agent comments that repeat another comment (from the PR too when they were pushed; GitHub comments are never deleted), trims an unpushed one that bundles an issue another comment covers, re-anchors misplaced ones, notes related ones and records a verdict (real, false_positive or already_fixed, with a reason) on the agent comments it keeps, then lists what it did. Takes minutes; refused while a review run is in progress. Denied in read-only runs.
delete_review_commentDelete an agent comment by comment_id (DELETE /api/channels/{id}/review/comments/{cid}), from the PR too when it was pushed. GitHub comments are refused. Denied in read-only runs.
update_review_commentReplace an unpushed agent comment’s body (PATCH /api/channels/{id}/review/comments/{cid}), e.g. to fold in what a deleted duplicate added, and/or record a verdict (real, false_positive or already_fixed) with a reason, replacing the dedup pass’s, and with real its impact (blocking or nice_to_have); Check all and the verification step record their verdicts this way. Give body, verdict or both. A pushed comment takes only a verdict; GitHub comments can’t change. Denied in read-only runs other than the verification step’s.
assess_review_threadRecord whether an unresolved GitHub thread a person started still stands, by its first comment’s comment_id (POST .../review/comments/{cid}/assess): status addressed (a later commit or a reply settles it) or open with its impact (blocking or nice_to_have), and a one-sentence reason. The outcome counts open threads; an agent comment takes a verdict instead. Denied in read-only runs other than the verification step’s.
push_review_commentPost one agent comment to the PR as the configured gh user (POST .../review/comments/{cid}/push). Denied in read-only runs.
push_all_review_commentsPost every unpushed agent comment to the PR (POST .../review/push-all), skipping those the dedup pass marked false_positive or already_fixed, and report how many were pushed, skipped and which failed. Denied in read-only runs.

The review tools act on the agent’s own channel only and take no channel_id. The daemon holds the routes that change comments to the token’s own channel as well (see Agent scope ).

Documentation

ToolDescription
get_readmeGet Loop README with setup instructions, configuration, commands, and architecture

Playground

ToolDescription
ui_stateThe desktop app’s windows: each one’s open channel, layout tab and panes (with agent terminals’ status and whether they’re busy), and the side view open beside the chat. Windows showing a channel outside the agent’s project have no state.
ui_runRun steps in a desktop app window, in order until one fails: open a channel, switch tabs, add, replace, remove or maximize panes, open a file, scroll the chat to a message (show_message), open or close the Learn, Explain or Shell side view and read it (open_side, pane side), type into an agent terminal, read its output or wait for it, or queue a message in a chat pane and wait for its reply. Without a leading select_channel, it opens the agent’s own channel first; any channel it opens must be in the agent’s project. Terminal steps work only on agent terminals (docker-agent, docker-shell) and chat panes (chat, chat-side) and the side view, never host shells. See API: UI Bridge .
playgroundManage playgrounds (action: create/update/delete). Create sets up the entry HTML, title, and description. Use playground_file to add JS, CSS, and other files.
playground_fileManage files within a playground (action: create/update/read/delete/list). Write script.js, style.css, importmap.json, lib/utils.js, etc. Files served at relative URLs for ES module imports.
playground_shareExpose a playground publicly over a cloudflared quick tunnel, or stop (action: share/unshare). share returns a unique public URL; idempotent per playground. Requires playground_share.enabled. See Playground: Public sharing .

Chat Components

ToolDescription
chat_componentShow a component inline in the desktop chat (action: templates/show). templates returns each template with a guide to filling it; show takes template, title, html and optional css/js, and posts the composed document as an agent message in the current channel. Built-in templates: math, canvas, react; more under chat_components in config. Rejected on Slack and Discord. See Chat: Components .

Shortcuts

ToolDescription
prompt_shortcutManage prompt shortcuts triggered via # in chat. Actions: list, add, update, delete. Scope: global (~/.loop/config.json) or project (project .loop/config.json).
bash_shortcutManage bash shortcuts triggered via $ in the terminal shortcuts bar. Actions: list, add, update, delete. Scope: global or project.

Shortcut scopes

list is cross-scope: it returns global and project shortcuts merged, with each entry labelled by the scope it is defined in (project entries shadow global ones of the same name).

add, update and delete operate on one config file and therefore require an explicit scope — there is no default. A shortcut written to one scope is invisible to the other, so a delete that omitted the scope used to silently search the global config and report a bare “shortcut not found” for a shortcut that list had just displayed. Run list first and pass back the scope it reports. A not-found error now names the scope and config file that were searched.

Names are stored verbatim, including spaces — git pull is a valid shortcut name and is matched exactly, never slugified.

Agent Tools (when --agent-id set)

Enabled for Swarm/Canvas terminal agents. Messages are typed into the target agent’s terminal.

ToolDescription
list_agentsList the agents in the current channel, its chats and terminal agents, with status and work summaries
send_agent_messageSend a message to another agent by ID as [from <agent id>] <message>: typed into a terminal agent’s terminal, or queued as a chat’s next message; fails when a terminal agent has no terminal
update_agent_statusUpdate this agent’s display name and work summary (visible to other agents and frontend)

When --agent-id is set, the server also sets instructions telling Claude how to use the agent tools and how messages from other agents arrive.

With --agent-id chat, the id chat runs get, the server has list_agents and send_agent_message but not update_agent_status, and it doesn’t register: the daemon lists the channel’s chats itself, and queues the messages sent to them (see Multi-Agent Workspaces ). Its list_agents marks the chat that asked, from the API’s self.

Learn Tools (learn agent only)

Registered only when --agent-id is learn, the id the orchestrator gives a learn pass . A learn pass works alone, so that id gets none of the agent tools above: no list_agents, send_agent_message or update_agent_status, and it isn’t registered in the agent registry.

ToolDescription
propose_learningsFile the learn pass’s proposals for the user to apply or dismiss in the Learn view; nothing is applied until they do. Takes proposals, at most 5 items of {kind, title, rationale?, payload}, where kind is prompt_shortcut, bash_shortcut, scheduled_task, gate_rule, mount, rename, description or ticket_url, plus an optional replaces, the id of an earlier proposal the new one supersedes. withdraw, at most 20 items of {id, reason}, withdraws earlier proposals the run shows are stale; only pending or failed ones of the channel can be. Either list may be empty, not both. Posts to POST /api/channels/{id}/learn/proposals for the server’s channel, which must be a learn thread; if any proposal is invalid or any withdrawal refused, nothing changes and the error names the one to fix.

A learn run can’t call the Loop tools that change state (shortcuts, tasks, threads, messages, workflows, playgrounds and so on): they’re denied by the learn run’s --disallowedTools, not left unregistered.

Workflow Tools (always available)

ToolDescription
run_workflowStart a workflow run by name. Pass inputs for required/optional workflow inputs. Uses list_workflows to discover available workflows.
get_workflow_runGet the status and node outputs of a workflow run by its run ID
list_workflowsList all available workflow definitions with names, descriptions, and input schemas
list_workflow_runsList recent workflow runs, optionally filtered by channel_id
cancel_workflow_runCancel a running workflow by its run ID
resume_workflow_runResume a paused workflow (e.g. after an approval node) with an optional response
save_workflowCreate or update a workflow definition in global or project config. Pass action (add/update) and the full workflow JSON
delete_workflowDelete a workflow definition by name from global or project config
delete_workflow_runDelete a workflow run by ID (cancels first if active)
retry_workflow_runRetry a completed/failed/cancelled workflow run by ID

Memory Tools (when --memory enabled)

ToolDescription
search_memorySemantic search across memory files. Returns relevant chunks ranked by similarity. Optional top_k (default 5).
index_memoryForce re-index all memory files after edits

Quality Tools

ToolDescription
quality_scanTrigger an architectural quality scan for the current channel. Returns a status hint immediately; the full report ships via the quality.scanned event.
quality_snapshotRead the persisted snapshot (current branch first, then most recent). Returns quality_signal, geo-mean, per-metric breakdown, and a branch-mismatch flag.
quality_complexityPer-function complexity hotspots from the cached graph (cyclomatic, cognitive, max nesting, params, LOC). Optional limit (default 50, max 100) and offset (default 0) page through the worst-first list. Requires a prior scan.
quality_clonesClone clusters from the cached graph (SimHash near-duplicate detection). Optional limit (default 25, max 50) and offset (default 0) page through clusters by total LOC desc. Requires a prior scan.

These four tools read channelID from the per-channel server struct and take no WorkDir argument. See Quality for the engine and metric semantics, including the T/raw complexity curve and the SimHash + Hamming-distance clone clustering.

Construction

server := mcpserver.New(channelID, apiURL, authorID, httpClient, logger,
    mcpserver.WithMemoryAPI(dirPath),       // optional: enables memory tools
    mcpserver.WithAgentTools("docker-agent-0"), // optional: enables inter-agent tools
    mcpserver.WithWorkflowAPI(),            // enables workflow tools (always added)
)
server.Run(ctx, transport)
server.UnregisterAgent() // graceful cleanup after Run returns

Graceful Shutdown

After Run() returns, the caller invokes UnregisterAgent() which sends DELETE /api/agents/{id}?channel_id=X to the backend. This removes the agent from the registry so other agents no longer see it in list_agents results. If no --agent-id was set, UnregisterAgent() is a no-op.

Design

  • All tool handlers call daemon HTTP endpoints — the MCP server is a thin proxy
  • doAPICall[T]() — generic HTTP request wrapper with JSON unmarshaling
  • doAPICallNoBody() — for DELETE/POST with 204 No Content
  • Error responses set IsError: true in MCP result
  • Per-channel MCP config files (mcp-{channelID}.json) avoid races when parent/thread share workDir

Related docs

  • API — HTTP API reference (includes agent registry endpoints)
  • Multi-Agent — Agent registry, MCP Channels, Swarm & Canvas layouts
  • Containers — Container MCP config setup
  • Memory — Semantic search and Ollama embeddings
  • Quality — Architectural quality engine
  • Scheduling — Task types and templates
  • Workflows — DAG-based workflow engine