corralctl

Integration

MCP Server

A window onto the clones already on disk. In its default mode it makes no network calls and never contacts the GitHub API.

What it is

corralctl ships a Model Context Protocol server that exposes your local, corralctl-organised workspace to AI coding agents — Claude Code, Cursor, Cline, Codex CLI, Aider, and anything else that speaks MCP.

Where GitHub's own MCP server covers the remote API surface — issues, pull requests, search — corral-mcp covers the dimension only it can: your local mirror, organised by visibility and language, queryable without a round trip.

In its default read-only mode no network calls are made and the GitHub API is never contacted. The server reads the clones already on disk. That is the whole security posture, and it is worth stating plainly because it is the question an agent operator should ask first.

Tools

Read-only, available by default:

Tool What it answers
corral_list_repos Filter local clones by visibility, language, name or sync state
corral_find_repo Resolve a fuzzy name to one clone, returning candidates when ambiguous
corral_get_repo_metadata Full metadata for one clone, including its current branch
corral_status_summary Workspace summary: counts by visibility and language
corral_workspace_index The full structured index, in a single call
corral_find_symbol Find where a symbol is declared, across every clone at once
corral_repo_overview Summarise one repository's shape — languages, entry points, layout
corral_search_code Find where text appears — call sites, config keys, error strings — across every clone
corral_graph_dependencies Analyze cross-repository dependencies and reverse dependents
corral_doctor Audit workspace health, dirty working trees, unpushed commits, and worktrees

Multi-language symbol extraction

corral_find_symbol uses fast native parsers for Go, Rust, Python, TypeScript, and JavaScript. When Universal Ctags is installed on the host system, corralctl automatically falls back to it to extract symbols from Java, Kotlin, Swift, C, C++, and C# source trees without external services.

Mutations and audit

Write tools are opt-in and off unless you ask for them.

Passing --enable-mutations adds tools that modify repository state or execute git operations. The GitHub API is still never contacted.

Tool Behaviour Requires
corral_sync_repo Runs git pull --rebase --autostash against one clone --enable-mutations
corral_clone_repo Clones a URL into a sandboxed target path --enable-mutations
corral_create_worktree Creates an isolated linked Git worktree under .git/corral-worktrees --enable-mutations
corral_release_worktree Removes a linked Git worktree after branch work is finished --enable-mutations
corral_prune_worktrees Prunes stale or temporary scratchpad worktrees across repositories --enable-mutations
corral_adopt_repo Adopts a local repository, optionally relocating it and creating its remote on a forge --enable-mutations
corral_delete_repo Removes a clone; refuses on uncommitted or unpushed changes, and asks a person to approve each deletion --enable-destructive-mutations

corral_adopt_repo presents the operator's forge token, so the caller does not choose where it goes. Without forge_url it targets the forge's public instance. A self-hosted instance is accepted only when the operator named it at startup with --forge-url <url> (repeatable); any other forge_url is refused and audited before a token is read or a request is made.

Deletion sits behind a second, separate flag on purpose, and it refuses outright when a clone holds uncommitted or unpushed work rather than asking an agent to decide.

Those refusals stop mistakes. They do nothing about a persuaded agent that picks the one clone holding no unpublished work, because every check passes and the deletion is exactly what was asked for. So each individual deletion is also put to a person, over MCP elicitation, before it runs — access control at the execution layer rather than in prompt text, which is where the 2026 MCP security guidance puts it. A client that cannot ask its user anything cannot delete. --no-confirm-deletes switches this off, and is only appropriate for an unattended workspace you are willing to lose.

Every mutation writes a JSONL audit record to $XDG_STATE_HOME/corral/mutations.log, falling back to ~/.local/state/corral/mutations.log. What an agent did to your workspace is therefore reconstructable after the fact, which is the point.

Event tracing

Passing --trace-file <path.jsonl> records structured event logs for every JSON-RPC request, tool invocation, and error with nanosecond timestamps:

corralctl mcp --trace-file /tmp/corral-trace.jsonl

Workspace watcher

--watch (on by default) drops the cached workspace index as soon as a repository is cloned, moved, removed or re-pointed, instead of waiting for the index's five-second expiry. It watches only what that index is built from: the layout directories down to the scan depth and each clone's .git directory. It does not watch a clone's working tree, hidden directories, or dependency and build trees such as node_modules, vendor and target. Inside a .git directory only config, HEAD and the corral state file count, so git's own bookkeeping (git status rewriting .git/index, a fetch writing FETCH_HEAD) does not drop the index.

On macOS and the BSDs every watched directory, and every entry in it, holds a file descriptor, so the watcher uses at most half of the process's open-file limit. If it reaches that budget, or the system refuses a watch, it says so once on stderr and stops adding watches; changes in the directories left unwatched are seen when the index expires. Registration runs in the background and does not delay the MCP handshake. --watch=false turns the watcher off.

A workspace scan that runs out of file descriptors is reported as an error rather than as a workspace with fewer (or no) repositories in it.

Transport

By default the server speaks stdio: the client launches corralctl mcp as a subprocess and talks to it over the pipe. There is no endpoint and no listening port.

The other two transports listen on --host and --port, for a client that connects to a server somebody else started:

Command Transport Endpoint Protocol revisions
corralctl mcp stdio — negotiated on initialize
corralctl mcp --transport streamable-http --host 127.0.0.1 --port 8000 Streamable HTTP http://127.0.0.1:8000/mcp 2026-07-28 and 2025-11-25, on the one endpoint
corralctl mcp --transport sse --port 8001 HTTP+SSE (legacy) http://127.0.0.1:8001/sse 2024-11-05

The Streamable HTTP endpoint serves both current revisions of the specification. A 2026-07-28 client sends no initialize and holds no session: each request carries its own identity in _meta, mirrored in the Mcp-Protocol-Version header, and server/discover asks who the server is. A 2025-11-25 client initialises, is issued Mcp-Session-Id, and may open a standalone stream with GET. The server tells them apart by the header and, without one, by whether the request opens or names a session; an idle session is closed after an hour. --transport sse is the older HTTP+SSE transport: GET /sse opens the event stream, and the endpoint event it sends names where the client posts its messages.

--http 127.0.0.1:7777 is the older spelling of --transport streamable-http and still works, with a notice on stderr.

The address has to be on loopback. This server exposes every repository under its root (with mutations enabled, it can change them), so binding it to a routable interface publishes all of that. --host 0.0.0.0, or --http :7777, which is the form typed by somebody thinking about the port and not the host, binds every interface and is refused with an explanation. --allow-remote overrides the refusal, and only together with a bearer token, read from --token-file or CORRAL_MCP_TOKEN. A request without the token, or with the wrong one, is answered 401 with a WWW-Authenticate challenge. --token still works but is deprecated, because anything on the command line is visible to every user on the machine through ps.

The server speaks plain HTTP and has no TLS listener. On a routable address the token and every response cross the network unencrypted unless a TLS-terminating reverse proxy sits in front of it, and the server says so on stderr when it starts. There is no OAuth resource-server mode.

A browser sends an Origin header with every request, and the server refuses one that is not listed in --allowed-origins. Loopback origins are not trusted by default, because every local dev server is one; list the page a browser client runs on, for example --allowed-origins http://localhost:6274. A client that is not a browser sends no Origin and is unaffected.