notes-mcp
Provides integration with Cloudflare via Cloudflare Tunnel to securely expose the MCP server publicly.
Provides integration with GitHub for authentication via OAuth and for storing notes as a git repository (pushed to GitHub).
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@notes-mcpsearch my notes for 'MCP server'"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
notes-mcp
Remote MCP server exposing a personal notes git repo to Claude via the
mobile/web app. Notes are markdown files (or folders with index.md, data
files, and runnable scripts); every write is a prettier-formatted git commit
pushed to GitHub. Auth is OAuth 2.1 served by this server with GitHub as the
login identity. Single-user by design: exactly one GitHub login is allowed
through; everyone else gets a 403.
Doc | Contents |
The system as built: architecture, note model, tool surface, git workflow, auth design, security model, decision log, v2 roadmap | |
Script-based deploys, continuous deploy, Synology/DSM specifics, troubleshooting | |
Every configuration variable, commented |
Self-hosting
What you need
A GitHub account. GitHub is load-bearing three ways: it hosts the notes repo, a fine-grained PAT is the push credential, and a GitHub OAuth app is the login identity. Swapping in GitLab or a self-hosted remote is not supported without code changes (see the v2 roadmap in the PRD).
A notes repo. A private GitHub repo with at least one commit on the branch you'll track (a repo created with a README is fine — see Your notes repo).
A Docker host with compose. Any always-on Linux box works. (Synology/DSM users: use the script path in
deploy/README.mdinstead of compose.)HTTPS ingress. The worked path below uses a Cloudflare Tunnel (needs a domain on Cloudflare; no inbound ports on your host). Any HTTPS reverse proxy works instead — see Bring your own reverse proxy.
Pick your public hostname now (e.g. mcp.example.com) — steps 1 and 3
reference it.
1. GitHub OAuth app (login identity)
github.com → Settings → Developer settings → OAuth Apps → New OAuth App (not a GitHub App):
Homepage:
https://<your-hostname>Authorization callback URL:
https://<your-hostname>/auth/callback(exact)Leave "Enable Device Flow" unchecked
Save the Client ID (public) and generate a client secret (shown once)
2. Fine-grained PAT (git push credential)
github.com → Settings → Developer settings → Personal access tokens → Fine-grained tokens:
Repository access: Only select repositories → your notes repo
Permissions → Repository → Contents: Read and write (nothing else)
When it expires, pushes fail with
push_failed/git_errorin the logs — rotate it in.envand restart.
3. Cloudflare Tunnel (ingress)
Zone must be on Cloudflare. Zero Trust dashboard → Networks → Tunnels →
Create → Cloudflared; copy the tunnel token; add public hostname
<your-hostname> → service http://notes-mcp:8000 (the compose service —
cloudflared reaches it by container name).
Do not put a Cloudflare Access application in front of /mcp — the
server does its own OAuth, and Access breaks the Claude connector (see PRD
decision log). DNS: the dashboard creates the proxied CNAME; an existing
wildcard record doesn't conflict.
4. Configure
git clone https://github.com/johnhringiv/notes-mcp.git && cd notes-mcp
cp .env.example .env && chmod 600 .envFill in .env: NOTES_REPO_URL, GITHUB_TOKEN (the PAT), PUBLIC_URL
(https://<your-hostname>), GITHUB_OAUTH_CLIENT_ID,
GITHUB_OAUTH_CLIENT_SECRET, GITHUB_ALLOWED_LOGIN (your GitHub username),
and TUNNEL_TOKEN. Every variable is documented in the file.
5. Run
docker compose up -dThis pulls the public image ghcr.io/johnhringiv/notes-mcp:latest — no
build step, no registry auth. (Forks get their own: CI publishes to the
fork's GHCR on every push to main. Set IMAGE= in .env to pin a release
tag like :0.2.0 or a sha-… tag, or to point at a fork.)
6. Verify
curl https://<your-hostname>/health # {"status":"ok","version":"…"}
curl -i -X POST https://<your-hostname>/mcp | head -3 # 401 + WWW-AuthenticateIf either fails, see the troubleshooting table in
deploy/README.md.
7. Connect Claude
In the Claude app (web or mobile): Settings → Connectors → Add custom
connector → URL https://<your-hostname>/mcp. Claude sends you through
the GitHub login; approve, and you're connected. A successful connect logs
registered client then authorization granted in
docker logs notes-mcp; a login by any other GitHub user is rejected with
a JSON error naming the login it saw.
Your notes repo
Start with any repo — a fresh one containing only a README is fine (the tracked branch must have at least one commit for the startup clone); the server creates notes as you go. Two kinds of note coexist, so flat Obsidian/GitJournal-style repos work as-is:
File note — any
.mdfile; its id is the repo-relative path (topic/idea.md).Folder note — any directory with an
index.md; may hold data files and ascripts/directory thatrun_scriptexecutes.
Frontmatter is optional (title falls back to first H1, then filename).
Optional create_note templates live in .templates/*.md; a built-in
default is compiled in, so no bootstrap is required. Imported reference
documents (articles, papers) go under resources/ — a separate content
class from notes; see the PRD.
Bring your own reverse proxy
Cloudflare is ingress, not architecture: the server just needs PUBLIC_URL
to match whatever terminates TLS in front of it (Caddy, Traefik, nginx +
Let's Encrypt, Tailscale Serve, …). Don't edit docker-compose.yml — set
COMPOSE_PROFILES= (empty) in .env to drop the cloudflared sidecar, and
publish the port with a docker-compose.override.yml (compose merges it
automatically):
services:
mcp-server:
ports:
- "127.0.0.1:8000:8000"Then proxy https://<your-hostname> → http://127.0.0.1:8000. The MCP
endpoint streams (streamable HTTP), so disable response buffering
(nginx: proxy_buffering off). Auth is unaffected — the server remains its
own OAuth issuer either way.
Updating
docker compose pull && docker compose up -dThe notes-repo and oauth-state volumes persist, so updates keep the
clone, issued tokens, and the Claude connector registration. Pin a release
tag (:0.2.0) or sha-… tag via IMAGE= in .env for rollbacks. For unattended pull-based deploys
(poll a promoted :prod tag on a schedule), see
deploy/README.md.
Related MCP server: Vault MCP Server
Development
uv sync # deps (Python 3.14, uv-managed)
uv run pytest
uv run ruff check src tests && uv run ruff format --check src tests
uv run ty check
# run locally against any notes working tree (git optional; push needs origin)
NOTES_REPO_PATH=~/notes MCP_PORT=8000 uv run notes-mcpRequires git and rg (ripgrep) on PATH; prettier optional locally
(writes commit unformatted without it). The MCP endpoint is
http://host:port/mcp (streamable HTTP); GET /health is a public liveness
probe reporting the running version. CI (.github/workflows/ci.yml) runs
ruff, ty, and pytest, then publishes the container image to GHCR.
One-time after cloning: git config core.hooksPath .githooks — enables the
pre-commit hook that prettier-formats staged markdown.
Local-dev degraded modes (production uses the full path):
NOTES_REPO_PATHnot a git repo → filesystem only, no commitsgit repo without an
originremote → commits locally, skips pull/pushPUBLIC_URLunset → auth disabled (logged warning)
Local image build: docker compose build (or docker build -t notes-mcp:dev .).
For air-gapped hosts there's a docker save tarball path in
deploy/README.md.
Layout
src/notes_mcp/
├── server.py # FastMCP app, tool registration, write-lock, /health, OAuth callback
├── auth.py # OAuth 2.1 provider (GitHub upstream identity, JWT minting)
├── config.py # env loading/validation (secrets: NAME or NAME_FILE)
├── git_ops.py # pull/commit/push wrapper, clone-at-startup, write_flow
├── notes.py # note model: list/read/search/create/append/edit
├── resources.py # imported reference documents (add/list/read ranged)
├── files.py # add_file_to_note
├── scripts.py # list_scripts, run_script (timeouts, caps, keepalives)
├── formatting.py # prettier on changed markdown before commit
├── templates.py # create_note templates
├── logging.py # structured JSON logging + tool-call wrapper
└── errors.py # structured error helper
tests/ # unit + full OAuth flow + git integrationThis server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/johnhringiv/notes-mcp'
If you have feedback or need assistance with the MCP directory API, please join our Discord server