# Bazel Mcp

> MCP server for Bazel focused on token reduction

- **Type:** MCP server
- **Install:** `agentstack add mcp-ewhauser-bazel-mcp`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [ewhauser](https://agentstack.voostack.com/s/ewhauser)
- **Installs:** 0
- **Category:** [Integrations](https://agentstack.voostack.com/c/integrations)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [ewhauser](https://github.com/ewhauser)
- **Source:** https://github.com/ewhauser/bazel-mcp

## Install

```sh
agentstack add mcp-ewhauser-bazel-mcp
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# bazel-mcp

[](https://github.com/ewhauser/bazel-mcp/actions/workflows/ci.yml)
[](https://ewhauser.github.io/bazel-mcp/)
[](LICENSE)

Run Bazel from MCP-compatible coding agents without filling their context
windows with build logs.

`bazel-mcp` is a local [Model Context Protocol](https://modelcontextprotocol.io/)
server. It runs Bazel in your workspace, returns a compact actionable result,
and keeps the complete invocation evidence available for inspection when an
agent needs more detail.

Read the [documentation](https://ewhauser.github.io/bazel-mcp/) for installation,
guided workflows, configuration, architecture, and reproducible benchmarks.

## Why bazel-mcp?

Bazel output can be enormous. Sending complete progress output, repeated
warnings, and test logs to a coding agent wastes context and can hide the error
that matters.

`bazel-mcp` gives agents:

- concise summaries of successful and failed invocations;
- structured diagnostics, test results, coverage, artifacts, and query output;
- filtered, paginated access to retained evidence;
- cancellation of queued or running commands.

Bazel still runs locally with your workspace, configured credentials,
toolchains, and remote execution settings.

## Quick start

### Requirements

- macOS, Linux, or Windows x86_64 (preview)
- Bazel 8 or 9, Bazelisk, or an executable workspace-local `tools/bazel`
- an MCP-compatible client
- Bazelisk when building from source (the repository pins Bazel and Rust)

### Install with Homebrew

```sh
brew install ewhauser/tap/bazel-mcp
```

### Install from a release

Download a prebuilt archive from the
[latest GitHub release](https://github.com/ewhauser/bazel-mcp/releases/latest),
or run the shell installer on macOS or Linux:

```sh
curl --proto '=https' --tlsv1.2 -LsSf \
  https://github.com/ewhauser/bazel-mcp/releases/latest/download/bazel-mcp-server-installer.sh | sh
```

On Windows, run the PowerShell installer:

```powershell
irm https://github.com/ewhauser/bazel-mcp/releases/latest/download/bazel-mcp-server-installer.ps1 | iex
```

### Build from source

Clone the repository and build the server with the pinned Bazel and Rust
toolchains:

```sh
git clone https://github.com/ewhauser/bazel-mcp.git
cd bazel-mcp
bazelisk build -c opt //:bazel-mcp
```

The binary is written to `bazel-bin/crates/bazel-mcp-server/bazel-mcp`.

### Connect your MCP client

Register the binary as a stdio MCP server. The exact settings location depends
on your client.

```json
{
  "mcpServers": {
    "bazel": {
      "command": "bazel-mcp"
    }
  }
}
```

If you built from source without installing the binary, use the absolute path
to `bazel-bin/crates/bazel-mcp-server/bazel-mcp` instead.

Restart the client, open a Bazel workspace, and try prompts such as:

- “Build `//app:server`.”
- “Run the tests under `//services/...` and explain any failures.”
- “Which targets depend on `//lib:core`?”

No configuration file is required. By default, the server can run Bazel in any
workspace accessible to the current user. See [Security and local data](#security-and-local-data)
to restrict it to specific roots.

### Use the filtered Bazel CLI

Harnesses that can launch only a Bazel-style command can use the same bounded
result without speaking MCP:

```sh
bazel-mcp passthrough -- test //services/...
```

Agent mode is also selected when `BAZEL_MCP_MODE=agent` is set or when the
executable filename is `bazel`, for example through a symlink placed earlier on
the harness's `PATH`. It accepts ordinary Bazel startup arguments, a configured
command, and command arguments. The current workspace is discovered by walking
upward from the launch directory.

Agent mode captures and reduces output through the same runner as `bazel.run`,
prints the configured JSON or TOON representation, and exits with Bazel's exit
code. Its raw output and BEP files live only in an invocation-scoped temporary
directory that is removed before the command returns. Because there is no
retained invocation, the result does not advertise `bazel.inspect`; truncated
or otherwise omitted detail instead produces a hint for an unfiltered rerun:

```sh
bazel --no-agent-mode test //services/...
```

`--no-agent-mode` must be the first argument. It executes the resolved real
Bazel with inherited standard input, output, and error streams. Configure
`bazel_executable` when the shim would otherwise be the only `bazel` on `PATH`;
the wrapper refuses to resolve itself recursively.

## Tools

The server exposes three tools:

| Tool | Purpose |
| --- | --- |
| `bazel.run` | Run an allowed Bazel or configured Aspect command and return a bounded summary. |
| `bazel.inspect` | Read filtered diagnostics, tests, coverage, artifacts, query results, or logs from an invocation. |
| `bazel.cancel` | Cancel a queued or running invocation. |

`bazel.run` supports `build`, `test`, `coverage`, `query`, `cquery`, `aquery`,
selected informational commands, and an opt-in form of the Bazel `run`
command. Operators may also route explicitly allowed commands such as `lint`
through Aspect CLI. Successful results are limited to 2 KiB and unsuccessful
results to 8 KiB. Follow-up `bazel.inspect` calls are also bounded and
paginated, so an agent only retrieves the evidence it needs.

The Bazel `run` command is denied by default. Once enabled by server policy, it
accepts one explicit `target` and a separate sensitive `program_args` list:

```json
{
  "workspace": "/src/project",
  "command": "run",
  "args": ["--config=dev"],
  "target": "//cmd/example",
  "program_args": ["--format=json", "input.txt"],
  "timeout_seconds": 300
}
```

The server owns the `--` boundary, omits run residue from BEP projections,
stores program-argument placeholders instead of values, and connects stdin to
null. This mode is intended for finite, non-interactive Unix programs. It does
not provide a PTY or background-service lifecycle. Bazel `run` always uses the
direct Bazel driver rather than Aspect CLI. See
[configuration](docs/configuration.md#enable-bazel-run) for the opt-in policy.

Failure results rank concrete root causes before aggregated action failures.
Equivalent fanout failures are represented once with `target: null` and a
`repetition_count`. Test and coverage commands use Bazel's
`--test_output=errors`. Failed-test logs are copied into private invocation
storage before they are exposed; test results report `test_log_available` or an
explicit `test_log_unavailable_reason`, never a synthetic or local failure-log
URI.

The `summary` view returns structured counts and bounded diagnostics. The `log`
and `test_log` views deliberately have the simpler logical shape below in every
result encoding:

```json
{
  "invocation_id": "019...",
  "view": "test_log",
  "items": [
    "[//foo:foo_test] assertion error: expected 3, received 4"
  ],
  "next_cursor": null,
  "truncated": false
}
```

The MCP shape has no stdout/stderr field or selector. The server automatically
normalizes, redacts, exactly deduplicates, filters, and sequences both captured
streams. Requested item limits are maxima; serialized byte packing may return
fewer items with an opaque cursor that resumes after the last emitted item.

Long calls can be returned as durable task handles when the MCP client declares
task support. With the default `auto` policy, the server discovers the
negotiated protocol and chooses synchronous execution, MCP `2025-11-25` legacy
tasks, or the `io.modelcontextprotocol/tasks` extension at runtime. This does
not add tools: task status, result, and cancellation are protocol methods.

## MCP protocol shape

MCP clients normally produce these messages on an agent's behalf, but the wire
shape is useful when integrating or debugging a host. `bazel-mcp` uses
newline-delimited JSON-RPC 2.0 over stdio; the examples below are formatted
across lines for readability and omit unrelated response fields.

| Flow | Client signal | Initial result | Final result |
| --- | --- | --- | --- |
| Synchronous | No task capability, or `sync_only` policy | `CallToolResult` | Same `tools/call` response |
| Legacy tasks | MCP `2025-11-25` plus `params.task` | Nested `result.task` | Separate `tasks/result` call |
| Tasks extension | Extension capability in request `_meta` | Flat `resultType: "task"` | Inline in terminal `tasks/get` |

Show representative JSON-RPC messages

### Synchronous call

A basic call is an ordinary MCP tool request:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "tools/call",
  "params": {
    "name": "bazel.run",
    "arguments": {
      "workspace": "/src/project",
      "command": "build",
      "args": ["//app:server"]
    }
  }
}
```

Without a compatible task opt-in, the request remains attached and returns a
normal `CallToolResult`. The default `toon` encoding places the bounded logical
result in one text content block:

```json
{
  "jsonrpc": "2.0",
  "id": 2,
  "result": {
    "content": [{
      "type": "text",
      "text": "invocation_id: \"019f...\"\nstate: succeeded\ncommand: build\nexit_code: 0\nheadline: Build succeeded\nmore_available: true"
    }],
    "isError": false
  }
}
```

That `invocation_id` can be passed to `bazel.inspect` or `bazel.cancel`.
Different result encodings change the content representation, not the logical
result or its byte budget.

### Legacy MCP tasks

A client negotiating MCP `2025-11-25` sees
`bazel.run.execution.taskSupport = "optional"`. It opts into detached execution
by adding `params.task` to the same tool call:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "bazel.run",
    "arguments": {
      "workspace": "/src/project",
      "command": "test",
      "args": ["//services/..."]
    },
    "task": {}
  }
}
```

The response contains a nested task. Its `taskId` is also the Bazel invocation
ID, and it is readable as soon as the handle is returned:

```json
{
  "jsonrpc": "2.0",
  "id": 3,
  "result": {
    "task": {
      "taskId": "019f...",
      "status": "working",
      "ttl": 86400000,
      "pollInterval": 2000
    },
    "_meta": {
      "io.modelcontextprotocol/related-task": {
        "taskId": "019f..."
      }
    }
  }
}
```

The client polls `tasks/get`, waits for the original `CallToolResult` with
`tasks/result`, lists durable handles with `tasks/list`, or requests
`tasks/cancel`.

### Tasks extension

Modern extension-aware clients can discover the capability with
`server/discover`:

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "method": "server/discover",
  "params": {}
}
```

```json
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "capabilities": {
      "extensions": {
        "io.modelcontextprotocol/tasks": {}
      }
    }
  }
}
```

After negotiating the extension's `2026-06-30` base protocol, each participating
request declares the extension in `_meta`. The initial result is flat rather
than nested:

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "method": "tools/call",
  "params": {
    "name": "bazel.run",
    "arguments": {
      "workspace": "/src/project",
      "command": "build",
      "args": ["//app:server"]
    },
    "_meta": {
      "io.modelcontextprotocol/clientCapabilities": {
        "extensions": {
          "io.modelcontextprotocol/tasks": {}
        }
      }
    }
  }
}
```

```json
{
  "jsonrpc": "2.0",
  "id": 5,
  "result": {
    "resultType": "task",
    "taskId": "019f...",
    "status": "working",
    "ttlMs": 86400000,
    "pollIntervalMs": 2000
  }
}
```

Here the client polls `tasks/get`; once terminal, that response contains the
original `CallToolResult` inline in its `result` field. The extension has no
`tasks/result` or `tasks/list` methods. Both task dialects are durable across a
server restart, while clients that declare neither continue to use the simpler
synchronous shape.

See [specification 003](specs/003-configurable-mcp-task-execution.md) for the
complete negotiation matrix, cancellation semantics, error codes, and pinned
protocol revisions.

## Security and local data

`bazel-mcp` executes Bazel with the permissions of the user who started the MCP
client. It does not make untrusted repositories safe; use a sandbox or isolated
account for untrusted source.

The server:

- invokes Bazel directly without shell evaluation or concatenated arguments;
- denies `clean`, `fetch`, `mobile-install`, `run`, `shutdown`, and `sync` by
  default;
- prevents requests from overriding server-owned BEP and output-root flags;
- filters the child-process environment and supports configurable regex
  redaction;
- stores raw output and BEP evidence only in the local cache, using private file
  permissions.

To restrict the server to one or more workspace roots, add a repeated
`--allow-root` argument to the MCP configuration:

```json
{
  "mcpServers": {
    "bazel": {
      "command": "bazel-mcp",
      "args": ["--allow-root", "/absolute/path/to/workspaces"]
    }
  }
}
```

See [SECURITY.md](SECURITY.md) for the security policy and threat-model
guidance.

## Configuration

The built-in defaults cover personal local use. For workspace restrictions,
retention limits, timeouts, command policy, result encoding, BEP transport,
optional Aspect CLI routing, custom redaction, or custom reducers, start with
[`examples/config.toml`](examples/config.toml).

BEP capture defaults to the private binary-file (`tail`) path so existing
remote BES and BuildBuddy configurations keep working. Set
`bep_transport = "fifo"` for the measured POSIX named-pipe optimization (with
automatic file-tail fallback on Windows or setup failure), or
`bep_transport = "bes"` to use bazel-mcp's loopback gRPC Build Event Service.
All three modes feed one ordered capture pipeline: raw frames are committed or
verified in the private evidence file before reduction observes them, and only
redacted projections may reach metadata, telemetry, or model-visible output.
See [BEP transport performance](docs/bep-transport-performance.md) for the
design tradeoffs, measured results, and reproduction commands.

Pass a configuration explicitly with `--config`, set `BAZEL_MCP_CONFIG`, or
place it at `$XDG_CONFIG_HOME/bazel-mcp/config.toml` (normally
`~/.config/bazel-mcp/config.toml`). Command-line options can also add allowed
roots or override the cache directory.

See the [configuration reference](docs/configuration.md) for all settings and
their defaults.

### Custom reducers

Built-in Rust reducers remain enabled by default. Operators can explicitly load
Starlark files to add diagnostics for custom Bazel rules or replace matching
built-in diagnostics while preserving the native invocation outcome and local
evidence:

```toml
[starlark]
files = ["reducers/custom_compiler.star"]
```

Reducer paths are relative to the configuration file. No reducer is discovered
from a Bazel workspace automatically. See the
[custom reducer guide](docs/custom-reducers.md) for the versioned API, selector
and override semantics, resource limits, trust model, and example.

### Result formats

TOON is the default model-visible result format. Select a different format in
the server configuration when required by an MCP host:

```toml
result_encoding = "toon"
```

| Value | Representation | When to use it |
| --- | --- | --- |
| `toon` | One token-oriented TOON text block. | Default; use for compact model context. |
| `text` | One compact JSON text block. | Use when a host or downstream tool expects JSON text. |
| `structured` | MCP `structuredContent` only. | Use with hosts verified to consume structured content without duplicating it into model context. |
| `both` | Structured content plus compact JSON text. | Compatibility mode for hosts that need both representations; it has the largest model-visible footprint. |

The setting applies to the server, not individual tool calls. Restart the MCP
server after changing it. All four choices carry the same logical result,
redaction, and byte ceilings; they only change its model-visible representation.

## Compatibility

| Component | Supporte

…

## Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [ewhauser](https://github.com/ewhauser)
- **Source:** [ewhauser/bazel-mcp](https://github.com/ewhauser/bazel-mcp)
- **License:** MIT

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** no
- **Shell / process execution:** no
- **Environment & secrets:** no
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/mcp-ewhauser-bazel-mcp
- Seller: https://agentstack.voostack.com/s/ewhauser
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
