Baton MCP Server
Enables bounded agent authority over a macOS workstation via 63 MCP tools with policy, approval, and audit.
README
<!-- 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.
English · 한국어 · 日本語 · 简体中文 · 繁體中文 · Español · Deutsch · Français · Português (Brasil) · Русский · Italiano · Bahasa Indonesia · Türkçe
Quickstart · Architecture · Tool surface · ADR index · Limitations · Credits
</div>
[!IMPORTANT] Apache-2.0 covers source code only. Media files are governed by the tiers and records in 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) — 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 → grantedis 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-version6.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). - 54 ported-file provenance rows in
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) — 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.
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; .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 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.
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.
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.tscan 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 watchdogis 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
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 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 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 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 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 — 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, 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 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
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 if you add or replace any media file.
Localization
Credits
See 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; modification notices are recorded in NOTICE. Media files are not licensed by Apache-2.0 and remain governed by CREDITS.md.
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。