Skip to main content

MCP server

Bleep ships a Model Context Protocol server. Point Claude Code (or any MCP-aware client) at it and an agent can compile, test, run, and inspect your build through structured tool calls, without parsing CLI output, without keeping a long-running interactive shell open, without reading pages of context for a one-line answer.

The design is built for the world where multiple agents run against multiple checkouts of the same build at the same time:

  • Stateless. Every tool call names the checkout it targets via a required directory parameter, is bootstrapped fresh from that workspace's bleep.yaml, talks to the shared compile server, and disconnects. One MCP server serves a whole session — including subagents working in other git worktrees — and holds nothing that can go stale or leak when a worktree is removed.
  • Compact by default. Compile and test return a small JSON summary (error counts, failure suites) plus a historyId. The full transcript of that exact run is one bleep.history.show call away.
  • Errors stream. Per-project compile errors land as MCP notifications the instant that project finishes, not at the end of the whole build. The latency floor for a real failure is milliseconds.

Setup

The recommended setup is one user-scoped registration that covers every project and every git worktree — MCP stdio servers launch with the session's working directory, and bleep needs nothing else:

claude mcp add --scope user bleep -- bleep mcp-server

Alternatively, run from a build root:

bleep setup-mcp-server

That writes .mcp.json, the config file that Claude Code, Cursor, and any other MCP client reads to discover servers. Restart the client (or trigger a re-scan) and the bleep tools appear.

The flag --force-jvm runs the MCP server through the JVM rather than the native binary, useful when iterating on bleep itself.

Non-bleep projects

A user-scoped registration advertises the bleep tools in every session — including sessions on Maven, Gradle, or sbt projects that bleep cannot build. An agent whose "compile" intent pattern-matches to bleep.compile in such a project gets a fast, definitive answer: the call fails in milliseconds with "<directory> is not part of a bleep build: no bleep.yaml exists there or in any parent directory", and the error text redirects to the project's own build system rather than reading like a transient bleep failure.

The inverse deserves stating because agents get it wrong: bleep tool availability never identifies a project's build system. If the bleep tools disappear mid-session, the MCP connection dropped (commonly because the bleep binary was reinstalled underneath the server) — reconnect with /mcp or fall back to the bleep CLI. Whether a project is a bleep project is decided by bleep.yaml on disk, and by nothing else.

The tool surface

ToolEffectRuns code from the checkoutWhat it does
bleep.compiledestructiveyesCompile selected projects. Returns error counts and a historyId. Optional diffBase adds a diff section vs a previous run.
bleep.testdestructiveyesRun tests. Returns pass/fail counts, failure summaries, and a historyId. Optional diffBase adds a diff section vs a previous run.
bleep.rundestructiveyesCompile and run a project or script. Returns stdout/stderr and exit code.
bleep.sourcegendestructiveyesRun sourcegen scripts for selected projects.
bleep.test.suitesadditiveyesList test suite class names without running them. Requires projects to be compiled.
bleep.fmtdestructivenoFormat Scala and Java sources via scalafmt and google-java-format. Rewrites files in place.
bleep.cleandestructivenoDelete compile output for selected projects.
bleep.copy-stateadditivenoSeed a fresh git worktree with the parent worktree's compiled state. Call once after forking.
bleep.restartdestructivenoExit the MCP server process. The client will relaunch it.
bleep.history.listread-onlynoList the workspace's recorded compile/test runs: historyId, time, mode, targets, client.
bleep.history.showread-onlynoThe full transcript of a completed compile/test run, by historyId. Searchable (regex) and paginated.
bleep.history.diffread-onlynoWhat logically changed between two runs: newly failing/fixed tests, invalidations, new/resolved diagnostics. baseDirectory diffs across worktrees.
bleep.history.diff-timingread-onlynoWhat got slower or faster between two runs, jitter suppressed, plus the target run's slowest items.
bleep.build.effectiveread-onlynoThe project config after templates apply, what bleep sees.
bleep.build.resolvedread-onlynoFully resolved classpath, source dirs, compiler JARs. Requires prior compile.
bleep.projectsread-onlynoList projects with their dependencies and test-project flag.
bleep.programsread-onlynoList projects with a mainClass (runnable programs).
bleep.scriptsread-onlynoList the named scripts under scripts: in bleep.yaml.

What the effect column means

Effect mirrors the MCP spec's tool annotations — read-only maps to readOnlyHint, destructive to destructiveHint — and clients use them to decide what may run unattended, without asking the user. So the column is a security statement, not a description of intent, and bleep sets it accordingly:

  • read-only means bleep reads files and its own build model, and nothing else happens. Only the nine tools above qualify: the history tools read transcript JSON off disk, and the listing tools parse bleep.yaml and expand templates. No build code runs, nothing is written.
  • Building is not read-only. A compile runs sourcegen scripts, annotation processors, KSP and macros; bleep.test additionally runs the test bodies; bleep.run runs the program you name. That is arbitrary code from the checkout, executing with your privileges — it can write files, reach the network, and touch anything your user can. A tool that does this must never be advertised as safe to run without asking, however read-only "compile" sounds. bleep.fmt rewrites your sources in place and bleep.clean deletes output, so they are destructive for the ordinary reason.
  • bleep.test.suites runs no test body, but it is not a pure read either. Discovery builds a classloader over the project's classpath and instantiates the test frameworks it finds there, so code from the checkout's dependencies executes in the compile daemon. It writes nothing, hence additive rather than destructive.

The practical consequence: if you point an agent at a checkout you do not trust (a contributor's pull request, a repository you just cloned), auto-approving bleep.compile is equivalent to auto-approving run whatever this branch says. The annotations are pinned by tests (bleep.McpToolEffectTest) so they cannot quietly drift back.

Tools that reach the network — anything that resolves dependencies or starts the compile daemon — additionally set openWorldHint.

Every tool except bleep.restart requires directory: the absolute path of the checkout to act on. An agent's current working directory works — bleep finds the build root from any directory inside it. Even the bleep.history.* tools take it: history ids are per-workspace, so an id means nothing without saying whose history to look in.

Output shape

Compile and test return a summary, not a transcript:

{
"success": false,
"errors": 2,
"warnings": 3,
"summary": "Build failed: 2 errors in 1 project. 3 warnings. Use bleep.history.show with this historyId for all diagnostics",
"failedProjects": ["myapp"],
"topErrors": [{"message": "...", "path": "Main.scala", "line": 42}],
"historyId": 7
}

The transcript of every completed run is written by the compile daemon into the workspace itself (.bleep/builds/normal/history/<id>.json, the last 32 kept), keyed by that historyId. History is per-worktree, shared with the CLI (bleep history, bleep history show, bleep history diff) and IDE builds, and survives MCP server restarts — the server itself keeps nothing in memory. The run history & diffs guide covers the recording and diffing model in depth. When the agent decides to drill in:

agent: bleep.history.show { directory: "/checkout", historyId: 7 }
→ every diagnostic, every failing test with its stack trace;
project/limit/offset parameters paginate large results

agent: bleep.history.show { directory: "/checkout", historyId: 7, query: "NullPointer|timed out" }
→ only the matching diagnostics or failures — a case-insensitive
regex over messages, paths, suite/test names and stack traces

The query parameter exists because searching output is what agents do. Given a long log they will divert it to a file and grep it — losing structure, and often the answer. Searching the structured transcript server-side returns exactly the matching items, still as data.

For the edit–run loop, bleep.compile and bleep.test take an optional diffBase ("previous" for the most recent run of the same mode, or a historyId to pin a base such as the last green run): the base is validated before the build starts, and the response gains a "diff" section — newly failing/fixed tests, new/resolved diagnostics — so the agent reads what its edit changed instead of re-reading full results. The CLI mirror is bleep compile --diff / bleep test --diff.

A transcript describes its own run, never current state — so it cannot go stale when someone compiles from the CLI in between. The current state of the build is always one (cheap, incremental) bleep.compile away.

Multiple checkouts and git worktrees

MCP clients launch stdio servers once per session, and subagents share the parent session's connection. A subagent working in a git worktree therefore talks to an MCP server that was started in the parent checkout — which is why directory is required on every call rather than inferred: the server has no ambient workspace, so there is nothing to silently get wrong. Each call bootstraps the named workspace fresh (config edits, including JVM changes, take effect on the next call) and is routed to the shared compile server, which already serves all worktrees from one deduplicated in-memory state.

If a build declares a $version different from the running MCP server's, the call fails with an explanation — install the matching binary and call bleep.restart, which exits the process so the client relaunches it as the new binary.

A fresh worktree starts cold: it has your parent's sources but none of its compiled state. bleep.copy-state { directory, from } (CLI: bleep copy-state <source-worktree>) fixes that in one call — it clones the parent worktree's classes, zinc analyses and generated sources into the new one, using the fastest copy the filesystem offers (clonefile on APFS, reflink where Linux supports it). The copy runs in the compile daemon under the same per-project locks compiles take, so it is safe even while other agents keep compiling the parent. The first build afterwards no-ops through everything unchanged and compiles incrementally from the parent's baseline for the rest — typically a few files, not a few hundred. State is copied from the worktree you forked off and nowhere else: what you start with is exactly what your parent had, which makes a seeded worktree's behavior explainable rather than dependent on whatever happens to be in a cache.

Why these design choices

Stateless over cached

Anything an MCP server caches between calls — a bootstrapped workspace, a daemon connection, "last build" results — can go stale: config edits, binary upgrades, CLI builds racing MCP builds, worktrees deleted under the server. Each piece of held state would need its own invalidation rule. Bootstrapping per call costs roughly what a CLI invocation costs, which is the thing bleep is already fast at; all expensive state lives in the shared compile server, where it is bounded and shared across checkouts.

Compactness over completeness

A full compile transcript can be tens of thousands of tokens, and dumping it into an agent's context does real damage: the signal drowns, the agent starts managing output instead of fixing code — redirecting logs to files, grepping them, paging through noise — and frequently never finds the line that mattered. Structured data removes that entire class of failure. The agent gets did anything go wrong as a boolean, counts, and the first errors in a couple hundred tokens; everything deeper is a targeted bleep.history.show call — paginated, or searched with a regex — that returns exactly the items asked for, still as data.

Errors stream, results summarise

When something is wrong, latency matters, the agent should know about a compile error the instant the project finishes, not 30 seconds later when the rest of the build wraps. So per-project errors stream as notifications during the call, while the summary that wraps up the call is the counts.

See also