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

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.
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 or channel’s display name. Only updates the name — the directory and Claude sessions are preserved.
rename_worktree_threadRename a worktree thread to new_name: renames the worktree directory and its worktree/<name> branch, relocates the Claude session store, and updates the display name. Sessions are preserved; rejected with 409 if a run is active.
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.

Documentation

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

Playground

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

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. Uses MCP Channels for push delivery.

ToolDescription
list_agentsList active agents in the current channel with status and work summaries
send_agent_messageSend a push message to another agent by ID (delivered via notifications/claude/channel)
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:

  • Declares capabilities.experimental["claude/channel"] in the MCP initialize response
  • Sets instructions telling Claude how to use agent tools and respond to channel messages
  • Starts a push receiver goroutine (WebSocket to /api/ws/agent-channel) that forwards messages as notifications/claude/channel JSON-RPC notifications to stdout

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