Skip to content

Architecture

Suavecito edited this page Jun 28, 2026 · 1 revision

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.

Packages

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

Components

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.

Dispatch pipeline

Every tools/call flows through seven layers; observability always fires, even on error:

  1. Transport — parse JSON-RPC, normalize to ToolRequest, compute args_hash.
  2. Auth — validate JWT or accept none (loopback only); build AuthContext with scopes.
  3. ACL — evaluate the tool's declarative ACL annotation against scopes + vault + paths; honor the global readOnly kill switch.
  4. Policy — (a) idempotency-key replay, (b) per-class rate limit, (c) HITL threshold → mint/consume elicit_token.
  5. Router — static name → impl lookup.
  6. Tool impl — Zod-parse args, run logic (may call native, a bridge, an embedding provider, SQLite).
  7. Observability — OTLP span, Prometheus counter + histogram, event emit, JSONL trace, event_log row.

Details and the per-error short-circuits live in Security and ACL and Observability.

IPC contracts

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).

Multi-vault registry

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.vaultOBSIDIAN_TC_DEFAULT_VAULT → config default → the sole vault → else invalid_input. Only search_vault fans out across vaults in v1.

Plugin discovery probe

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.

Scope boundary (v1.x vs converged engine)

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.

Protocol

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.

Clone this wiki locally