Skip to content

feat(vault): multi-vault discovery + selection — v0.3.0#18

Merged
bezata merged 2 commits into
mainfrom
feat/vault-selection
Apr 24, 2026
Merged

feat(vault): multi-vault discovery + selection — v0.3.0#18
bezata merged 2 commits into
mainfrom
feat/vault-selection

Conversation

@bezata
Copy link
Copy Markdown
Owner

@bezata bezata commented Apr 24, 2026

Summary

  • Adds a new `vault.*` namespace (4 tools) that lets an LLM enumerate the user's Obsidian vaults and switch between them for the session. Tool surface 62 → 66 across 16 namespaces.
  • Fully backwards compatible — `OBSIDIAN_VAULT_PATH` behaves identically to v0.2.5, no existing tool signatures change, and all 56 pre-existing tests pass unmodified. The precedence chain gains a middle slot that collapses to v0.2.5 exactly when `vault.select` is never called: `arg > session > env > error`.
  • `obsidian.json` discovery is marked EXPERIMENTAL. Obsidian's vault registry has no documented API — every third-party tool parses the internal `obsidian.json` file. We parse it by default for zero-config onboarding, but label it experimental in tool descriptions, CHANGELOG, ENVIRONMENT.md, and MIGRATION.md because the format is internal to Obsidian and could change without notice. Toggle with `KOBSIDIAN_VAULT_DISCOVERY=off`; parse failures never crash the server.
  • Cross-platform CI required. Depends on #17 (the CI matrix PR) landing first so we have a confirmed green macOS + Windows + Linux baseline before this merges.

The 4 new tools

Tool What Annotation
`vault.list` Merge + dedup vaults from `OBSIDIAN_VAULT_PATH` + `OBSIDIAN_VAULT_=` env vars + parsed `obsidian.json`. Each item carries `{id, name, path, isDefault, isActive, source, lastOpened?, exists}`. `READ_ONLY`
`vault.current` Echo the vault filesystem tools resolve to right now, plus the `reason` (session-selected | env-default | none) + env default + live-Obsidian REST caveat. `READ_ONLY`
`vault.select` Set the session-active vault by exactly one of `id`, `name`, or `path`. `path` accepts ad-hoc absolute directories, so the LLM can point at a fresh/empty vault to initialise. `ADDITIVE`
`vault.reset` Clear the session selection; fall back to `OBSIDIAN_VAULT_PATH`. Idempotent. `IDEMPOTENT_ADDITIVE`

Discovery sources (in priority order)

Source Status Toggle
`OBSIDIAN_VAULT_PATH` Documented, stable, always included
`OBSIDIAN_VAULT_=path` env vars Documented, stable
`obsidian.json` parsing EXPERIMENTAL — Obsidian's undocumented registry, stable since 1.0 `KOBSIDIAN_VAULT_DISCOVERY=on` (default) / `off`

`obsidian.json` paths per OS

  • macOS: `~/Library/Application Support/obsidian/obsidian.json`
  • Windows: `%APPDATA%\obsidian\obsidian.json` (with home fallback)
  • Linux: `$XDG_CONFIG_HOME/obsidian/…` → `~/.config/obsidian/…` → Flatpak → Snap
  • Escape hatch: `KOBSIDIAN_OBSIDIAN_CONFIG=/abs/path/obsidian.json` for portable installs / WSL.

Security / gating

  • `KOBSIDIAN_VAULT_ALLOW=name1,name2,/abs/path` — allowlist filtering (names or absolute paths).
  • `KOBSIDIAN_VAULT_DENY=...` — denylist, applied after allowlist.
  • `OBSIDIAN_VAULT_PATH` is never filtered by either, preventing operator self-lockout.
  • `vault.select` distinguishes `not_found` (vault doesn't exist) from `unauthorized` (vault exists but is blocked) for better error UX.

Changes outside `vault.*`

  • Every filesystem tool description gets a one-line session-vault note appended at registration time (via `buildDescription()` in `create-server.ts` — centralised, no per-file edits). `workspace.` and `commands.` get the inverse note: they bridge to the live Obsidian instance via `OBSIDIAN_API_URL` and are NOT affected by the selection.
  • `pick-vault` prompt — MCP server-side prompt template that Claude Desktop surfaces as a slash command; walks `vault.list` → presents options → calls `vault.select`.
  • Stale prompt text — fixed the `ingest-source` prompt's leftover references to `notes.insertAfterHeading` / `notes.append` (removed in v0.2.5) → now `notes.edit` with the appropriate `mode`.
  • `DomainContext` extended with `session: SessionState` + `vaults: VaultCache`. Additive — consumers destructuring `{env, api}` continue to work unmodified.

HTTP transport caveat

Multi-client HTTP deployments share the `DomainContext` across requests, so `vault.select` is process-global today. `vault.select`'s description warns about this; concurrent HTTP clients should pass `vaultPath` per call. Per-`Mcp-Session-Id` scoping is deferred to a future v0.3.x.

Test plan

  • 4 new test files, 49 new assertions — platform-mocked path resolution, fixture parsing, precedence chain (all 8 set/unset combos), integration with real temp-dir vaults
  • 7 fixture `obsidian.json` files (macOS, Windows escaped-backslash, Linux, malformed, no-vaults, empty-vaults, with-unknown-keys)
  • All 56 v0.2.5 tests still pass unmodified
  • `bun run typecheck` — clean
  • `bun run lint` — clean
  • `bun run test` — 111/111 passing on Windows
  • `bun run build` — stdio 2.1 MB + http 1.51 MB
  • `docs/tool-inventory.json` regenerated — 66 tools
  • CI matrix green on `macos-latest` + `windows-latest` + `ubuntu-latest` (depends on ci: add cross-platform test matrix (macos + windows + linux) #17 landing first — after that, this PR will get the matrix)
  • Manual smoke on the maintainer's Windows machine: `vault.list` returns real obsidian.json vaults; `vault.select { name: "…" }` flips the vault; `notes.list` hits it.
  • Manual smoke on macOS (borrowed/cloud): same four steps, confirm `vault.list.obsidianConfigPath` is `~/Library/Application Support/obsidian/obsidian.json`.

After merge

Tag `v0.3.0` on the merge commit — `release.yml` handles mcpb bundles + npm publish via OIDC Trusted Publishing.

🤖 Generated with Claude Code

bezata and others added 2 commits April 25, 2026 01:21
Adds four new tools — vault.list, vault.current, vault.select,
vault.reset — that let an LLM enumerate the user's known Obsidian
vaults and switch between them for the session, without restarting
the server or threading vaultPath through every call.

Tool surface: 62 → 66 across 15 → 16 namespaces.

Fully backwards compatible:
- OBSIDIAN_VAULT_PATH behaves identically to v0.2.5.
- No existing tool signatures change.
- requireVaultPath precedence gains a middle slot —
  arg > session > env > error — that collapses to v0.2.5's
  arg > env > error when vault.select is never called.
- All 56 pre-existing tests pass unmodified; 55 new tests added.

Vault discovery:
- OBSIDIAN_VAULT_PATH                 — the default (documented, stable)
- OBSIDIAN_VAULT_<NAME>=path env vars — additional named vaults (new,
                                        documented, stable)
- obsidian.json parsing               — opt-out via
                                        KOBSIDIAN_VAULT_DISCOVERY=off
                                        (EXPERIMENTAL — Obsidian's
                                        undocumented registry format,
                                        stable since 1.0 but could
                                        change without notice; parse
                                        failures never crash the
                                        server, just surface via
                                        vault.list.obsidianConfigError)

Platform-path resolution anchored on os.homedir() with escape hatch
env var KOBSIDIAN_OBSIDIAN_CONFIG for portable installs / WSL:
- darwin  ~/Library/Application Support/obsidian/obsidian.json
- win32   %APPDATA%\obsidian\obsidian.json (with home fallback)
- linux   $XDG_CONFIG_HOME/obsidian/... → ~/.config/... → flatpak → snap

Security gating:
- KOBSIDIAN_VAULT_ALLOW=name1,name2,/abs/path — allowlist
- KOBSIDIAN_VAULT_DENY=...                    — denylist (applied after allowlist)
- OBSIDIAN_VAULT_PATH is NEVER filtered by allow/deny to prevent
  operator self-lockout.

Error UX: vault.select distinguishes "vault doesn't exist" (not_found)
from "vault exists but is blocked by operator gating" (unauthorized)
by resolving against the ungated pool, then applying allow/deny.

Other:
- vault.select on workspace.* / commands.* tools: surfaced as a note
  on every tool description via buildDescription() in create-server.ts,
  so LLMs know workspace/commands bridge to the live Obsidian instance
  (OBSIDIAN_API_URL) and are not affected by the filesystem selection.
- New pick-vault server prompt for slash-command discoverability.
- Fixed stale ingest-source prompt references to removed v0.2.5 tools
  (notes.insertAfterHeading / notes.append → notes.edit modes).

Cross-platform testing:
- 4 new test files: vault-paths, vault-discovery, vault-precedence,
  vault-tools (49 assertions across platform mocks, fixtures,
  precedence, real integration).
- 7 fixture obsidian.json files (macOS / Windows escaped backslashes /
  Linux / malformed / no-vaults / empty-vaults / with-unknown-keys).
- Platform-path tests feed mocked {platform, home, env} so they pass
  on every CI runner without needing the real OS.

Docs: README, AGENTS.md, docs/tools.md, docs/ENVIRONMENT.md,
docs/MIGRATION.md, CHANGELOG.md all updated. Version bumped to 0.3.0
across package.json, server.json, manifest.json.

Verification:
- bun run typecheck — clean
- bun run lint (biome) — clean
- bun run test — 111/111 passing (+55 vs v0.2.5)
- bun run build — stdio + http bundles built
- docs/tool-inventory.json regenerated — 66 tools

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Highlight v0.3.0's multi-vault support as the differentiator: no other
Obsidian MCP lets an LLM list and switch between vaults in-session.

- New docs/WORKSPACES.md — focused feature page: 60-second tour, all
  three discovery sources (with the EXPERIMENTAL label on obsidian.json
  parsing), precedence chain, operator gating, HTTP caveat, cross-links
  to ENVIRONMENT.md / MIGRATION.md / CHANGELOG.md / tools.md.
- README top-of-page callout linking to docs/WORKSPACES.md so the
  feature is visible to anyone scanning the hero block.
- Link WORKSPACES.md from docs/README.md (the docs landing page) with
  its own section above Architecture.
- Fixed stale MCP_tools=90 badge in the hero — now reflects the actual
  66 tools.

No code changes — typecheck + lint + test + inventory all still green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@bezata bezata force-pushed the feat/vault-selection branch from f5f8be1 to d2209ce Compare April 24, 2026 22:21
@bezata bezata merged commit d9ca738 into main Apr 24, 2026
3 checks passed
@bezata bezata deleted the feat/vault-selection branch April 24, 2026 22:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant