Skip to main content
Glama
README.md
# ObsidianMCP

Read-only MCP server that exposes an Obsidian vault to LLM clients over Streamable HTTP.

> Status: **v1 feature-complete**. All four MCP tools are implemented, all routed
> through the sharing filter: `list_tree` (accessible tree, each file annotated
> with a summary read from its YAML frontmatter `summary`), `read_note` (full note
> content, with a filesystem-level guard against symlink escapes/aliasing),
> `search_notes` (case-insensitive full-text search over shared `.md` notes,
> ranked snippets), and `resolve_links` (Obsidian wiki-links and backlinks over
> the accessible note set only). The operator **web panel** (`/panel`) edits which
> paths are shared.

## Settings

All configuration lives in one `settings.json` — there are no environment
variables. Copy the template and edit it:

```bash
cp settings.example.json settings.json
```

```json
{
  "apiToken": "your-mcp-bearer-token",
  "port": 3000,
  "vaultPath": "/abs/path/to/vault",
  "sharingConfigPath": "/abs/path/to/sharing.json",
  "panelPassword": "your-panel-password"
}
```

| Field | Required | Default | Purpose |
|---|---|---|---|
| `apiToken` | yes | — | Bearer token for `/mcp` |
| `port` | no | `3000` | HTTP port |
| `vaultPath` | no* | — | Vault root (read-only, **absolute**); *required for the vault-reading tools |
| `sharingConfigPath` | yes | — | Shared-paths config file (**absolute**; see below) |
| `panelPassword` | no | — | Enables the `/panel` operator UI; unset ⇒ panel disabled |

`vaultPath` and `sharingConfigPath` must be **absolute paths** (relative values are
rejected), so there is no base-directory ambiguity. `settings.json` holds secrets
and is gitignored.

## Run (local)

```bash
npm install
npm run build
node dist/index.js               # reads ./settings.json
node dist/index.js /path/to/settings.json   # or an explicit path
# or, without building: npm run dev
```

- `GET /healthz` — health check (no auth)
- `POST /mcp` — MCP endpoint (requires `Authorization: Bearer <apiToken>`)
- `GET /panel` — operator panel (only when `panelPassword` is set; password login)

## Run (Docker)

Create `settings.json` (with in-container paths `"vaultPath": "/vault"` and
`"sharingConfigPath": "/config/sharing.json"`), then:

```bash
VAULT_PATH=/abs/path/to/vault docker compose up --build
```

`VAULT_PATH` here is only the Docker bind-mount source for the host vault; the
vault is mounted read-only, `settings.json` read-only, and the sharing config
writable so the panel can edit it.

For a **remote deployment via Portainer** (GHCR image + bind-mounted config over a
private network), see [`deploy/README.md`](deploy/README.md) and
[`deploy/portainer-stack.yml`](deploy/portainer-stack.yml).

## Sharing config

Which vault paths are exposed is controlled by a JSON file (default `./sharing.json`),
read fresh on every request. It is a single ordered list of rules:

```json
{
  "rules": [
    { "path": "Projects",           "share": true  },
    { "path": "Projects/secret.md", "share": false }
  ]
}
```

- `path` is a vault-relative POSIX path — a folder (shared recursively) or a single file.
- For any path, the **longest (most specific) matching rule wins**, so a folder can be
  shared while a descendant is denied, and vice versa.
- **Default-deny:** a path with no matching rule is not shared. `{ "rules": [] }` shares nothing.
- Dotfiles/dotfolders (e.g. `.obsidian`) and symbolic links are always excluded, regardless of rules.

The vault is never written to; this file is the only knob.

## Operator panel

Set `PANEL_PASSWORD` and open `GET /panel`. After a password login it shows the
full vault tree with a checkbox per file (folders toggle their descendants). Save
rewrites `sharing.json` as one `share: true` rule per checked file — so a newly
added note stays private until you check it (default-deny). Changes take effect on
the next MCP request; no restart. The panel writes **only** `sharing.json`, never
the vault. In Docker the config is mounted writable while the vault stays
read-only; the panel is not mounted unless `PANEL_PASSWORD` is set.