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.
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues