# Braid MCP server — the braid for Claude (and any MCP client)

`mcp/braid-mcp.mjs` speaks the Model Context Protocol over stdio and exposes
the entire Braid braid as a tool suite. The names deliberately parody the
GitHub MCP server so an agent that knows how to work a GitHub repository can
work a Braid repository without relearning anything — while the descriptions
teach it what is different (branchless intent layers instead of pull requests,
per-operation convergence instead of merges, signed check receipts instead of
an Actions fleet).

## Setup

Inside this repository nothing is needed: `.mcp.json` registers the server for
Claude Code, while the trusted-project `.codex/config.toml` registers it for
the Codex app, CLI, and IDE extension. Restart the client after first trusting
or opening the project so it reloads MCP configuration.

Anywhere else:

```bash
claude mcp add braid -- node /path/to/braid/mcp/braid-mcp.mjs
```

By default the tools talk to the live deployment (`https://braid-forge.pages.dev`).
Point them elsewhere with the same environment the CLI uses: `BRAID_URL`,
`BRAID_ORIGIN`, `BRAID_HOME`.

## Signing in

The server reuses the CLI credential store (`~/.braid/credentials.json`), so it
is signed in exactly when `braid whoami` is. Three ways in:

1. `braid link` in a terminal (device-code link to your browser account), or
2. the `braid_connect` → `braid_connect_complete` tools — the agent shows you a
   code, you enter it at `https://braid-forge.pages.dev/#/link`, or
3. `braid_register` for a brand-new account with a software passkey.

There are no tokens to paste anywhere: the hosted API is passkey-and-session
only, and repository writes are signed with this machine's registered P-256
device key.

Clients that defer individual tool schemas can load `braid_core` for identity,
repository discovery, charts, files, Beam, history, events, and local checks,
or `braid_write` for pushes, intent lifecycle, review, convergence, checks, and
chart generation. Each is a real dispatcher with an `action` enum and ordinary
tool arguments, so `select:braid_core` / `select:braid_write` replaces guessing
dozens of names up front.

## The suite, GitHub-to-Braid

| GitHub habit | Braid tool(s) |
|---|---|
| `get_me` | `braid_whoami` |
| `gh auth login` | `braid_connect`, `braid_connect_complete`, `braid_register` |
| list/search/create repos | `braid_list_repos`, `braid_search_repos`, `braid_get_repo`, `braid_create_repo`, `braid_list_orgs`, `braid_create_org` |
| `get_file_contents` | `braid_get_file`, `braid_list_files` |
| `push_files` / `git push` | `braid_push_files`, `braid_push_directory` |
| `list_commits` / branch head | `braid_history`, `braid_get_head`, `braid_list_events` |
| `search_code` | `braid_scope_beam` — token-true lookup from the push/import-time symbol index |
| issues & comments | `braid_list_issues`, `braid_get_issue`, `braid_create_issue`, `braid_update_issue`, `braid_add_comment`, `braid_create_label`, `braid_create_milestone`, `braid_set_milestone_state` |
| pull requests | **intent layers**: `braid_list_intents`, `braid_get_intent`, `braid_create_intent`, `braid_set_intent_state` |
| reviews & merging | `braid_request_reviewers`, `braid_accept_reviewership`, `braid_submit_review`, `braid_decide_operation` (convergence is per-operation — there is no merge button), `braid_converge_intent` |
| judging your own work | `braid_accept_own_operation` — the signed home for adopting work you directed yourself; credited, and deliberately conferring no governance standing |
| governance | `braid_get_charter`, `braid_list_polls`, `braid_get_roster` — **all read-only**. Adoption, amendment, opening a poll, casting a stance, admitting to the roster: no tool exists for any of them by design, and none should be added |
| who has a voice | `braid_get_roster` (admitted by poll) — not `braid_list_members`, which is write access granted by an admin |
| equity & settlement | `braid_list_evidence` (derived contribution events, recomputed by every replayer), `braid_get_settlement` (episodes, allocation decisions, appeals) — **read-only**: an allocation decision is a conflict-free juror's act |
| checks / Actions | `braid_list_checks`, `braid_run_check` (local digest-pinned isolated execution), `braid_submit_check_receipt` (P-256-signed receipts) |
| tags & releases | `braid_list_releases`, `braid_create_tag`, `braid_create_release`, `braid_publish_release` |
| stars, watching, notifications | `braid_star`, `braid_watch`, `braid_list_notifications`, `braid_mark_notification_read` |
| collaborators | `braid_list_members`, `braid_invite_member`, `braid_accept_invite`, `braid_set_member_role`, `braid_remove_member` |
| fork (in reverse) | `braid_import_git`, `braid_import_status`, `braid_import_finalize`, `braid_import_cancel` — Git is import-only |

## Orienting without overflowing the context

Start with `braid_get_chart`. When a repository has no chart yet, the response
still includes `directorySummary` plus `repositoryOverview`: exact directory
counts, notable manifests and guides, likely entry points, and dominant file
types. This deterministic fallback requires no client sampling. Use one of the
directory paths as `pathPrefix` for `braid_generate_chart`:
the server asks the connected MCP client's model through
`sampling/createMessage`, binds the exact input CIDs and returned model into a
receipt, and writes the signed navigation map. Braid holds no model key, and
the map is explicitly excluded from contribution evidence. The read and
sampling phase is deadline-bounded, reports each sampled file as progress, and
honors cancellation right up to the moment the signed write begins (see the
limits below). A client without
sampling can still use that prefix with `braid_list_files` or
`braid_scope_beam` and author a chart manually.

Repository list and search results include a materialized `fileCount` as well
as `lastEventAt` (`null` when no event has ever been recorded), so an active,
populated repository can be distinguished from an empty, similarly named shell
without replaying either one. A count is `null` when the hosted service cannot
honestly derive it, including ciphertext-only private repository state.

`braid_list_files` is bounded to 100 entries by default (500 maximum) and to a
64 KiB serialized response. Whichever ceiling is reached first ends the page.
It returns `total`, `hasMore`, `responseBytes`, `byteLimit`, and an opaque
`nextCursor`; pass that cursor back to continue. Cursors are tied to the
original `pathPrefix` and `glob` and advance
after the last path rather than by array offset, so earlier insertions do not
duplicate the next page. Prefixes are normalized repository paths and match an
exact file or a directory boundary — `src` includes `src/main.js`, never
`src-old/main.js`. Globs match full repository paths: `*` and `?` stay within a
path segment while `**` crosses directories (for example `src/**/*.test.js`).

Tool values are returned in MCP `structuredContent`; the text content block is
only a short compatibility summary. This avoids JSON encoded inside JSON-RPC
strings and keeps truncation from turning an otherwise valid tool result into
unparseable embedded JSON.

### Timeouts and limits

These are hard server-side bounds unless a smaller caller-supplied limit is
accepted by the individual tool:

| Surface | Default | Hard limit / behavior |
|---|---:|---|
| `braid_list_files` entries | 100 per page | 500 requested entries and 64 KiB serialized response per page; the first ceiling reached wins |
| `braid_list_files` glob | — | 1,024 characters |
| `braid_list_files` cursor | — | 32 KiB encoded cursor |
| `braid_scope_beam` | 50 matching files | 500 matching files per call; index requests are paged 25 files at a time, and a capped result returns `nextCursor` — pass it back as `cursor` to continue past the cap (a resumed cursor dies with `SYMBOL_INDEX_CHANGED` if the index advances) |
| `braid_scope_beam` lines per file | first 100 line numbers | `count` keeps the true per-file total; `linesTruncated` flags the gap. `excerptLines` (1–40) returns the matched line TEXT with a `roleHeuristic` (definition/reference guess) and re-derives the full line list past the storage cap, at one content read per returned file |
| `braid_scope_beam` batching | one symbol | `symbols` beams up to 8 identifiers sequentially in one call; `excludePrefixes` drops up to 16 subtrees with an honest `excludedFiles` count |
| `braid_scope_beam` execution | 30 seconds (override with `timeoutSeconds`) | At the deadline the active index request is aborted with `BEAM_TIMEOUT`; completed pages remain in `error.details.partialFiles`, inlined into the error text for clients that render only text |
| `braid_generate_chart` | 25 text files | 50 files; at most 32 KiB from each file and 768 KiB total model input |
| `braid_generate_chart` read/sample phase | 120 seconds | At 120 seconds the active read or sampling request is aborted with `CHART_READ_TIMEOUT`; no chart is written |
| chart sampling request | — | 90-second server-to-client `sampling/createMessage` timeout |
| replay-backed reads (`braid_list_files`, `braid_get_file`, `braid_get_brief`, the read phase of `braid_push_files`) | 120 seconds | At 120 seconds the replay is aborted with `REPLAY_TIMEOUT`, which names the index-backed tools to prefer; nothing is submitted |
| `braid_get_chart` directory fallback | two directory levels | 100 directory entries |
| brief entry excerpts | — | Six regions and 4,000 characters |
| file create/update | — | 8 MiB resulting content per file |
| patch-bearing push | — | 400 resulting operations in one atomic signed command |
| history and event pages | 50 entries | 500 entries |
| `braid_run_check` execution | 600 seconds | Caller may select 1–1,800 seconds |
| `braid_run_check` workspace | — | 10,000 files and 512 MiB total content |
| `braid_run_check` captured output | — | 2 MiB |

Cancellation applies to read-only tools, including Scope Beam. An MCP
`notifications/cancelled` request aborts the active HTTP/index operation and
returns `REQUEST_CANCELLED`, including any completed Beam pages. Accepted
sequence-fenced mutations deliberately ignore cancellation and drain to a
single success or failure so a write cannot be stranded halfway through.
`braid_generate_chart` is the one fenced write with a long read: cancellation
is honored through its listing, content-fetch, and sampling phase — checked
one final time immediately before its write begins — and ignored from the
moment the write is submitted, so a cancelled chart run either writes nothing
or writes completely.

Tools backed by a full command-log replay — `braid_list_files`,
`braid_get_file`, `braid_get_brief`, and the read phase of `braid_push_files`
— report replay progress (pages, commands, and intent objects fetched, plus
brief entries enriched) through `notifications/progress` when the caller
supplies a `progressToken`. `braid_generate_chart` reports each sampled file
the same way. Rate-limit pacing inside the shared API client is also surfaced
on the active calls' progress tokens as a `rate-limit-pacing` message naming
the wait, so a paced call reads as waiting rather than hung; the CLI keeps its
stderr notes.

The checked-in Codex project configuration gives MCP tool calls a 60-second
client timeout and server startup a 10-second timeout. Those are client
configuration bounds in `.codex/config.toml`, separate from the server-side
bounds above; other MCP clients may configure different outer timeouts.

`braid_scope_beam` never replays and downloads the repository at query time.
Public pushes and imports update a token-true symbol index atomically with the
signed event; existing repositories are rebuilt once after migration. Lookups
page in batches of 25 and have a hard 30-second aggregate deadline. When the
MCP caller supplies a `progressToken`, each
completed page is emitted immediately through `notifications/progress`; a
later timeout or cancellation also returns those pages as `partialFiles`.

`braid_run_check` materializes an exact, sequence-stable repository snapshot
into a temporary read-only mount and executes direct argv in an image that is
already present locally and pinned by SHA-256 digest. It performs no implicit
pull, gives the container no network, capabilities, writable root, credentials,
or Docker socket, and bounds files, bytes, output, CPU, memory, processes, and
time: 10,000 files, 512 MiB of content, 2 MiB of captured output, and a
600-second default execution timeout configurable from 1 to 1,800 seconds.
The result includes logs and a content-bound attestation; hosted Actions
continue to use the stricter Firecracker production path.

## The one workflow worth spelling out

Where GitHub says branch → commit → PR → merge, Braid says:

1. `braid_push_files` or `braid_push_directory` — content becomes signed
   semantic operations; note the returned `operationIds`.
2. `braid_create_intent` with those `operationIds` — a reviewable layer, born
   as a draft.
3. `braid_set_intent_state` → `ready`.
4. Reviewers (`braid_request_reviewers`) accept the request with
   `braid_accept_reviewership`, respond with `braid_submit_review`, and decide
   each operation with `braid_decide_operation`.
5. `braid_converge_intent` settles the layer once its checks pass —
   irreversibly, and only by the author or a requested reviewer.

For a small text edit, `braid_push_files` also accepts
`patches: [{path, oldString, newString}]`. The old text must occur exactly once
in the current UTF-8 file. Patch commands are pinned to the history sequence
that was replayed; if the repository advances first, the tool returns
`PATCH_CONFLICT` instead of retrying against unseen content. Re-read and retry
the patch in that case. A patch-containing call is capped at 400 resulting
operations so the whole call fits in one signed command and cannot partially
commit; split a larger edit into separate calls.

If a caller loses the original push response, `braid_history` and
`braid_list_events` recover the `operationIds` from each public signed intent
object, so the review workflow is not permanently stranded.

Anyone may add themselves as a reviewer, so the handshake in step 4 is what
makes a review read as *independent*: requested by someone else, and accepted.
Independence is stamped onto each review and decision as it is made, so
accepting afterwards never upgrades an earlier judgment. Work you directed
yourself has its own honest record — `braid_accept_own_operation` — which is
fully credited and deliberately confers no governance standing, because
steering is earned when someone else accepts your work.

## What this suite deliberately cannot do

Agents hold their person's keys — the CLI credential store *is* those keys — so
the only thing standing between an instrument and its person's franchise is
what the tool surface refuses to offer. `docs/EQUITY_LAYER.md` §6.1 names the
floor (`HUMAN_ONLY_FLOOR` in `braid/commons-core.js`): charter adoption and
amendment, charter acceptance, poll stances, allocation decisions, payout
address binding, ML preference. Opening a poll and admitting to the roster are
governance acts too.

There is no tool for any of them, and adding one is a policy violation rather
than a feature. `tests/mcp-server.test.mjs` enforces this two ways: the
governance-voice tool names must be absent from `tools/list`, and no tool in
`mcp/tools.mjs` may issue a `governance.*`, `identity.address.*` or `value.*`
workflow command at all — so the rule cannot be routed around by giving the
tool an innocuous name.

This is the weakest of the three enforcement layers by design (§6.1 calls it
"courtesy, worth having, worth nothing alone"); the real teeth are the key
class recorded at replay. It is still the layer that decides what an agent
reaches for by default. Read the record with the tools above, draft the
argument, and hand the act back to your person.

## Testing

`tests/mcp-server.test.mjs` boots the real hosted API in a child process and
drives the server over real stdio through the full journey — the same
composition `cli/selftest.mjs` proves for the CLI.
