secondbrain-mcp
by TomLous
README.md
# secondbrain-mcp
**One server. One connector. Every Claude client.**
There is no chat UI to build. A custom connector is configured **once, on your
account**, at claude.ai → Customize → Connectors → "+" → Add custom connector.
From then on it is available in Claude Desktop, claude.ai, Cowork and the iOS
and Android apps. Same server, same vault, same tools everywhere.
Claude connects to this remote MCP server from Anthropic's cloud, so the
server must be on the public internet with real TLS. Deploy target:
`https://brain.lous.info` on the shared Hetzner host. Platform services are
owned by **`hetzner-infra`**; this repo deploys only the app plus nginx /
observability drop-ins (same pattern as Padel Vibe / Unseen Servant).
## Routes
| route | who calls it | auth |
|---|---|---|
| `/mcp` | Claude, every client | GitHub OAuth (allow-listed login) |
| `/authorize`, `/token`, `/auth/*`, `/.well-known/*` | Claude OAuth handshake | public endpoints of the OAuth proxy |
| `/` | optional browser view | optional nginx basic auth |
| `/refresh` | GitHub webhook (optional) | `X-Webhook-Secret` |
| `/health` | Docker / CI healthcheck | none |
## Auth
| Env | Role |
|-----|------|
| `GITHUB_OAUTH_CLIENT_ID` / `GITHUB_OAUTH_CLIENT_SECRET` | GitHub OAuth App for the Claude connector |
| `GITHUB_ALLOWED_USERS` | Comma-separated GitHub logins (e.g. `TomLous`) — everyone else is rejected |
| `VAULT_GITHUB_PAT` | Fine-scoped PAT for HTTPS clone/pull/push of the vault (separate from OAuth) |
In Claude: add connector URL `https://brain.lous.info/mcp`, leave OAuth Client ID/Secret empty (Dynamic Client Registration), then Connect and sign in with GitHub as an allow-listed user.
## How it works
```
phone browser ─┐ ┌─ shared nginx :443 ─┐
├── https ───────────┤ ├── secondbrain-mcp:8000
Claude cloud ──┘ └─────────────────────┘ │
├─ /vault (git clone)
│ ▲
│ │ pull every 5 min
github.com/.../SecondBrain
▲
Obsidian Git │ auto-commit
laptop
```
The container never touches your laptop. GitHub is the sync bus.
## Deploy (Hetzner)
See [docs/hetzner-deploy.md](docs/hetzner-deploy.md).
```bash
# one-time on the server
sudo mkdir -p /opt/secondbrain/deploy/nginx
sudo chown -R deploy:deploy /opt/secondbrain
cp .env.example /opt/secondbrain/deploy/.env # fill secrets
chmod 600 /opt/secondbrain/deploy/.env
```
Push to `main` builds `ghcr.io/<owner>/secondbrain-mcp`, SSHs to Hetzner,
installs the nginx vhost into `/opt/hetzner/deploy/nginx/conf.d/`, syncs
observability drop-ins, and health-checks `https://brain.lous.info/health`.
GitHub Environment **`Hetzner`**: secret `HETZNER_SSH_KEY`, vars
`HETZNER_HOST`, `HETZNER_USER` (same as the other apps).
## Local
```bash
cp .env.example .env # set VAULT_GITHUB_PAT + GitHub OAuth vars
docker compose -f docker-compose.dev.yml up -d --build
curl -s localhost:8770/health
```
## Tools
Reads: `search_vault`, `get_note`, `neighbors`
Lenses: `next_actions`, `renewals`, `annual_cost`, `open_unknowns`,
`stale_facts`, `check_graph`
Writes: `capture`, `answer_unknown`
Every write is `pull --rebase → edit → check_graph.py → commit → push`, and
rolls back if the validator reports dangling links.
## Known limits
- `answer_unknown` does string surgery on frontmatter; fine for current vault
shapes, not exotic YAML.
- `renewals` needs notes with `renewal_date` populated.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues