-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
Full design record: ARCHITECTURE.md. Tool surface: docs/G2.1-tools.md.
obsidian-tc is a polyglot monorepo. The server is a single Bun process; the companion plugin runs inside Obsidian; the native module is Rust linked over napi-rs.
| Package | Language | Owns |
|---|---|---|
packages/server |
TypeScript (Bun + Hono) | Transport, auth, ACL, policy, router, tool impls, bridges, observability |
packages/plugin |
TypeScript | Companion Obsidian plugin (/obsidian-tc/v1/* routes) |
packages/native |
Rust (napi-rs) | cosine similarity, Unicode tokenizer, BM25 term scoring |
packages/shared |
TypeScript | Shared Zod schemas + types |
Server-side (one process, soft TS-module boundaries): Transport (JSON-RPC over STDIO / Streamable HTTP), Auth (JWT / none), ACL (scope vs path/op), Policy (idempotency, rate limit, HITL), Router (name → impl), Tool impls, Plugin bridges (HTTP to the companion plugin), Embedding providers (Ollama / OpenAI / Voyage / Cohere), Native module, SQLite cache (one DB per vault), Observability emitters.
Obsidian-side (separate process): Companion plugin and the third-party Local REST API plugin.
Every tools/call flows through seven layers; observability always fires, even on error:
-
Transport — parse JSON-RPC, normalize to
ToolRequest, computeargs_hash. -
Auth — validate JWT or accept
none(loopback only); buildAuthContextwith scopes. -
ACL — evaluate the tool's declarative ACL annotation against scopes + vault + paths; honor the global
readOnlykill switch. -
Policy — (a) idempotency-key replay, (b) per-class rate limit, (c) HITL threshold → mint/consume
elicit_token. - Router — static name → impl lookup.
- Tool impl — Zod-parse args, run logic (may call native, a bridge, an embedding provider, SQLite).
-
Observability — OTLP span, Prometheus counter + histogram, event emit, JSONL trace,
event_logrow.
Details and the per-error short-circuits live in Security and ACL and Observability.
| Boundary | Wire | Auth |
|---|---|---|
| Server ↔ Companion plugin | HTTP over the Local REST API port (127.0.0.1:27124), path-versioned /obsidian-tc/v1/*
|
Shared bearer token (REST API key) |
| TypeScript ↔ Rust native | napi-rs FFI, synchronous, copy-on-boundary | In-process |
The native module never throws on a missing prebuild — a hand-written loader tries a local .node, then the platform sub-package, then the numerically identical pure-JS fallback (module.exports.nativeLoaded tells callers which backend is active).
Each configured vault is isolated at the storage layer: its own SQLite cache (<cacheDir>/cache.db), its own JSONL trace directory, its own embedding provider, and its own slice of the global ACL. Vault resolution order: explicit args.vault → OBSIDIAN_TC_DEFAULT_VAULT → config default → the sole vault → else invalid_input. Only search_vault fans out across vaults in v1.
At server start (and on reload_vault) the server fires GET /obsidian-tc/v1/probe. The companion plugin reports installed plugins and versions via Obsidian's app.plugins API. Results are cached in memory for the vault's lifetime. See Plugin Bridges.
Shipped v1.x is an access + search MCP: vault I/O, the six search tools plus an embedding/index substrate, structured formats, bridges, capture, and memory entities. Retrieval intelligence — GraphRAG, hybrid BM25 + vector + RRF fusion, rerank, and a typed-atom (MemIR) memory substrate — is the converged memory engine direction tracked downstream (Linear THE-233 / THE-235), not part of the v1.x line. The earlier reserved Python ML sidecar and the native kmeansAssign / actrDecayScore stubs were removed.
MCP spec 2025-11-25. STDIO for local; Streamable HTTP for HTTP modes (no SSE shim). Server advertises tools only; resources and native elicitation are deferred to a v1.x follow-up. HITL uses a custom single-use elicit_token pattern that works with any MCP client.