Skip to main content
Glama
KORThomasJeong

obsidian-mcp-search

README.md
# obsidian-mcp-search

> 🇰🇷 한국어 README: [README.ko.md](README.ko.md)

Self-hosted [MCP](https://modelcontextprotocol.io) server that exposes
**embedding-powered semantic search + graph context** over your Obsidian
vault(s). Point it at a folder of `.md` files and it serves hybrid
(BM25 + vector + alias) search to any MCP client — with a web dashboard to
manage vaults and the embedding index.

- No external index: walks a plain Obsidian vault directly
- Local embeddings by default (`intfloat/multilingual-e5-small`, no API key);
  OpenAI/Azure optional
- Returns the matching passage **plus** heading breadcrumb, tags, aliases,
  outbound links and backlinks
- Multiple vaults from one server, each incrementally indexed and watched
- Web admin dashboard at `/admin`

## Install

### pip / uvx

```bash
pip install obsidian-mcp-search
# Or install with the server extra (includes uvicorn + fastmcp):
pip install "obsidian-mcp-search[server]"
obsidian-mcp-search add-vault main /path/to/your/vault
obsidian-mcp-search serve
```

### One-line installer (registers an auto-start service)

```bash
./install.sh --vault /path/to/your/vault
# macOS -> launchd, Linux -> systemd; prints dashboard URL + client config
```

### Docker

```bash
echo "VAULT_PATH=/path/to/your/vault" > .env
docker compose up --build -d
```

## Connect an MCP client

> On **localhost (default) no token is needed** — a server bound to `127.0.0.1`
> runs without auth. A token is only required for remote exposure
> (`OMCS_HOST=0.0.0.0`); see [Authentication token](#authentication-token).

### Claude Desktop

Add to `claude_desktop_config.json` (local, no token):

```json
{
  "mcpServers": {
    "obsidian-search": {
      "url": "http://127.0.0.1:8848/mcp"
    }
  }
}
```

Remote + token — pass the bearer token via `headers`:

```json
{
  "mcpServers": {
    "obsidian-search": {
      "url": "https://your-host:8848/mcp",
      "headers": { "Authorization": "Bearer <your-token>" }
    }
  }
}
```

### Claude Code (CLI)

```bash
# local
claude mcp add --transport http obsidian-search http://127.0.0.1:8848/mcp

# remote + token
claude mcp add --transport http obsidian-search https://your-host:8848/mcp \
  --header "Authorization: Bearer <your-token>"
```

Tools exposed: `search(query, k, vault, mode)`, `list_vaults()`,
`read_note(vault, path, section?)`, `reindex(vault)`.

## Dashboard

Open `http://127.0.0.1:8848/admin` to add/remove vaults, reindex, clear an
index, switch the embedding model (re-embeds all vaults), and watch live status.

**Remote dashboard note:** The `/admin` dashboard is intended for **localhost**
use (default: no token, works fully in a browser). Browsers cannot attach
`Authorization: Bearer` headers to page navigations or htmx background requests,
so if you need to access the dashboard remotely you must front the server with a
reverse proxy that handles authentication (e.g. nginx `auth_basic` or an SSO
proxy), or use an SSH tunnel (`ssh -L 8848:127.0.0.1:8848 yourhost`). The
bearer token set via `MCP_AUTH_TOKEN` still fully protects the `/mcp` endpoint
for all MCP clients that send the `Authorization` header correctly.

## Configuration (env vars)

| Var | Default | Meaning |
|---|---|---|
| `OMCS_HOST` | `127.0.0.1` | bind address (set `0.0.0.0` to expose) |
| `OMCS_PORT` | `8848` | port |
| `MCP_AUTH_TOKEN` | _(unset)_ | bearer token; **required** when bound off-localhost |
| `OMCS_EMBED_BACKEND` | `local` | `local` (fastembed) or `openai` |
| `OMCS_EMBED_MODEL` | `intfloat/multilingual-e5-small` | embedding model |
| `OMCS_EMBED_BASE_URL` | _(unset)_ | Azure/OpenAI-compatible base URL |
| `OMCS_EMBED_BATCH_SIZE` | `128` | max chunks per embedding request (`openai` backend) |
| `OMCS_EMBED_MAX_TOKENS` | `80000` | max estimated tokens per embedding request (`openai` backend) |
| `OMCS_EMBED_MAX_RETRIES` | `6` | retries on HTTP 429 (honors `Retry-After`, else backoff) |
| `OMCS_PREFER_SQLITE_VEC` | `0` | `1` to use sqlite-vec when loadable |

Indexes live in `~/.cache/obsidian-mcp-search/<vault-hash>/`; the vault registry
in `~/.config/obsidian-mcp-search/config.toml`. Your notes are never written to.

## Security

Binds to localhost by default. To expose remotely, set `MCP_AUTH_TOKEN` and
`OMCS_HOST=0.0.0.0`; the `/mcp` endpoint then requires
`Authorization: Bearer <token>` from all MCP clients. See the Dashboard section
above for notes on remote `/admin` access.

## Authentication token

> The token is **not issued to you — you create it yourself** and set it via
> `MCP_AUTH_TOKEN`. It is only needed when exposing the server remotely
> (`OMCS_HOST=0.0.0.0`); localhost needs no token.

**1. Generate a token** — any sufficiently long random string:

```bash
openssl rand -hex 24
# or:
python3 -c "import secrets; print(secrets.token_urlsafe(24))"
```

**2. Set it on the server and bind remotely:**

```bash
export MCP_AUTH_TOKEN="<token-from-step-1>"
export OMCS_HOST=0.0.0.0
obsidian-mcp-search serve
```

Starting with `OMCS_HOST=0.0.0.0` but **no** `MCP_AUTH_TOKEN` is refused (so your
notes are never served unauthenticated). For Docker, put both in `.env`; for a
launchd/systemd service, add them to that service's environment.

**3. Use the same token from the client** via the `Authorization: Bearer <token>`
header (see the Claude Desktop `headers` and Claude Code `--header` examples
above). Quick check:

```bash
curl -H "Authorization: Bearer $MCP_AUTH_TOKEN" https://your-host:8848/mcp
# wrong/missing token → 401; correct → normal response
```

**(Optional) Regenerate at runtime** via the dashboard endpoint:

```bash
curl -X POST -H "Authorization: Bearer $MCP_AUTH_TOKEN" \
  https://your-host:8848/admin/token/regenerate
# → {"token": "new-token"}
```

Note: a token changed this way applies only to the **running process** — a
restart reverts to the `MCP_AUTH_TOKEN` env value. To change it permanently,
update the env var and restart.

## License

MIT.