{
  "markdown": "<h1 align=\"center\">Obsidian Semantic Search MCP</h1>\n\n<p align=\"center\">\n  Read-only semantic retrieval for agents that need to find the right Obsidian note without write access.\n</p>\n\n<p align=\"center\">\n  <a href=\"https://www.npmjs.com/package/@dalecb/obsidian-semantic-mcp\"><img alt=\"npm\" src=\"https://img.shields.io/npm/v/@dalecb/obsidian-semantic-mcp?color=111827\"></a>\n  <a href=\"https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github.DalecB/obsidian-semantic-mcp\"><img alt=\"MCP Registry\" src=\"https://img.shields.io/badge/MCP%20Registry-active-111827\"></a>\n  <a href=\"./LICENSE\"><img alt=\"License\" src=\"https://img.shields.io/badge/license-MIT-111827\"></a>\n  <img alt=\"Node.js\" src=\"https://img.shields.io/badge/node-%3E%3D24-111827\">\n</p>\n\n<p align=\"center\">\n  <a href=\"./README.ko.md\">한국어</a> ·\n  <a href=\"#quick-start\">Quick Start</a> ·\n  <a href=\"#why-this-exists\">Why This Exists</a> ·\n  <a href=\"#how-it-works\">How It Works</a>\n</p>\n\n---\n\nYour Obsidian vault is useful only if your agent can find the right note.\n\nKeyword search misses context. Full write-capable Obsidian MCP servers expose more power than a retrieval agent needs. Obsidian plugins are great inside Obsidian, but they are not always the right boundary for Codex, Claude Desktop, Cursor, or any other MCP client.\n\nThis project is the narrow version:\n\n```text\nlocal Obsidian vault -> read-only scanner -> local SQLite index -> MCP search/read tools\n```\n\nNo note writes. No cloud embeddings by default. No Obsidian plugin runtime. No sync service.\n\n<p align=\"center\">\n  <img src=\"https://raw.githubusercontent.com/DalecB/obsidian-semantic-mcp/main/docs/architecture.png\" alt=\"Local, read-only pipeline: Obsidian vault to scanner to chunker to Ollama embeddings to SQLite index, queried by an MCP client via search_notes and read_note\" width=\"100%\">\n</p>\n\n> Status: `0.3.1` early preview. The server is usable today, but ranking behavior and tool schemas may change before `1.0`.\n\n## What You Get\n\n| Need | What this server does |\n| --- | --- |\n| Find the note an agent should read | Hybrid semantic + keyword search over Markdown notes |\n| Keep the vault safe | Exposes search/read/index/status only; no write, patch, move, rename, or delete tools |\n| Stay local-first | Uses Ollama embeddings and stores the index on your machine |\n| Make results agent-friendly | Returns file-level matches with headings, snippets, and line ranges |\n| Avoid plugin state | Reads the vault directly from the filesystem; Obsidian does not need to be running |\n\nExample result shape:\n\n```json\n{\n  \"path\": \"02_Projects/RealtimeAPI/05_Interview_QA.md\",\n  \"title\": \"Interview Q&A\",\n  \"score\": 0.7431,\n  \"matched_sections\": [\n    {\n      \"heading\": \"Level 4 > Redis Lua atomicity\",\n      \"lines\": [266, 305],\n      \"reason\": \"semantic=1, keyword=0.5565, metadata=0.6\"\n    }\n  ]\n}\n```\n\n## Quick Start\n\nRequirements:\n\n- Node.js `>= 24`\n- Ollama\n- An Obsidian vault\n- An MCP client such as Codex, Claude Desktop, Cursor, or another stdio MCP client\n\nInstall the embedding model:\n\n```bash\nollama pull bge-m3\ncurl http://localhost:11434/api/tags\n```\n\nPrint setup guidance:\n\n```bash\nnpx -y --package @dalecb/obsidian-semantic-mcp obsidian-semantic-mcp-setup\n```\n\n## Codex Setup\n\nAdd this to `~/.codex/config.toml`:\n\n```toml\n[mcp_servers.obsidian_semantic]\ncommand = \"npx\"\nargs = [\"-y\", \"@dalecb/obsidian-semantic-mcp\"]\n\n[mcp_servers.obsidian_semantic.env]\nOBSIDIAN_VAULT_ROOT = \"/path/to/your/Obsidian Vault\"\nOBSIDIAN_SEMANTIC_MCP_HOME = \"/Users/you/.obsidian-semantic-mcp\"\nOLLAMA_BASE_URL = \"http://localhost:11434\"\nOBSIDIAN_EMBED_MODEL = \"bge-m3\"\nOBSIDIAN_SEMANTIC_AUTO_INDEX = \"true\"\n```\n\nRestart Codex, then run:\n\n```text\nobsidian_semantic.index_status\nobsidian_semantic.search_notes { \"query\": \"Redis Lua atomicity\", \"limit\": 5 }\n```\n\n## Claude Code Setup\n\nAdd the server at user scope so the personal vault path stays out of project `.mcp.json` files:\n\n```bash\nclaude mcp add obsidian_semantic --scope user \\\n  --env OBSIDIAN_VAULT_ROOT=\"/path/to/your/Obsidian Vault\" \\\n  --env OBSIDIAN_SEMANTIC_MCP_HOME=\"/Users/you/.obsidian-semantic-mcp\" \\\n  --env OLLAMA_BASE_URL=\"http://localhost:11434\" \\\n  --env OBSIDIAN_EMBED_MODEL=\"bge-m3\" \\\n  --env OBSIDIAN_SEMANTIC_AUTO_INDEX=\"true\" \\\n  -- npx -y @dalecb/obsidian-semantic-mcp\n\nclaude mcp get obsidian_semantic\n```\n\nStart a new Claude Code session and run `/mcp` to confirm the server is connected. On native Windows, use `-- cmd /c npx -y @dalecb/obsidian-semantic-mcp` after the environment flags.\n\nSee Anthropic's [Claude Code MCP documentation](https://docs.anthropic.com/en/docs/claude-code/mcp) for scope and command details.\n\n## Claude Desktop / JSON MCP Client Setup\n\nIn Claude Desktop, open **Settings → Developer → Edit Config**. This opens `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS or `%APPDATA%\\Claude\\claude_desktop_config.json` on Windows. Merge the following server into `mcpServers`:\n\n```json\n{\n  \"mcpServers\": {\n    \"obsidian_semantic\": {\n      \"command\": \"npx\",\n      \"args\": [\"-y\", \"@dalecb/obsidian-semantic-mcp\"],\n      \"env\": {\n        \"OBSIDIAN_VAULT_ROOT\": \"/path/to/your/Obsidian Vault\",\n        \"OBSIDIAN_SEMANTIC_MCP_HOME\": \"/Users/you/.obsidian-semantic-mcp\",\n        \"OLLAMA_BASE_URL\": \"http://localhost:11434\",\n        \"OBSIDIAN_EMBED_MODEL\": \"bge-m3\",\n        \"OBSIDIAN_SEMANTIC_AUTO_INDEX\": \"true\"\n      }\n    }\n  }\n}\n```\n\nFully quit and restart Claude Desktop. Cursor and other JSON-style stdio MCP clients can use the same `mcpServers` entry. On Windows, if `npx` is not found, use `\"command\": \"cmd\"` and prepend `\"/c\", \"npx\"` to `args`.\n\nSee the official [local MCP server guide](https://modelcontextprotocol.io/docs/develop/connect-local-servers) for Claude Desktop configuration and logs.\n\n## Why This Exists\n\nThis isn't aiming to be the most powerful Obsidian automation server. It aims to be the safest retrieval tool you can hand an agent.\n\nHere's how it stacks up against the two tools it usually comes down to — a full-permission Obsidian MCP server (Local REST API based) and GBrain (a broader knowledge-compilation platform):\n\n| | **This project** | **Full-permission Obsidian MCP** | **GBrain** |\n| --- | --- | --- | --- |\n| Access model | Read-only: search / read / index | Read + write + edit + delete | Read + write; compiles notes into its own model |\n| Touches your vault | Never | Yes | Yes — restructures content |\n| Obsidian must run | No — reads files directly | Yes — needs the REST API plugin | No |\n| Required local runtime | Node.js + Ollama | Obsidian + plugin | Standalone platform |\n| Embeddings & data | Local Ollama by default; remote URL is configurable | Local API; embeddings vary by setup | Built-in pipeline; optional sync |\n| Storage | One SQLite file you can delete and rebuild | Plugin-managed | Its own store / migration |\n| Best for | A small read-only retrieval boundary for agents | Full vault automation and editing | Building a compiled knowledge base across sources |\n\nThat trade is on purpose: give up writing, editing, and running inside Obsidian, and you get fewer moving parts and a smaller blast radius in return.\n\nUse this if your agent should answer:\n\n- \"Which note explains this project decision?\"\n- \"Find the file where I wrote about idempotency payload mismatch.\"\n- \"Show me the career notes related to this interview topic.\"\n- \"Search my vault, but do not mutate it.\"\n\nDo not use this if you want an Obsidian UI plugin, automatic note generation, or write-capable vault automation.\n\n## Tools\n\n### `index_status`\n\nReturns index metadata and safety settings.\n\n### `index_vault`\n\nBuilds or updates the external SQLite index.\n\n```json\n{ \"mode\": \"incremental\" }\n```\n\nSpecific files:\n\n```json\n{\n  \"mode\": \"incremental\",\n  \"paths\": [\"02_Projects/My Note.md\"]\n}\n```\n\n### `search_notes`\n\nSearches notes with hybrid semantic and keyword ranking.\n\n```json\n{\n  \"query\": \"live coding notes\",\n  \"limit\": 8,\n  \"mode\": \"hybrid\"\n}\n```\n\nModes:\n\n- `hybrid`: semantic vector + SQLite FTS5 + metadata boosts\n- `semantic`: vector-first search\n- `keyword`: FTS5 keyword search without embedding the query\n\n### `read_note`\n\nReads a note or line range by vault-relative path.\n\n```json\n{\n  \"path\": \"02_Projects/My Note.md\",\n  \"start_line\": 10,\n  \"end_line\": 40\n}\n```\n\n## How It Works\n\n```text\nindex_vault\n  -> scan Markdown files under OBSIDIAN_VAULT_ROOT\n  -> block denied paths and symlink escapes\n  -> split notes by Markdown headings\n  -> create one summary chunk per file\n  -> embed chunks with Ollama bge-m3\n  -> store notes, chunks, FTS rows, and vectors in SQLite\n\nsearch_notes\n  -> incrementally index on first search or first search after a Markdown change\n  -> embed the query with Ollama\n  -> score vector similarity\n  -> score SQLite FTS5 keyword matches\n  -> apply title/path/heading metadata boosts\n  -> regroup chunk matches into file-level results\n```\n\nDefault storage:\n\n```text\n~/.obsidian-semantic-mcp/\n  data/semantic.sqlite\n```\n\nThe vault remains the source of truth. The SQLite database is a derived index and can be deleted/rebuilt.\n\n## Safety Model\n\nThe server reads your vault and never writes to it. Three layers decide what an agent can see.\n\n**1. Always denied (system / tooling).** Never indexed, no override:\n\n- `.obsidian/`, `.smart-env/`, `.claude/`, `.codex-*/`\n- any hidden folder (name starts with `.`)\n- `node_modules`, `cache`, `logs`\n\n**2. Sensitive — denied by default, unlockable.** Stays blocked even when a tool call passes `include_sensitive: true`, unless the server is started with `OBSIDIAN_SEMANTIC_ALLOW_SENSITIVE=true`. Defaults to `08_PersonalInfo/`. Override the list with `OBSIDIAN_SEMANTIC_SENSITIVE_PATHS` (comma- or newline-separated folders):\n\n```toml\nOBSIDIAN_SEMANTIC_SENSITIVE_PATHS = \"08_PersonalInfo, 09_Finance\"\n```\n\n**3. Your own excludes — always denied.** Folders you never want indexed, searched, or read. No unlock flag:\n\n```toml\nOBSIDIAN_SEMANTIC_EXCLUDE = \"03_Journal, Private, Clients/Acme\"\n```\n\nWhich one do you want?\n\n- **\"Don't index this at all\"** → `OBSIDIAN_SEMANTIC_EXCLUDE`\n- **\"Keep it locked, but I can unlock it with a flag when I need to\"** → `OBSIDIAN_SEMANTIC_SENSITIVE_PATHS` + `OBSIDIAN_SEMANTIC_ALLOW_SENSITIVE`\n\nAdditional guards:\n\n- All paths are resolved through `realpath`.\n- Path traversal and URL-encoded traversal are blocked.\n- Symlinks that escape the vault root or point to a denied in-vault path are blocked.\n\n> `search_notes` and `read_note` enforce the current exclusion settings on every call. With automatic indexing enabled, the next search also removes excluded stale rows from the index. Confirm the active lists with `index_status`.\n\nThe local index stores snippets and embedding vectors. Treat it as a derived copy of your vault. See [PRIVACY.md](./PRIVACY.md).\n\n## Indexing Strategy\n\nThe default is action-driven, not timer-based:\n\n1. The first `search_notes` after server start.\n2. The first `search_notes` after a Markdown create, update, delete, or rename event.\n3. The first `search_notes` after the embedding model or index format changes; this triggers a full rebuild.\n4. An explicit `index_vault` call.\n\nThe filesystem watcher only marks the index dirty. Indexing runs once, immediately before the next search, and concurrent searches share that work. If recursive watching is unavailable, the server falls back to an incremental scan before every search.\n\nDisable automatic indexing with:\n\n```toml\nOBSIDIAN_SEMANTIC_AUTO_INDEX = \"false\"\n```\n\nThen call `index_vault { \"mode\": \"incremental\" }` yourself. The legacy `OBSIDIAN_SEMANTIC_STARTUP_INDEX=true` remains supported, but is not the default because it does work even when no search follows.\n\n## Search Quality Evaluation\n\nCreate a JSON file containing queries and expected paths to compare keyword, semantic, and hybrid modes under the same conditions:\n\n```json\n[\n  { \"query\": \"strong consistency at 1000 TPS\", \"expected_paths\": [\"03_Knowledge/QnA/Strong-Consistency-at-1000TPS.md\"] }\n]\n```\n\n```bash\nnpm run evaluate -- ./queries.json 5\n```\n\nThe command reports Recall@K and MRR. Avoid adding ranking complexity or embedding cost unless hybrid search measurably beats the keyword baseline.\n\nRefresh the index with `search_notes` or `index_vault` before evaluating.\n\n## Development\n\n```bash\nnpm test\nnpm run pack:check\n```\n\nBefore publishing:\n\n```bash\nnpm pack --dry-run\n```\n\nConfirm the package does not include `data/`, `*.sqlite`, or private vault files.\n\n## License\n\nMIT\n",
  "bytes": 12455,
  "sha": "faad379b9a8b5cc70da66534aabe300b5761b88c9ad91c3bec7461f64f465dd2",
  "repo_slug": "dalecb/obsidian-semantic-mcp",
  "fonte": "repo",
  "truncated": false,
  "api": "https://agentalog.com/api/listings/mcp_io_github_dalecb_obsidian_semantic_mcp_e433addd/readme"
}