Skip to main content
Glama
README.md
# brain-mcp

**Your second brain as a private git repo, wired into claude.ai.**

A remote [MCP](https://modelcontextprotocol.io) server + web cockpit for a
personal "second brain" made of plain markdown files in a **private GitHub
repo**. Every new Claude chat can read your context (who you are, what you're
working on, what you decided) and write back to it — through guarded,
intent-shaped tools. A single-page web app renders the same brain as a live
knowledge graph.

Runs entirely on the **Cloudflare Workers free plan**. No database, no vector
store, no server to babysit: GitHub is the storage, git history is the backup,
markdown is the format you'll still be able to read in 30 years.

## What you get

- **MCP connector for claude.ai** — Streamable HTTP at `/mcp`, OAuth 2.1 + PKCE
  handshake handled by `@cloudflare/workers-oauth-provider`, GitHub login
  restricted to a single allowed account (yours).
- **Read tools** (tiered, token-efficient): `list_brain` (file map + one-line
  summaries), `get_brain_file`, `search_brain`, `get_graph`, `brain_health`.
- **Write tools** (intent-shaped — no raw "write anywhere"):
  - `add_to_inbox(filename, content)` — stage into `inbox/`, never overwrites.
  - `append_decision(text)` — append a dated entry to `decisions/YYYY-MM.md` (append-only).
  - `update_now(content)` — replace `now.md`, bump the `updated:` date.
  - `upsert_project(name, content)` — create/update `projects/<slug>.md`.
  - `upsert_file(path, content)` — general fallback, restricted to known folders.
  - `delete_inbox_item(filename)` — the only delete, restricted to `inbox/`.
- **Guards on every write**: refuses content that looks like an API key, token
  or private key; refuses paths outside the brain's folders; `decisions/` is
  append-only; deletes are restricted to `inbox/`.
- **Web cockpit** at the Worker root (GitHub-gated session): force-directed
  knowledge graph (canvas + d3-force), file reader with wiki-links and
  backlinks, inline editor, inbox triage, health report (broken links, stale
  files, orphans), decision log, and a temporal replay of the brain's growth
  from git history. The UI language is currently French.
- **Two themes, one anatomy** ("Atelier") — stacked surfaces rather than
  rules: the room, the panel, the card, the control, each lifted by a soft
  shadow and an achromatic glaze instead of a heavier border. Generous radii,
  Inter for the human voice and JetBrains Mono for the machine voice (both
  self-hosted), and a neutral palette where color is semantic and rare: blue
  for selection, green for healthy, amber for attention, red for urgency.
  Dark mode is the default; light flips the same anatomy onto pale paper. A
  segmented switch (light | auto | dark) lives on the graph page and in the
  sidebar footer; **auto follows the actual sunset/sunrise** for your location
  (NOAA formula, no API). All deltas live in CSS tokens and `GRAPH_SKINS` —
  see `design/guidelines.md`. Set your coordinates in `app/src/theme.ts` and
  in the inline script of `app/index.html` (default: Paris).
- **PWA share target** — share a link/text from your phone straight into `inbox/`.
- **Phone capture** at `/capture` — a standalone box for the idea you'd
  otherwise lose in a notes app. Installable as its own home-screen icon
  (separate manifest, `start_url: /capture`), it renders before the graph
  loads and works offline: the text is queued in `localStorage` *before* the
  network is touched, so a dead tunnel or an expired session costs nothing —
  the queue drains on the next load and on `online`. Captures land appended to
  `inbox/idees-YYYY-MM-DD.md`, one dated line each, no folder and no
  classification: sorting is a separate, deliberate session. Optional pre-tag
  (to-do / réflexion / projet / question) while the context is still in mind.

## Architecture

```
claude.ai ──(OAuth 2.1 + PKCE)──► Worker (its own OAuth server)
                                    │  GitHub login = identity gate (ALLOWED_LOGIN)
                                    ▼
                              /mcp  Durable Object (MCP tools)
Browser ──(cookie session)──► /api + static SPA (Vite + React)
                                    │
                                    ▼
                     GitHub contents API — your private brain repo
                     (fine-grained PAT, server-side only)
```

Two credentials, two jobs, never mixed:

- The **GitHub OAuth App** only proves *who you are*. Its token is never used
  to touch the repo.
- A **fine-grained PAT** (`GITHUB_BRAIN_TOKEN`, scoped to the brain repo only,
  Contents read/write) does all repo access, server-side. claude.ai never sees it.

## The brain repo

Create a **separate, private** repo (e.g. `your-brain-repo`) holding plain markdown.
The layout the tools expect:

```
README.md            # how your brain works (also read by Claude)
identity.md          # who you are — stable base context
preferences.md       # how you like to work
now.md               # current focus + deadlines (updated often)
context/             # long-lived context (company, school, protocols…)
people/              # one file per person
projects/            # one file per project
domains/             # knowledge areas
decisions/YYYY-MM.md # append-only decision log
personal/            # whatever is yours
inbox/               # raw captures, triaged later
```

Files carry a small YAML frontmatter the graph and health report understand
(all fields optional):

```markdown
---
id: my-project
type: project
tags: [dev]
status: active
summary_l0: "One-line summary shown in list_brain."
updated: 2026-07-07
---

# My project
Links to other files as [[people/jane-doe]] wiki-links → graph edges.
```

## Setup

Prereqs: Node 18+, a Cloudflare account (free), a GitHub account.

### 1. Install

```bash
npm install
npm --prefix app install
```

### 2. Configure your identity

Edit the `vars` block in `wrangler.jsonc`:

| Var | What it is |
|---|---|
| `BRAIN_OWNER` | GitHub login that owns the brain repo |
| `BRAIN_REPO` | Brain repo name (e.g. `your-brain-repo`) |
| `BRAIN_BRANCH` | Branch to read/write (`main`) |
| `ALLOWED_LOGIN` | The **only** GitHub login allowed to authenticate |
| `TIMEZONE` | IANA timezone for date stamps (e.g. `Europe/Paris`) |
| `CENTER_PATH` | Optional: the graph's central "me" node (e.g. `people/me.md`) |

### 3. Create a GitHub OAuth App (login / identity)

GitHub → Settings → Developer settings → **OAuth Apps** → **New OAuth App**.

- **Homepage URL:** your future worker URL, e.g. `https://brain-mcp.<your-subdomain>.workers.dev`
- **Authorization callback URL:** the **Worker's own callback** —
  `https://brain-mcp.<your-subdomain>.workers.dev/callback`

> Architecture note: the Worker is its *own* OAuth 2.1 server for claude.ai;
> GitHub is only the upstream login. So GitHub redirects back to the
> **Worker's** `/callback`, and that is the only callback GitHub needs.
> claude.ai registers its own redirect URIs with the Worker automatically via
> dynamic client registration (`/register`). You won't know the exact
> workers.dev subdomain until the first deploy — deploy once with a
> placeholder, then come back and set the real URL.

Copy the **Client ID** and generate a **Client secret**.

### 4. Create the fine-grained PAT (repo read/write)

GitHub → Settings → Developer settings → **Fine-grained tokens** → **Generate new token**.

- **Repository access:** *Only select repositories* → your brain repo
- **Permissions:** `Contents` = **Read and write** (`Metadata` = Read is added automatically)

### 5. Cloudflare: KV + secrets

```bash
npx wrangler login
npx wrangler kv namespace create OAUTH_KV
# paste the printed id into wrangler.jsonc (REPLACE_WITH_KV_ID)

npx wrangler secret put GITHUB_CLIENT_ID
npx wrangler secret put GITHUB_CLIENT_SECRET
npx wrangler secret put GITHUB_BRAIN_TOKEN
```

### 6. Build the app & deploy

```bash
npm --prefix app run build
npx wrangler deploy
```

Your MCP endpoint is the printed URL **+ `/mcp`**. If your OAuth App used a
placeholder host, update its Homepage/callback URLs to the real one now.

> `app/public/` ships **placeholder** icons (`favicon.svg`, `icon-192.png`,
> `icon-512.png`): a neutral little graph, no branding. They are there because
> a PWA without icons doesn't install, which would break the phone capture and
> the share target. Replace the three files with your own and rebuild — the
> filenames are referenced from `app/index.html` and both manifests, so
> keeping the names means nothing else to change.

### 7. Connect

- **claude.ai** → Settings → **Connectors** → **Add custom connector** → paste
  the `/mcp` URL → Connect → log in with GitHub. Anyone who isn't
  `ALLOWED_LOGIN` gets a 403.
- **Browser cockpit** → open the worker URL → "Se connecter avec GitHub".
- **Phone** → open the worker URL in mobile Chrome/Safari, log in once (the
  session cookie lasts 30 days), then use the browser's *Install* / *Add to
  home screen*. Long-pressing the installed icon exposes the **Nouvelle idée**
  shortcut; installing from `/capture` directly gives you a second icon that
  opens straight into the capture box.

### 8. Smoke test

1. In a new chat: *"list my brain"* → file map with summaries.
2. *"read my now.md"* → file content.
3. *"add a test decision: validated the MCP write path"* → appends to
   `decisions/YYYY-MM.md`; confirm the commit on GitHub.
4. Open `/capture`, type anything, send → a new line in
   `inbox/idees-<today>.md` on GitHub. Turn airplane mode on and send another
   one: it stays queued and leaves as soon as the network is back.

### Triaging what you capture

Capture is deliberately dumb, so `inbox/` fills up with day-files. Emptying
them is a separate session, and Claude does the work through the same MCP
tools: *"read `inbox/idees-2026-08-15.md`, ask me about anything ambiguous,
then file each line where it belongs and delete the inbox file"*. Nothing
classifies on its own — an idea filed by guesswork is worse than an idea left
in the inbox.

## Local development

```bash
cp .dev.vars.example .dev.vars   # fill in the three secrets
npx wrangler dev
```

UI-only work doesn't need any of that: `npm --prefix app run dev` then open
`/mock.html` — the API is stubbed with plausible fake data (`app/src/dev-mock.ts`).

## Security model

See [SECURITY.md](SECURITY.md) for the full threat model. Short version: the
worker URL is public but serves nothing without a valid GitHub-verified
session; a single `ALLOWED_LOGIN` gate covers both surfaces (MCP + browser);
the repo PAT lives only in Worker secrets; every write goes through secret
detection and folder guards; rendered markdown is sanitized against stored XSS.
Disaster recovery (lost Cloudflare account, etc.) is covered in
[RECOVERY.md](RECOVERY.md) — worst case degrades to a folder of readable
markdown, never to nothing.

## License

[MIT](LICENSE).

Maintenance

ActivityActive
ResponsivenessNo issues