An AI assistant can discuss your plans. It cannot maintain the system where those plans live.
This closes that gap. It is a self-hosted MCP server that gives an AI app — Claude, ChatGPT, or any MCP client with remote connectors — read and write access to exactly one GitHub repository: your Markdown notes, typically an Obsidian vault.
The assistant reads the current state of your notes, proposes a change, and writes it back. Every change is an ordinary Git commit — reviewable, revertible, and readable without any of the tools involved.
You run it yourself. There is no hosted service and no account: you deploy the server, point it at your own repository, and connect your AI app to it.
Background article: Beyond Chat History: Building an AI-Native Second Brain with Obsidian, GitHub, and MCP
Once connected, you talk to your assistant normally and it works on the real files:
- "What did I decide about pricing last month?" — it searches the vault and answers from your actual notes, not from chat history.
- "Add today's outcome to my daily note." — it reads the note, edits it, and commits the change.
- "Restructure this project note to match my conventions." — it reads the vault's rules file first, then applies them.
Afterwards you open Obsidian, or GitHub, and see exactly what changed.
AI client (Claude, ChatGPT, …)
→ OAuth login against this server
→ remote MCP server
→ member's fine-grained GitHub PAT (server-side only)
→ one Markdown vault repository
→ Git history
Access and behaviour are two separate layers. This server controls what the
assistant is allowed to touch. The vault's own root-level CLAUDE.md — plain
Markdown, versioned alongside the notes — defines how it should behave:
folder conventions, frontmatter contracts, what it must ask about before doing.
The github_write_file tool points the assistant at that file explicitly.
Giving an AI app write access to your knowledge base is only reasonable if the access is bounded. These are the boundaries:
- One repo, hard-scoped. Every tool is pinned to a single configured
owner/repo. No tool accepts an arbitrary owner, repo, or URL. - Credentials never reach the model. GitHub PATs are held server-side. The AI app sees explicit tools, not the token behind them.
- No delete tool. Files can be created and replaced. Nothing can be removed.
- Writes are full-file, straight to
main, capped at 100 KB. No patch mode, no branch sprawl — and ashaprecondition, so a write fails rather than clobbering a change made since the assistant last read the file. - Every change is a commit. What changed, when, and what it replaced stays visible in Git history.
- Own OAuth server. The AI app authenticates through a real OAuth flow against a self-hosted login — no static tokens pasted into a connector UI.
- Single user or team. A member registry maps each login to that member's own fine-grained PAT, so commits are attributed to the right person. A single user is just a one-member roster.
Full auth and threat model:
docs/security-notes.md.
| Tool | Purpose |
|---|---|
github_list_tree |
Every note path, recursive — the primary way to find a note |
github_read_file |
Read one file; returns content plus the sha needed to edit it |
github_write_file |
Create or replace one file, committed to main. No delete |
github_list_directory |
Entries directly under one directory |
github_search_notes |
Full-text search (best-effort; GitHub's index lags recent commits) |
github_repo_info |
Default branch, visibility, URL of the connected repo |
github_list_issues |
Issues, if the vault repo uses them |
github_list_pull_requests |
Pull requests, if the vault repo uses them |
ping |
Connectivity check — returns the server name and tool mode |
cp .env.example .env
# edit .env — each variable is documented inline
uv sync
uv run python server.pyThen validate the OAuth gate and connect it to your AI app (Claude, ChatGPT, …)
following docs/connector-setup.md.
All configuration is via environment variables (see .env.example):
MCP_PUBLIC_BASE_URL/OAUTH_ISSUER_URL— the server's own public URL.TEAM_MEMBER_IDS+ aMEMBER_<ID>_*block per member — the roster. One entry for single-user, several for a team.GITHUB_ALLOWED_OWNER/GITHUB_ALLOWED_REPO— the one repo everyone reads and writes.MCP_SERVER_NAME(optional) — the name this instance reports (login page,ping). Defaults tosecond-brain-github-mcp.
The app builds from the included Dockerfile and runs on any Docker host
(e.g. DigitalOcean App Platform, or a VPS via docker compose). Set every
variable from .env.example as an encrypted platform env var — never upload a
.env file — and set the public-URL vars to the real assigned URL. The server
reads env only at startup, so redeploy after any change (PAT rotation, new
member, URL). Full steps are in the connector-setup doc.
Use /health for uptime monitors. It is intentionally unauthenticated and
returns HTTP 200 when the server process is running:
https://<your-domain>/health
uv run pytest- Python 3.12+, managed with
uv. - Built on the official MCP Python SDK (
FastMCP, Streamable HTTP transport).
Built by Manuel Blinkert.