Baton MCP Server
by euuuuuuan
README.md
<!-- Rewritten for the public snapshot: the development README was replaced with verified public documentation. Every quantity below is re-checked by the publication claims gate before a commit is created. -->
<div align="center">
# Baton
**A local-first orchestration platform for bounded agent work: a Swift macOS control daemon behind fail-closed permission gates, driven by a TypeScript orchestrator over MCP.**
[](LICENSE)
[](pnpm-lock.yaml)
[](pnpm-lock.yaml)
[](daemon/Package.swift)
[](package.json)
[](#development)
[](docs/TOOL_SURFACE.json)
**English** · [한국어](docs/i18n/README.ko_KR.md) · [日本語](docs/i18n/README.ja_JP.md) · [简体中文](docs/i18n/README.zh_CN.md) · [繁體中文](docs/i18n/README.zh_TW.md) · [Español](docs/i18n/README.es.md) · [Deutsch](docs/i18n/README.de_DE.md) · [Français](docs/i18n/README.fr_FR.md) · [Português (Brasil)](docs/i18n/README.pt_BR.md) · [Русский](docs/i18n/README.ru_RU.md) · [Italiano](docs/i18n/README.it_IT.md) · [Bahasa Indonesia](docs/i18n/README.id_ID.md) · [Türkçe](docs/i18n/README.tr_TR.md)
[Quickstart](#quickstart) · [Architecture](#architecture) · [Tool surface](docs/TOOL_SURFACE.json) · [ADR index](docs/adr/INDEX.md) · [Limitations](docs/LIMITATIONS.md) · [Credits](CREDITS.md)
</div>
> [!IMPORTANT]
> Apache-2.0 covers source code only. Media files are governed by the tiers and records in [CREDITS.md](CREDITS.md), which take precedence for those files. Media that is not listed there is unrecorded, not free to reuse.
## What this is
Baton is a two-layer platform for giving an agent a *bounded* amount of authority over one macOS workstation. A Swift daemon exposes machine capabilities as MCP tools behind a policy engine, an approval broker, and an append-only audit log; a TypeScript orchestrator plans work, keeps durable state, and drives those tools without ever being able to grant itself new authority. This is a working system built and run by one author on one machine — a platform plus command-line surfaces, **not** a packaged product: there is no installer, no notarized release, and no hosted service here, and the daemon layer runs on macOS only. Workstation-derived evaluations, private operational packs, and local audit captures are deliberately excluded from the public source, so this snapshot is the engineering substrate rather than the author's live configuration.
## Highlights
- **63 tools** in the generated tool-surface ledger ([`docs/TOOL_SURFACE.json`](docs/TOOL_SURFACE.json)) — a machine-checked public answer to "what can this thing actually do".
- **Fail-closed approval broker** (ADR-003): the approval fingerprint is `SHA-256(tool + "\n" + canonical JSON args)`, rows are inserted *pending*, `pending → granted` is one atomic transition, consumption is fingerprint-bound and single-use, and grants are TTL-bounded. A row's existence does not imply a grant.
- **234 JavaScript/TypeScript test and spec files**; the persona-behaviour test alone carries **112 assertions**.
- **16 TypeScript workspace packages** and **90 Swift files** across the daemon and a native menu-bar app (`swift-tools-version` 6.1 and 6.0, macOS 14+).
- **15 architecture decision records**, plus an ADR-index gate (`npm run audit:adr-index`) that fails when a repo-local ADR is missing from the index.
- **9 CI jobs**, split so TypeScript work runs on Linux runners while the Swift daemon and menu-bar targets run on macOS runners.
- **332 PNG files, every one first-party**: 33 runner packs × 10 frames, plus 2 contact sheets, all emitted by a single in-repo Pillow generator ([`apps/menubar/tools/gen-runners-v2.py`](apps/menubar/tools/gen-runners-v2.py)).
- **54 ported-file provenance rows** in [`docs/PROVENANCE.md`](docs/PROVENANCE.md), each recording source path, source commit, date, and an explicit divergence note.
- **A limitations document that is part of the design** ([`docs/LIMITATIONS.md`](docs/LIMITATIONS.md)) — for example, an unbundled daemon cannot hold a CoreLocation grant, so there is no location tool, permanently.
## Screenshots
This snapshot tracks no UI captures: the menu-bar app, companion surface, and CLI are not photographed here, and inventing captures for a README would be worse than omitting them. What it does track is the contact sheet the sprite generator emits, which is the visual output that actually lives in the repository:
<img src="apps/menubar/tools/runner-packs-v2/_contact_sheet.png" alt="Contact sheet of 33 menu-bar runner packs, 10 animation frames each, rendered by the in-repo generator" width="440">
Each row is one runner pack: 10 frames on a 44×36 menu-bar sprite spec, with a per-character palette, an automatic dark contour so the sprite stays legible on light *and* dark menu bars, a ground shadow, an eye blink, and a distinct motion cycle. Nothing here was downloaded — see [How this was built](#how-this-was-built).
## Use it
Baton is used from source. Three surfaces exist:
| Surface | What it is | Requirement |
|---|---|---|
| Control daemon | The Swift MCP daemon. Speaks MCP over stdio; every gated call passes policy → approval → audit. | macOS 14+, Swift 6.1 toolchain |
| Orchestrator CLI | The TypeScript entry point for plans, missions, durable state, and adapters. | Node ≥ 22.13, pnpm |
| Menu-bar app | Native macOS status and control surface. It discovers runner packs from disk, so adding a pack needs no Swift change. | macOS 14+, Swift 6.0 toolchain |
Configuration is environment-driven and documented in [`docs/CONFIG.md`](docs/CONFIG.md); [`.env.example`](.env.example) enumerates the knobs across 162 lines. Do not put tokens or workstation paths in the repository. Mutating and outward-facing actions stay gated at the capability boundary — and read [`docs/LIMITATIONS.md`](docs/LIMITATIONS.md) before trusting a timeout.
## Quickstart
Prerequisites: **Node.js ≥ 22.13** and **pnpm**. `package.json` pins `pnpm@10.33.0` for corepack; this snapshot's gate ran on Node.js `v26.3.1` with pnpm `11.6.0`. The Swift targets additionally need macOS 14+ and a Swift 6.1 toolchain.
```bash
git clone <your-fork-url> baton
cd baton
corepack enable
pnpm install --frozen-lockfile
pnpm -r build # TypeScript packages
pnpm -r test && npm run test:scripts # 234 test/spec files
npm run audit:adr-index # index must cover every repo-local ADR
```
Those commands are exactly what the publication gate runs for this snapshot. The Swift targets are separate and are **not** exercised by that gate: `npm run build:daemon`, `npm run build:menubar`, and `npm run checks` each require the toolchain named above.
## Architecture
Two layers, one boundary. Authority lives in Swift, planning lives in TypeScript, the only path between them is MCP, and the only way authority widens is an owner grant.
```mermaid
flowchart TB
subgraph TS["TypeScript orchestrator — 16 workspace packages"]
PL[Plans and missions] --> OC[Orchestrator core]
OC --> AD[Adapters: chat, web, editor, phone, voice]
OC --> DU[(Durable state / SQLite)]
OC --> PM[permission.ts — may request, never grant]
end
subgraph SW["Swift layer — 90 Swift files"]
GD[Gated dispatch watchdog] --> PE[Policy engine + recursive redaction]
PE --> AB[Approval broker: fingerprint, TTL, single-use]
AB --> TL[63 documented tools]
TL --> AU[(Append-only audit log)]
TL --> RD[Per-runner SIGTERM to SIGKILL deadline]
end
PM -->|MCP over stdio| GD
AB -->|grant required| OW([Owner: Touch ID / approval surface])
```
Load-bearing properties:
- **Authority is not delegable.** `orchestrator/packages/core/src/permission.ts` can *ask*; only the Swift side decides. Orchestration cannot widen its own capability surface — which is why the tool ledger is generated rather than hand-maintained.
- **Fingerprint canonicality is a correctness dependency.** Approval fingerprints hash canonical JSON with sorted keys, so the Swift check harness pins the JSON encoder's key ordering. Without that pin, an encoding change would silently invalidate every stored fingerprint.
- **Two layered timeouts, deliberately.** A per-runner deadline (default 30 s) is what actually reclaims a hung child process; the dispatch watchdog (default 180 s) is a backstop, because Swift cannot preempt a thread blocked on a child. The invariant `runner deadline < dispatch watchdog` is what makes the design sound, and the residual leak — side effects that already happened before a timeout was recorded — is written down instead of hidden.
- **Hermetic Swift.** The daemon has zero external SwiftPM dependencies, and the menu-bar core target is pure Foundation so its models, REST client, and icon mapping are unit-testable without SwiftUI or AppKit.
## Project layout
```text
orchestrator/packages/ 16 TypeScript workspace packages
core/ orchestrator, agent loop, permission, memory, tools, security
cli/ mcp/ bus/ command surfaces, MCP wiring, event bus
durable/ sqlite/ durable state and storage
graph/ observe/ verify/ retrieval graph, telemetry, judge and verification
discord/ web/ phone/ voice-call/ companion/ env/ net-guard/ adapters and guards
daemon/ Swift MCP daemon: tools, safety stack, persistence, audit
apps/menubar/ native macOS menu-bar app + sprite generator and packs
apps/companion-extension/ browser companion surface
integrations/ editor integration and config sync
scripts/ build and install scripts, audit harness, CI helpers
docs/adr/ 15 architecture decision records + generated INDEX.md
docs/ TOOL_SURFACE.json, PROVENANCE.md, LIMITATIONS.md, runbooks
docs/i18n/ translated README set
profiles/ MCP and profile presets
eval/ prompt-evaluation config
```
## How this was built
This repository is an AI-augmented build, and the interesting part is not that a model wrote code — it is where the model was allowed to be authoritative and where it was not.
**Deterministic scanning first, model review second.** [`scripts/audit/harness.sh`](scripts/audit/harness.sh) runs typecheck, ESLint, Prettier, `knip` dead-code and unused-export detection, and `madge` circular-dependency analysis across all 16 packages — and says so in its own header: deterministic code does the mechanical scanning, the model only reviews the summary. It is report-only by default with a `--strict` mode for CI, so the ramp from advisory to blocking is an explicit decision rather than an accident.
**A relay, not a single agent.** Work was cut into bounded lanes: one agent implemented or audited a lane, a second cross-checked the behaviour *and* the evidence, and the author adjudicated. Handoff documents from those rounds ship in `docs/`, so the reasoning is auditable rather than folklore.
**In-repo AI review that cannot merge anything.** [`scripts/ci/ai-review.mjs`](scripts/ci/ai-review.mjs) reuses Baton's own primitives: a cross-encoder reranker picks the diff hunks most worth reviewing, a multi-axis judge pass runs over those hunks through a local model gateway, and the result is posted as a markdown review. It is advisory by construction — it never fails the build and never auto-merges.
**Self-healing that stays a draft.** [`scripts/ci/self-heal.mjs`](scripts/ci/self-heal.mjs) reacts to a red check by asking a local model for a strict unified diff, applying it on a scratch branch, re-running the failing command, and iterating up to a bounded number of attempts. Its default mode changes nothing at all; with `--apply` and a green result it opens a **draft** PR. It never pushes to `main` and never auto-merges. It also treats token thrift as a safety property: the model sees only the ~40 most error-relevant log lines plus the working-tree diff, never raw logs.
**Mutation testing as the work generator.** [`stryker.config.json`](stryker.config.json) scopes mutation to `core/src`, runs incrementally, and writes a JSON report. The loop is that a surviving mutant becomes the ticket, and a proposed killing test is accepted only once a runner proves it actually kills that mutant. Passing tests are not treated as evidence on their own.
**Provenance gating on code, not just assets.** Every file ported in from a sibling project carries a `PORTED-FROM` header and a row in [`docs/PROVENANCE.md`](docs/PROVENANCE.md) — 54 rows recording source path, source commit, date, and a divergence note. Several of those notes are bugs found *because* the port forced a line-by-line comparison; one ancestor's integer accessor truncated `3.9` to `3`, which had been letting "must be a non-negative integer" tool guards silently accept fractional input.
**Self-generated media instead of downloads.** All 332 PNGs come from [`apps/menubar/tools/gen-runners-v2.py`](apps/menubar/tools/gen-runners-v2.py), a supersampled Pillow renderer with one draw function per character, so a licence question about menu-bar art never arises. The generator ships alongside the packs, so a fork can regenerate rather than trust the binaries — and [CREDITS.md](CREDITS.md) records a tier and a digest for the shipped media regardless.
**A fail-closed publication gate.** This public snapshot is produced by an out-of-repo sanitizing pipeline that never writes to the private source. It archives a ref, applies exclusions, applies publication-only documentation, then refuses to commit unless *every* gate is green: secret scanning, credential-shaped-file detection, absolute-home-path and email-shape checks, a fixed-string employer/client wordlist expanded over delimiter variants, exclusion verification, build, tests — and a claims gate that re-runs the command behind each number in this README. A false README claim was the dominant defect class in the audit that produced this document, so these numbers are executable rather than aspirational.
## Development
```bash
pnpm -r build # build all TypeScript packages
pnpm -r test # package test suites (Vitest)
npm run test:scripts # node:test suites for scripts/lib
npm run audit:adr-index # ADR index completeness gate
npm run typecheck # tsc -b across the workspace
npm run lint # eslint --max-warnings 0
npm run format:check # prettier --check
bash scripts/audit/harness.sh # full report-only audit
bash scripts/audit/harness.sh --strict # same, but any finding exits nonzero
```
macOS with a Swift toolchain additionally enables `npm run build:daemon`, `npm run build:menubar`, `npm run checks` (the Swift check harness), and `npm run test:menubar`.
When changing behaviour: add or extend a deterministic test, keep the ADR index in sync if you add an ADR, and update [CREDITS.md](CREDITS.md) if you add or replace any media file.
## Localization
## Credits
See [CREDITS.md](CREDITS.md) for the four-tier media register, the fail-closed rule for unlisted media, and the SHA-256 records. Its asset-specific terms take precedence over the code licence for any image or media file. Third-party *software* dependencies retain their upstream licences; the package manifests and `pnpm-lock.yaml` are the authoritative dependency inventory.
## License
Source code is licensed under the [Apache License 2.0](LICENSE); modification notices are recorded in [NOTICE](NOTICE). Media files are not licensed by Apache-2.0 and remain governed by [CREDITS.md](CREDITS.md).
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues