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

A small **remote MCP server** that lets an LLM (claude.ai, Claude Code, mobile)
**search, read, and write** an [Obsidian](https://obsidian.md) vault organised
with the [PARA](https://fortelabs.com/blog/para/) method — and **save URLs** as
notes.

The server holds **no LLM of its own**. It is a deterministic set of tools; the
connecting model is the brain. When you ask it to "save this to my vault", the
model calls `vault_taxonomy` to see your folder structure, picks the right
folder itself, and calls `vault_write`.

```
Claude Desktop / claude.ai / Claude Code
        │  HTTPS + GitHub OAuth (or Bearer token)
        ▼
 reverse proxy / TLS  ──►  vault-mcp (this server)  ──►  ~/Obsidian/YourVault
```

> **Docs:** [Architecture](docs/ARCHITECTURE.md) ·
> [Authentication](docs/AUTHENTICATION.md) ·
> [Deployment](docs/DEPLOYMENT.md) · [Changelog](docs/CHANGELOG.md)

## Tools

| Tool | What it does |
|---|---|
| `vault_taxonomy` | Returns the folder tree, PARA policy, and subcategory whitelists so the model can choose a destination folder. |
| `vault_search` | `semantic` (hybrid vector search) or `title` (exact name/path lookup). |
| `vault_read` | Returns a note's content. Machine-generated index/log files and legacy dumps are blocked. |
| `vault_write` | Create / overwrite / append a note. The caller supplies the folder. Writes are restricted to PARA buckets; the vault root is never written. |
| `vault_save_url` | Fetch a URL, extract clean text, save it as a note, and return a small preview. |

## Safety

- Every path is resolved and confined to the vault root — no `..` traversal, no absolute paths, no symlink escapes.
- Writes are allowed only under `01-Inbox/`, `10-Projects/`, `20-Areas/`, `30-Resources/`, `docs/`.
- Reads of regenerable index/map/log artifacts and legacy wiki backups are refused (they are huge and waste tokens).
- Auth is required: a static **Bearer token** or **GitHub OAuth** with a user allowlist (see [Authentication](#authentication)).

## Install

```bash
git clone https://github.com/KORThomasJeong/vault-mcp.git
cd vault-mcp
cp .env.example .env        # then edit .env
uv sync
```

Generate an auth token:

```bash
python -c "import secrets; print(secrets.token_urlsafe(32))"
```

## Configure

All config is via environment (see `.env.example`):

| Var | Required | Default | Notes |
|---|---|---|---|
| `VAULT_PATH` | ✅ | — | Absolute path to the vault root. |
| `AUTH_MODE` |  | inferred | `token` \| `github` \| `none`. See [Authentication](#authentication). |
| `MCP_AUTH_TOKEN` |  | (none) | Bearer token (token mode). |
| `GITHUB_CLIENT_ID` / `GITHUB_CLIENT_SECRET` |  | (none) | GitHub OAuth app credentials (github mode). |
| `BASE_URL` |  | (none) | Public HTTPS URL of the server (github mode). |
| `GITHUB_ALLOWED_USERS` |  | (none) | Comma-separated GitHub logins allowed to connect. **Required in github mode.** |
| `MCP_HOST` |  | `127.0.0.1` | Use `0.0.0.0` when the proxy reaches it over the LAN (e.g. NPM in Docker), else 502. |
| `MCP_PORT` |  | `8848` | |
| `MCP_PATH` |  | `/mcp` | |
| `FAST_SEARCH_BIN` |  | (none) | Warm semantic-search front-end, called `<bin> "<query>" -n <limit>`. Preferred over `QMD_BIN`. |
| `QMD_BIN` |  | `qmd` | Fallback semantic search helper. Blank + no `FAST_SEARCH_BIN` → that mode is disabled. |
| `WIKI_QUERY_BIN` |  | `wiki-query` | Title/path search helper. Blank → disabled. |
| `SAVE_LINK_BIN` |  | `obsidian-save-link` | URL→note helper. Blank → disabled. |
| `INDEX_REBUILD_CMD` |  | (none) | Command run when a tool is called with `rebuild_index=true`. |

The search and URL-save tools shell out to optional companion CLIs. If you don't
have them, those tools simply report that they're disabled — read/write/taxonomy
still work.

## Run

```bash
uv run vault-mcp
# serves Streamable HTTP at http://127.0.0.1:8848/mcp
```

Expose it through your existing reverse proxy (nginx / Nginx Proxy Manager /
Caddy / Cloudflare) so that `https://your-host/mcp` forwards to `127.0.0.1:8848`.
Enable WebSocket/streaming pass-through and disable response buffering so SSE
works (for nginx: `proxy_buffering off;`, a long `proxy_read_timeout`).

## Authentication

| Mode | How clients authenticate | Works with |
|---|---|---|
| `token` | `Authorization: Bearer <token>` header | Claude Code (`--header`). **Not** the Claude Desktop / claude.ai custom-connector UI (no header field). |
| `github` | GitHub OAuth (browser), restricted to `GITHUB_ALLOWED_USERS` | Claude Desktop / claude.ai remote connectors, Claude Code. |
| `none` | nothing | trusted/private networks only. |
| `github` + token | both at once (MultiAuth) | Desktop via OAuth **and** CLI/automation via token, one server. |

> **OAuth authenticates, the allowlist authorizes.** In `github` mode the server
> refuses to start unless `GITHUB_ALLOWED_USERS` is set — otherwise *any* GitHub
> account could connect, which is more open than a token.

> **Both at once:** set `MCP_AUTH_TOKEN` alongside `github` mode and the server
> accepts GitHub OAuth *and* the static token simultaneously (MultiAuth). Details
> in [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md).

### Set up GitHub OAuth (github mode)

1. Register an OAuth App at <https://github.com/settings/developers> → **New OAuth App**:
   - **Homepage URL**: `https://your-host`
   - **Authorization callback URL**: `https://your-host/auth/callback`
2. Copy the **Client ID**, generate a **Client secret**, and put them plus
   `BASE_URL=https://your-host`, `AUTH_MODE=github`, and
   `GITHUB_ALLOWED_USERS=<your-login>` in `.env`.
3. Restart the server.

### Connect from Claude Code

```bash
# token mode
claude mcp add --transport http vault https://your-host/mcp \
  --header "Authorization: Bearer <your-token>"

# github mode (OAuth — opens a browser)
claude mcp add --transport http vault https://your-host/mcp
```

### Connect from Claude Desktop / claude.ai

Settings → Connectors → add a custom connector pointing at `https://your-host/mcp`.
Use **github mode** — the desktop/web connector UI authenticates via OAuth and
has no field for a static Bearer token.

## Develop

```bash
uv run --extra dev pytest
```

## License

MIT