Skip to content
GitHubBuy Me A Coffee

MCP

The Model Context Protocol (MCP) is a standard for connecting AI agents to external tools and data sources. Vault Operator plays two MCP roles at the same time:

  • In-plugin MCP server. Vault Operator exposes the vault, memory, and history to external clients like Claude Desktop, ChatGPT, and Perplexity.
  • External MCP client. Vault Operator calls remote MCP servers you have configured, so their tools and resources become available to the in-Obsidian agent.

Two directions

On the left, Vault Operator acts as a client and reaches out to MCP servers you have configured. A GitHub server that can search issues, a database server that can run queries, whatever tools those servers expose. They become available to the agent alongside the activated tool groups.

On the right, Vault Operator itself is the server. External clients connect to it and get access to the vault, the persistent memory layer, and the conversation history. Each call carries a source_interface tag (obsilo, claude-ai, claude-code, chatgpt, perplexity, unknown) so memory and history stay separable per surface. See Unified Chat Memory for the cross-surface UX.

Client side

You configure external MCP servers in Settings > Vault Operator > Customize > Connectors. Each server needs a transport type and connection details:

  • Streamable HTTP is the first-party transport for modern remote servers. It supports optional authentication: a header bearer token, or OAuth, where the SDK opens your browser to sign in and stores the returned tokens encrypted and refreshes them automatically.
  • SSE is supported as a first-party fallback for older servers that have not migrated yet.
  • stdio launches a local command-line MCP server directly, on Desktop only. This is the one client-side path where the plugin spawns a host process, so it is fenced in by the layers described under Local stdio servers below.

When Vault Operator connects to a server, it discovers the available tools and resources through MCP's standard discovery protocol. Those tools appear in the agent's tool list alongside the activated tool groups. The agent calls them like any other tool and does not need to know they run in a separate process.

The MCP client is deliberately lean and does not reconnect on its own. Enabled connectors reconnect when you restart Obsidian, and you can reconnect a server on demand from Settings > Vault Operator > Customize > Connectors (the Reconnect action). If a server becomes unreachable mid-session, its tools stay listed but calls fail until you reconnect.

Resources, a second MCP concept alongside tools, are also supported. If an MCP server exposes resources like documentation files or database schemas, Vault Operator can list and read them. The agent pulls in resource content as additional context when needed.

Local stdio servers

A stdio server is a local command Vault Operator starts itself, talking MCP over the process's standard input and output. It is the one client-side path where the plugin spawns a host process, so it is deliberately narrow and fail-closed. Several independent layers stand between a configured entry and a running process:

  • Desktop only. Mobile has no Node runtime, so the stdio branch is unavailable there (Platform.isDesktopApp in src/core/mcp/McpClient.ts).
  • Device-local config, never synced. stdio servers live in a per-device store outside the vault (~/.obsidian-agent/devices/<id>/), not in the synced settings. A config injected through sync or a shared vault never exists on another device, so it can never auto-launch there.
  • Per-device human trust. The first launch needs an explicit "Trust and run" confirmation on that device. Without it the spawn is refused. The trust is bound to a fingerprint of the exact command and its arguments, so editing the command later prompts again.
  • Command allowlist. Only a bare node or npx may be spawned (assertStdioCommandAllowed in src/core/security/spawnAllowlist.ts: no path separator, so a /tmp/evil/node basename spoof is rejected, and no shell metacharacters). The launch itself uses the MCP SDK's StdioClientTransport (cross-spawn with shell: false), so arguments are never shell-interpreted.

The child process inherits a fixed environment allowlist (paths, locale, and known CLI-config locations, no credentials). Any secret-named variable you set for the server is encrypted at rest and decrypted only at spawn. The agent (the LLM) cannot add, edit, trust, reconnect, or test a stdio server: manage_mcp_server refuses stdio for every mutating action, so there is no path from model output to a process spawn. Only you manage stdio servers, in Settings. The Connectors guide has the setup steps.

Server side

The McpBridge (src/mcp/McpBridge.ts) runs an HTTP server on localhost (default port 27182) that speaks the MCP Streamable HTTP protocol. The tool surface is organized in four tiers:

TierToolsWhat they do
Readget_context, search_vault, read_notes, get_vault_note_metadata, get_vault_implicit_edgesRetrieve vault, ontology, and structural information.
Memoryrecall_memory, save_to_memory, update_memory (deprecated)Cross-surface memory access. update_memory is kept for legacy clients and routes to save_to_memory internally.
Historysave_conversation, close_conversation, search_history, sync_sessionPersist a conversation as a living document, or look up past chats.
Writewrite_vault, execute_vault_opwrite_vault creates, edits, and deletes files in batch. execute_vault_op dispatches the read-side agent tools from the activated tool groups; the list is generated at runtime. Write tools and agent-internal tools are fail-closed from this dispatcher, and dedicated MCP tools cover the cases where writes are wanted.

The get_context tool is meant to be called first in every conversation. It returns the user profile, memory, behavioral patterns, vault statistics, available skills, and rules. The same context Vault Operator's internal agent gets from its system prompt. Under strict source isolation (Settings > Vault Operator > Agents > Memory), the response for non-obsilo callers omits memory, soul, skills, and rules, and only vault stats and structural info come through.

All tool calls dispatch directly to Vault Operator's services within Obsidian's renderer process. No IPC overhead. The HTTP handler calls the same functions the internal agent uses.

The server authenticates every request with a bearer token, auto-generated on first run and written to ~/.obsidian-agent/mcp-token. It binds only to the 127.0.0.1 loopback interface, with a CORS origin and a Host-header allowlist as defense in depth against DNS rebinding. Clients that speak MCP over stdio rather than HTTP, Claude Desktop being the common one, go through a thin proxy, mcp-server-worker.js (src/mcp/mcp-server-worker.ts): Claude Desktop launches it, and it forwards each stdio request to the local HTTP server on port 27182, reading the same token file to authenticate. The "Configure Claude Desktop" button in Settings writes this proxy into Claude Desktop's config for you.

The search_vault tool on the MCP server uses the same knowledge layer pipeline described on the knowledge layer page. External agents get the same retrieval (vector search, graph expansion, implicit connections, reranking) as the internal agent. The write_vault tool supports batch operations, so create, edit, append, and delete can happen in a single call. Per-call content caps and an aggregate cap protect against runaway writes.

Living documents and source-interface tagging

Multiple save_conversation calls within 30 minutes from the same source interface append to a single thread (thread-YYYY-MM-DD-{6-hex}) instead of creating new conversations. Living documents are append-only. Memory extraction runs incrementally on the new turns rather than re-processing the whole thread.

Every persisted message carries the source_interface tag. The history sidebar groups conversations by source so you can answer "what did Claude Desktop and I work on yesterday?" separately from "what came in via ChatGPT?". Settings > Vault Operator > Agents > Memory has a Cross-Surface Sync section that controls whether memory writes from non-Vault Operator surfaces are accepted, and whether reads from non-Vault Operator surfaces see your full memory layer.

Remote access

The local HTTP server is only reachable on your machine. For remote access (from Claude Desktop on a different device, or from the Claude web app), the RelayClient (src/mcp/RelayClient.ts) connects to a Cloudflare Workers relay.

The relay uses HTTP long-polling. The client polls for incoming requests, processes them locally, and sends responses back. Authentication uses a token embedded in the URL. No data is stored on the relay. It is a passthrough.

Long-polling is a deliberate choice: Obsidian's content security policy blocks WebSocket connections, and plain HTTPS requests stay allowed. The worker itself deploys without Wrangler or a terminal. Vault Operator ships the worker code and uploads it to Cloudflare through the Cloudflare REST API (src/mcp/CloudflareDeployer.ts, via Obsidian's requestUrl), using a Cloudflare API token you provide in Settings.

Remote access requires Obsidian to be running on your machine. The relay cannot access your vault on its own. It only forwards requests to the plugin.

The RelayClient handles the connection lifecycle: initial connection, reconnection with exponential backoff when the relay becomes unreachable, and clean shutdown when the plugin unloads. A callback notifies the Settings UI of the current tunnel URL so you can copy it into Claude Desktop's MCP configuration. Poll interval is bounded by the Cloudflare Workers Free Plan request budget; intervals and reconnect delays are named constants in the source.

System context

External agents connecting via MCP do not automatically know how to behave. The buildPrompts function (src/mcp/prompts/systemContext.ts) generates context about your vault: size, structure, installed plugins, active rules. External agents receive this as part of the get_context response, which gives them enough background to be useful without manual setup.

Practical use

You can use Claude Desktop as your primary interface while Vault Operator handles the vault integration. Or you can run Obsidian as the IDE while ChatGPT, Perplexity, or any other MCP-aware client reads from the same memory layer. The protocol is the same in both directions.

The MCP server only runs while Obsidian is open. If you close Obsidian, external clients lose access until you reopen it. The relay client reconnects automatically when Obsidian comes back up.

Session sync vs save_conversation

save_conversation is the canonical way external clients persist a turn. It supports living documents, source tagging, and incremental memory extraction.

sync_session is the legacy bulk path: an external client sends an entire transcript at the end of a conversation and Vault Operator stores it as a single conversation. It is kept for clients that do not yet support per-turn save_conversation. Both paths apply per-message length caps and a maximum messages-per-call limit to bound resource use.