Skip to content

Architecture

Overview

ObsidianPalace is a single-container MCP server that provides AI clients bidirectional access to an Obsidian vault. It combines real-time vault synchronization via Obsidian Sync, semantic search via ChromaDB, and AI-assisted file placement via the Anthropic API.

The system is designed for a single user -- one Google account, one vault, one always-on VM.

System Architecture

flowchart TD
    subgraph Clients ["MCP Clients"]
        CD["Claude Desktop"]
        CI["Claude iOS"]
        CW["claude.ai"]
    end

    subgraph VM ["GCE e2-small"]
        subgraph Container ["Docker Container (supervisord)"]
            direction TB
            subgraph Python ["Python Server (FastAPI)"]
                MCP["MCP SSE Transport"]
                AUTH["OAuth 2.0 Middleware"]
                TOOLS["Tool Handlers"]
                MP["MemPalace (ChromaDB)"]
                PLACE["AI Placement"]
            end

            subgraph Node ["Node.js Sidecar"]
                OB["ob sync --continuous"]
            end
        end

        VAULT["/data/vault"]
        CHROMA["/data/chromadb"]
    end

    subgraph External ["External Services"]
        OS["Obsidian Sync"]
        ANTHROPIC["Anthropic API"]
        GOOGLE["Google OAuth"]
    end

    CD -->|"SSE"| MCP
    CI -->|"SSE"| MCP
    CW -->|"SSE"| MCP

    MCP --> AUTH
    AUTH -->|"Validate token"| GOOGLE
    AUTH --> TOOLS
    TOOLS -->|"Read / Write"| VAULT
    TOOLS -->|"Search"| MP
    TOOLS -->|"Placement"| PLACE
    PLACE -->|"Claude API"| ANTHROPIC
    MP --> CHROMA

    OB <-->|"Bidirectional sync"| OS
    OB <--> VAULT

Core Components

1. MCP SSE Transport

The entry point for all AI client connections. Implements the Model Context Protocol over Server-Sent Events, which is the transport required by Claude's Custom Connectors.

  • Endpoint: /sse -- SSE connection for MCP messages
  • Endpoint: /messages/ -- POST endpoint for client-to-server messages
  • Auth: MCP OAuth 2.1 authentication with PKCE (see below)

2. MCP OAuth 2.1 Authentication

Implements the full MCP OAuth 2.1 specification with Google as the identity provider. The server acts as its own Authorization Server and delegates user authentication to Google.

  • Discovery: /.well-known/oauth-protected-resource (RFC 9728) and /.well-known/oauth-authorization-server (RFC 8414)
  • Dynamic client registration: /register -- MCP clients register automatically on first connection
  • Authorization: /authorize -- redirects to Google OAuth, then back to the server
  • Token exchange: /token -- issues access/refresh tokens after Google authentication
  • Callback: /oauth2/callback -- handles the Google OAuth redirect

The server validates that the authenticated Google account matches the configured allowed_email. Only one user can access the system.

3. Vault Operations

Path-safe read/write operations against the vault directory. All paths are resolved and validated to prevent directory traversal attacks before any file I/O occurs.

  • Read: Returns the full markdown content of a note
  • Write: Creates or overwrites a note, creating parent directories as needed
  • List: Returns folders or note files at a given path

4. AI-Assisted Placement

When a note is written without an explicit path, the system calls Claude to determine where the note should be placed based on:

  • The vault's current folder structure
  • The note's title and content preview
  • Existing organizational patterns

Falls back to Inbox/ if no Anthropic API key is configured or the API call fails.

ChromaDB-backed semantic search over the vault contents. A file watcher monitors the vault directory for changes and re-indexes modified files to keep the search index current.

  • Index storage: Persistent disk at /data/chromadb
  • Memory: Requires 200-400 MB for a ~600 MB vault
  • Update strategy: File watcher triggers re-indexing on file changes

6. Obsidian Sync Sidecar

A Node.js process running ob sync --continuous from the obsidian-headless CLI. This keeps the vault directory synchronized with Obsidian's cloud sync service bidirectionally.

  • Credentials: Extracted from a one-time interactive ob login, stored in GCP Secret Manager, injected at container startup
  • Sync mode: Bidirectional -- changes from MCP clients propagate back to Obsidian apps and vice versa

Process Management

Three processes run inside a single container managed by supervisord:

Process Command Role
nginx nginx -g "daemon off;" SSL termination + reverse proxy (443 → 8080)
obsidian-sync ob sync --continuous Keeps vault in sync with Obsidian Sync
mcp-server uvicorn obsidian_palace.app:app Serves MCP tools over SSE

An entrypoint.sh script runs before supervisord to inject Obsidian Sync credentials and wait for the initial vault sync to pull at least one file. Supervisord ensures all three processes restart on failure and logs are captured.

Data Flow

Read a Note

sequenceDiagram
    actor U as Claude
    participant S as MCP Server
    participant A as OAuth
    participant V as Vault (/data/vault)

    U->>S: SSE connect (Bearer token)
    S->>A: Validate token
    A-->>S: OK (email matches)
    U->>S: call_tool("read_note", {path: "Projects/foo.md"})
    S->>V: Resolve & validate path
    V-->>S: File contents
    S-->>U: TextContent(markdown)

Write with AI Placement

sequenceDiagram
    actor U as Claude
    participant S as MCP Server
    participant AI as Anthropic API
    participant V as Vault (/data/vault)

    U->>S: call_tool("write_note", {content: "...", title: "Meeting Notes"})
    S->>V: list_folders()
    V-->>S: Folder tree
    S->>AI: "Where should this note go?"
    AI-->>S: "Work/Meetings/meeting-notes.md"
    S->>V: Write to Work/Meetings/meeting-notes.md
    V-->>S: Written successfully
    S-->>U: TextContent("Note written to: Work/Meetings/meeting-notes.md")

Infrastructure

Resource Spec Purpose
GCE Instance e2-small (2 vCPU, 2 GB RAM) Application runtime
Boot Disk 10 GB pd-standard, COS image Container-Optimized OS
Data Disk 20 GB pd-standard Vault files + ChromaDB index
Static IP Regional external IP Stable DNS target
DNS A record at domain registrar Points domain to static IP
Secret Manager 4 secrets OAuth, API keys
SSL Let's Encrypt (certbot) TLS termination

Estimated monthly cost: ~$15 (e2-small + persistent disk + static IP).