16 min read MD App mdapp/0.1.0

MCP Architecture Deep Dive

Product Boundaries

This project consists of three distinct products that work together:

Product What It Is Runs Where User Interaction
MD Bot Standalone MCP server + CLI binary Background process, terminal, npx mdbot Agents talk to it; users configure it
MD App Desktop markdown editor (Electron) Foreground desktop app Users edit, review, organise documents
mdlib.dev SaaS publishing platform Web (existing) Users publish, share, and read documents
┌─────────────┐     ┌─────────────────────────────────────────────┐     ┌─────────────┐
│  AI Agents   │     │              User's Machine                 │     │  mdlib.dev  │
│  (Claude,    │     │                                             │     │  (Web SaaS) │
│   Cursor,    │     │  ┌───────────┐    IPC    ┌──────────────┐  │     │             │
│   Copilot)   │────▶│  │  MD Bot   │◀────────▶│   MD App     │  │     │  Published  │
│              │stdio│  │  (MCP     │           │  (Electron   │  │     │  documents  │
│              │     │  │   server) │           │   desktop    │  │     │  Analytics  │
│              │     │  │           │           │   editor)    │  │     │  Sharing    │
│              │     │  └─────┬─────┘           └──────┬───────┘  │     └──────┬──────┘
│              │     │        │                        │          │            │
│              │     │        ▼                        ▼          │            │
│              │     │  ┌─────────────────────────────────────┐   │   HTTPS    │
│              │     │  │  Shared: ~/.mdbot/                  │   │◀──────────▶│
│              │     │  │  ├── library.db (SQLite metadata)   │   │            │
│              │     │  │  ├── documents/ (flat .md files)    │   │            │
│              │     │  │  └── config.json (settings)         │   │            │
│              │     │  └─────────────────────────────────────┘   │            │
│              │     │                                             │            │
└─────────────┘     └─────────────────────────────────────────────┘            │

Key principle: MD Bot and MD App share the same data directory (~/.mdbot/) but are independently runnable. You can use MD Bot without MD App (agent-only workflow). You can use MD App without MD Bot (manual editing). Together they’re better.


1. Why MCP Is the Killer Feature

Every markdown editor competes on editing experience. That’s a race to the bottom — CodeMirror 6, Monaco, and Prosemirror are all excellent and freely available. The editing layer is commoditised.

What isn’t commoditised is the relationship between your document library and AI agents. Today, Obsidian has 5+ fragmented community MCP plugins, each with different transport mechanisms, different tool sets, and significant bugs. None of them have a publishing pipeline. None of them were designed from the ground up as a first-party experience.

MD Bot should be the best way for any AI agent to interact with a user’s markdown document library. Not bolted on — built in from day one. MD App then becomes the human-facing companion for reviewing, editing, and organising what agents produce.


2. Competitive Intelligence: What Obsidian’s MCP Plugins Do (and Don’t)

The Fragmented Landscape

Plugin Tools Transport Search Write Key Weakness
Claudesidian (Nexus) 34 tools via 2 meta-tools IPC (sockets/pipes) Semantic (local embeddings) Full CRUD Intermittent connection drops; meta-tool indirection adds latency
mcp-obsidian (Smithery) Undocumented NPX/stdio Full-text only Read-only No write support; crashes on Windows 11; minimal docs
obsidian-mcp-plugin (aaronsb) 40+ across 8 groups HTTP (port 3001) Advanced operators Full CRUD Daily notes hardcoded; graph.path broken
MCP Tools (jacksteamdev) 3 capabilities HTTP (hardcoded port 27124) Semantic (via Smart Connections plugin) Templates only Hardcoded port; requires 3 prerequisite plugins; schema validation issues

What They All Get Wrong

  1. Fragmentation itself — Users must choose between plugins, each with different capabilities and bugs. No unified experience.
  2. Obsidian must be running — 3 of 4 require the Obsidian app to be open. If you just want Claude to read your docs while you’re in the terminal, tough luck.
  3. No publish pipeline — None of them can publish to the web. Obsidian Publish is a walled garden with no API for agents.
  4. Setup complexity — Claudesidian requires understanding IPC transports. jacksteamdev requires installing 3 other plugins first. aaronsb requires HTTP port configuration.
  5. No review workflow — Agents can write documents, but there’s no mechanism for the user to review, accept/reject, or provide feedback through the MCP protocol.
  6. Meta-tool indirection — Claudesidian wraps 34 tools behind 2 meta-tools (getTools, useTools), adding a layer of abstraction that confuses agents and adds latency.

What We Should Steal

  • aaronsb’s semantic tool grouping — Organising tools into logical groups (vault, edit, view, graph) is good UX for agents
  • Claudesidian’s local semantic search — Offline embeddings via MiniLM-L6-v2 is the right approach for privacy
  • aaronsb’s advanced search operatorstag:, path:, content: filters are powerful
  • Claudesidian’s memory/workspace concept — Persisting agent context across sessions is forward-thinking

3. MCP Protocol Fundamentals (What We’re Building On)

Three Core Primitives

Primitive Control Purpose Our Use
Tools Model-controlled (LLM decides) Executable functions CRUD operations, search, publish
Resources App-controlled (client decides) Passive data sources Browse document library, read documents
Prompts User-controlled (explicit invocation) Instruction templates “Summarise this project”, “Write a doc like my previous ones”

Transport Options

Transport Best For Our Choice
stdio Local, single-client, low latency Primary — Claude Desktop, Claude Code, Cursor all support this
HTTP + SSE Multi-client, remote access Secondary — For scenarios where the app runs as a background service
Streamable HTTP Long-running operations Future — For large batch operations or sync

Decision: stdio as primary transport. This is what Claude Desktop, Claude Code, and Cursor all expect for local servers. The app registers itself in the client’s config file and spawns as a child process. Zero network configuration, zero port conflicts (unlike Obsidian’s plugins).

Discovery: How Agents Find Us

MCP has no auto-discovery. Agents find servers through config files:

Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json (Mac) Claude Code: .mcp.json in project root or ~/.claude/settings.json Cursor: .cursor/mcp.json in project root

{
  "mcpServers": {
    "mdapp": {
      "command": "mdbot",
      "args": ["--stdio"],
      "env": {
        "MDLIB_API_KEY": "optional_api_key"
      }
    }
  }
}

Key UX decision: The desktop app should include a one-click “Connect to Claude” button that writes this config automatically. No manual JSON editing. This alone puts us ahead of every Obsidian plugin.


4. Proposed MCP Server Architecture

Design Principles

  1. Flat, direct tools — No meta-tool indirection (unlike Claudesidian). Each tool is directly callable.
  2. snake_case naming with mdbot_ prefix — Follows the 90%+ convention; prefix groups our tools in listings.
  3. Markdown in, markdown out — Following Notion’s lead: represent content as markdown, not JSON. Better token efficiency.
  4. Read and write by default, with read-only mode — Users can restrict to read-only in settings.
  5. Hints on every toolreadOnlyHint, destructiveHint annotations so agents understand impact.
  6. Flattened schemas — All parameters at top level, no nested objects. Easier for LLMs to reason about.

4.1 Tools (Model-Controlled)

Document CRUD

mdbot_list_documents
├── Parameters:
│   ├── project (string, optional) — Filter by project name
│   ├── tags (string[], optional) — Filter by tags (AND logic)
│   ├── query (string, optional) — Full-text search
│   ├── sort_by (enum: "modified", "created", "title") — Default: "modified"
│   ├── limit (int, default: 20, max: 100)
│   └── offset (int, default: 0)
├── Returns: Array of {slug, title, project, tags, word_count, modified_at, created_at}
└── Hints: readOnlyHint=true

mdbot_get_document
├── Parameters:
│   ├── slug (string, required) — Document slug/identifier
│   └── include_metadata (bool, default: true) — Include frontmatter and stats
├── Returns: {slug, title, markdown, project, tags, word_count, modified_at, published_url?}
└── Hints: readOnlyHint=true

mdbot_create_document
├── Parameters:
│   ├── title (string, required)
│   ├── markdown (string, required) — Document content
│   ├── project (string, optional) — Assign to project
│   ├── tags (string[], optional)
│   └── template (string, optional) — Template slug to base on
├── Returns: {slug, title, file_path, created_at}
└── Hints: readOnlyHint=false

mdbot_update_document
├── Parameters:
│   ├── slug (string, required)
│   ├── markdown (string, optional) — Full replacement of content
│   ├── title (string, optional)
│   ├── project (string, optional)
│   ├── tags (string[], optional)
│   ├── append (string, optional) — Append to end instead of replacing
│   ├── section (string, optional) — Heading text to target (e.g., "## Architecture")
│   └── section_content (string, optional) — New content for the targeted section
├── Returns: {slug, title, modified_at, word_count}
├── Hints: readOnlyHint=false
└── Notes: Three modes — full (markdown), append, section targeting. Section mode
    replaces everything from the matched heading to the next heading of equal/higher level.

mdbot_delete_document
├── Parameters:
│   ├── slug (string, required)
│   └── confirm (bool, required) — Must be true; safety gate
├── Returns: {deleted: true, slug}
└── Hints: destructiveHint=true

Search & Discovery

mdbot_search
├── Parameters:
│   ├── query (string, required) — Natural language or keyword query
│   ├── mode (enum: "fulltext", "semantic") — Default: "fulltext"
│   ├── project (string, optional) — Scope to project
│   ├── tags (string[], optional) — Filter by tags
│   ├── limit (int, default: 10, max: 50)
│   └── include_snippets (bool, default: true) — Return matching excerpts
├── Returns: Array of {slug, title, score, snippet?, project, tags}
└── Hints: readOnlyHint=true

mdbot_list_projects
├── Parameters: none
├── Returns: Array of {name, document_count, last_modified}
└── Hints: readOnlyHint=true

mdbot_list_tags
├── Parameters:
│   └── project (string, optional) — Scope to project
├── Returns: Array of {tag, count}
└── Hints: readOnlyHint=true

Publishing (mdlib.dev Integration)

mdbot_publish
├── Parameters:
│   ├── slug (string, required) — Local document to publish
│   ├── mdlib_project (string, optional) — mdlib.dev project
│   ├── mdlib_tags (string[], optional) — mdlib.dev tags
│   ├── public (bool, default: true) — Public or private on mdlib.dev
│   └── custom_slug (string, optional) — Custom URL slug on mdlib.dev
├── Returns: {url, mdlib_slug, published_at, edit_key}
├── Hints: readOnlyHint=false
└── Notes: Requires mdlib.dev API key in settings

mdbot_unpublish
├── Parameters:
│   ├── slug (string, required) — Local document slug
│   └── confirm (bool, required)
├── Returns: {unpublished: true}
└── Hints: destructiveHint=true

mdbot_get_publish_status
├── Parameters:
│   └── slug (string, required) — Local document slug
├── Returns: {published, url?, view_count?, last_synced?, mdlib_slug?}
└── Hints: readOnlyHint=true

mdbot_sync_from_mdlib
├── Parameters:
│   ├── mdlib_slug (string, optional) — Pull specific document
│   └── project (string, optional) — Pull all docs from mdlib.dev project
├── Returns: Array of {slug, title, action: "created"|"updated"|"unchanged"}
└── Hints: readOnlyHint=false

Templates & Workflow

mdbot_list_templates
├── Parameters: none
├── Returns: Array of {slug, title, description, tags}
└── Hints: readOnlyHint=true

mdbot_get_template
├── Parameters:
│   └── slug (string, required)
├── Returns: {slug, title, markdown, variables: string[]}
└── Hints: readOnlyHint=true

mdbot_create_from_template
├── Parameters:
│   ├── template (string, required) — Template slug
│   ├── title (string, required)
│   ├── variables (object, optional) — Key-value pairs to fill template
│   └── project (string, optional)
├── Returns: {slug, title, file_path}
└── Hints: readOnlyHint=false

Diff & Version History

mdbot_get_versions
├── Parameters:
│   ├── slug (string, required)
│   └── limit (int, default: 10)
├── Returns: Array of {version_id, timestamp, word_count, summary?}
└── Hints: readOnlyHint=true

mdbot_get_diff
├── Parameters:
│   ├── slug (string, required)
│   ├── from_version (string, optional) — Default: previous version
│   └── to_version (string, optional) — Default: current
├── Returns: {diff_markdown, additions, deletions, from_version, to_version}
└── Hints: readOnlyHint=true

mdbot_restore_version
├── Parameters:
│   ├── slug (string, required)
│   ├── version_id (string, required)
│   └── confirm (bool, required)
├── Returns: {slug, restored_to: version_id, new_version_id}
└── Hints: destructiveHint=true

Total: 19 tools — Comprehensive but focused. Compare: Notion has 22, Obsidian’s best plugin has 40+ (many redundant).

4.2 Resources (App-Controlled)

Resources let MCP clients browse and include document content as context without the agent explicitly calling tools.

Resources:
├── mdbot://documents                    — List all documents (paginated)
├── mdbot://documents/{slug}             — Single document content
├── mdbot://projects                     — List all projects
├── mdbot://projects/{name}              — Documents in a project
├── mdbot://tags/{tag}                   — Documents with a tag
├── mdbot://templates                    — Available templates
├── mdbot://templates/{slug}             — Template content
└── mdbot://published                    — All published documents with URLs

Resource Templates (dynamic):
├── mdbot://documents/{slug}             — Any document by slug
├── mdbot://search/{query}               — Search results as a resource
└── mdbot://projects/{name}/recent       — Recent docs in a project

Resources support subscriptions — clients can subscribe to mdbot://documents/{slug} and get notified when a document changes. This enables real-time context updates.

4.3 Prompts (User-Invoked)

Prompts are reusable instruction templates users can invoke from Claude’s UI.

mdbot_write_similar
├── Description: "Write a new document in the same style as an existing one"
├── Parameters:
│   ├── reference_slug (string) — Document to use as style reference
│   └── topic (string) — What the new document should be about
└── Returns: Prompt with reference document content + instructions

mdbot_review_document
├── Description: "Review a document for quality, accuracy, and completeness"
├── Parameters:
│   └── slug (string) — Document to review
└── Returns: Prompt with document content + review checklist

mdbot_summarize_project
├── Description: "Summarize all documents in a project"
├── Parameters:
│   └── project (string) — Project to summarise
└── Returns: Prompt with all project document titles/summaries + synthesis instructions

mdbot_changelog
├── Description: "Generate a changelog from recent document changes"
├── Parameters:
│   ├── days (int, default: 7) — Look back period
│   └── project (string, optional)
└── Returns: Prompt with version diffs + changelog formatting instructions

5. mdlib.dev Integration Architecture

The Publish Pipeline

┌─────────────┐     ┌──────────────┐     ┌─────────────┐
│  AI Agent    │────▶│  MD Bot      │────▶│  mdlib.dev  │
│  (Claude,    │stdio│  (MCP server)│HTTPS│  (Web SaaS) │
│   Cursor)    │     │              │     │             │
│              │     │  Handles:    │     │  Published  │
│  MCP tools:  │     │  • CRUD      │     │  documents  │
│  create_doc  │     │  • Search    │     │  Analytics  │
│  publish     │     │  • Publish   │     │  Sharing    │
└─────────────┘     └──────────────┘     └─────────────┘
                           │
                     IPC (shared DB)
                           │
                    ┌──────────────┐
                    │  MD App      │
                    │  (Desktop)   │
                    │              │
                    │  Handles:    │
                    │  • Editing   │
                    │  • Review UI │
                    │  • Settings  │
                    └──────────────┘

Who does what in the publish flow:

  • MD Bot calls the mdlib.dev API, stores edit keys, manages sync state
  • MD App provides the UI: “Publish” button, status indicators, analytics display
  • mdlib.dev hosts the published document, serves it to readers

Sync Model

Local → mdlib.dev (Publish):

  • User or agent calls mdbot_publish
  • App sends markdown + metadata to mdlib.dev API
  • App stores: mdlib_slug, edit_key, published_at, last_synced in local SQLite
  • Subsequent updates use update_document with stored edit_key (anonymous) or API key (authenticated)

mdlib.dev → Local (Pull):

  • Agent calls mdbot_sync_from_mdlib to pull documents published via mdlib.dev’s own API/MCP
  • Use case: another agent published a document directly to mdlib.dev; pull it into local library for editing
  • App calls mdlib.dev’s list_documents and get_document APIs
  • Creates local .md files with frontmatter linking back to mdlib.dev slug

Conflict Resolution:

  • Simple last-write-wins with version history
  • If both sides changed: create a local version with conflict markers, let user resolve
  • Never silently overwrite — always preserve history

Authentication Flow

1. [MD App] User clicks "Connect mdlib.dev" in settings UI
2. [MD App] Opens browser to mdlib.dev/dashboard/settings
3. [User]   Copies API key (or app uses OAuth if mdlib.dev adds it later)
4. [MD App] Stores API key in OS keychain (macOS Keychain / Windows Credential Manager)
5. [MD App] Writes key reference to ~/.mdbot/config.json (so MD Bot can access it)
6. [MD Bot] All subsequent publish/sync operations use this key
7. [MD Bot] Anonymous publish still available without key (7-day expiry on mdlib.dev)

6. How This Beats Obsidian’s MCP Plugins

Capability Best Obsidian Plugin MD Bot + MD App
Setup Edit JSON config + install plugin(s) MD App: One-click “Connect to Claude” button
Transport HTTP with port conflicts, or IPC with drops MD Bot: stdio (zero config, zero port conflicts)
Must be running? Yes, Obsidian must be open (3/4 plugins) MD Bot: Runs independently as background process; MD App doesn’t need to be open
Tool design 40+ tools or 2 meta-tools MD Bot: 19 focused, flat, well-documented tools
Publish to web Not possible MD Bot: One tool call: mdbot_publish → mdlib.dev
Semantic search Requires extra plugins (Smart Connections) MD Bot: Built-in, offline, first-party
Version history Not available via MCP MD Bot: mdbot_get_versions, mdbot_get_diff; MD App: Visual diff UI
Resources Not exposed MD Bot: Full resource protocol with subscriptions
Prompts Not available MD Bot: 4 purpose-built prompts for common workflows
Review workflow None MD Bot: Creates doc → MD App: User reviews, accepts/rejects sections

7. Agent Workflow Examples

Example 1: “Write a spec like my previous ones”

Agent                           MD Bot (MCP Server)
  │                                    │
  ├─ mdbot_search ─────────────────────▶ query="spec", project="Architecture"
  │◀─ [{slug: "auth-spec", ...}, ...]──┤
  │                                    │
  ├─ mdbot_get_document ───────────────▶ slug="auth-spec"
  │◀─ {markdown: "# Auth Spec\n..."}──┤
  │                                    │
  │  [Agent studies the format]        │
  │                                    │
  ├─ mdbot_create_document ────────────▶ title="Payments Spec", markdown="..."
  │◀─ {slug: "payments-spec"}──────────┤
  │                                    │
  └─ Done. Document in user's library. │

Example 2: “Publish my latest draft”

Agent                           MD Bot                     mdlib.dev
  │                                    │                       │
  ├─ mdbot_list_documents ─────────────▶ sort_by="modified"    │
  │◀─ [{slug: "draft-v3", ...}]────────┤                       │
  │                                    │                       │
  ├─ mdbot_publish ────────────────────▶ slug="draft-v3"       │
  │                                    ├─ POST /v1/docs ──────▶│
  │                                    │◀─ {url, edit_key} ────┤
  │◀─ {url: "https://mdlib.dev/..."}───┤                       │
  │                                    │                       │
  └─ "Published! Here's the link: ..." │                       │

Example 3: “What have I been working on this week?”

Agent                           MD Bot (MCP Server)
  │                                    │
  ├─ mdbot_list_documents ─────────────▶ sort_by="modified", limit=20
  │◀─ [list of recently modified docs]─┤
  │                                    │
  ├─ mdbot_summarize_project (prompt)──▶ project="Q1 Planning"
  │◀─ [prompt with all doc summaries]──┤
  │                                    │
  │  [Agent synthesises weekly summary]│
  │                                    │
  ├─ mdbot_create_document ────────────▶ title="Week 10 Summary", ...
  │◀─ {slug: "week-10-summary"}────────┤
  │                                    │
  └─ "Here's your weekly summary."     │

8. Technical Implementation Notes

Shared Storage (used by both MD Bot and MD App)

~/.mdbot/
├── config.json              — Shared settings, mdlib.dev credentials reference
├── library.db               — SQLite: document metadata, tags, projects, versions, publish state
├── embeddings.db            — SQLite + sqlite-vec: semantic search vectors
└── documents/
    ├── my-spec.md           — Raw markdown files (user's actual documents)
    ├── my-spec.md.versions/ — Version snapshots (gzipped)
    │   ├── 2026-03-05T10:00:00.md.gz
    │   └── 2026-03-04T15:30:00.md.gz
    └── ...

Why flat .md files on disk (not a database)?

  • Users can open files in any editor; not locked into MD App
  • Git-friendly if users want to version control
  • Easy backup (it’s just a folder)
  • Agents can also read files directly if MCP isn’t available

Process Model (Decision: Standalone MD Bot)

┌──────────────────┐    ┌──────────────────────┐
│ MD App           │    │ MD Bot               │◀──── Claude Desktop (stdio)
│ (Electron)       │    │ (standalone binary)   │◀──── Claude Code (stdio)
│                  │    │                      │◀──── Cursor (stdio)
│ Responsibilities:│    │ Responsibilities:    │
│ • Editor UI      │◀──▶│ • MCP protocol       │
│ • Preview/render │IPC │ • SQLite CRUD        │
│ • Settings UI    │    │ • File I/O           │
│ • Review mode    │    │ • mdlib.dev API      │
│ • Deep link      │    │ • Search (FTS +      │
│   handler        │    │   semantic)          │
│ • "Connect to    │    │ • Version snapshots  │
│   Claude" button │    │ • OS notifications   │
└──────────────────┘    └──────────────────────┘
         │                        │
         └────────┬───────────────┘
                  ▼
         ~/.mdbot/ (shared data)

Why separate binaries?

  • MD Bot works without MD App — agents access your library even when the desktop editor isn’t open
  • MD App works without MD Bot — manual editing doesn’t require the MCP server running
  • Lighter weight: npx mdbot has no Electron overhead
  • Can be distributed independently: npx mdbot --library ~/Documents/mdbot
  • MD App communicates with MD Bot via IPC when both are running

MD Bot: SDK & Dependencies

  • MCP SDK: @modelcontextprotocol/server (TypeScript, production-ready)
  • Transport: stdio (primary), HTTP+SSE (optional)
  • Search: SQLite FTS5 (full-text), sqlite-vec + ONNX Runtime (semantic)
  • Embeddings model: all-MiniLM-L6-v2 via @xenova/transformers (same as Claudesidian, proven approach)
  • File watching: chokidar (for detecting external edits to .md files)
  • Schema validation: Zod v4 (required peer dependency of MCP SDK)
  • Notifications: node-notifier (cross-platform OS notifications with deep links)

MD App: SDK & Dependencies

  • Framework: Electron (cross-platform desktop)
  • Editor: CodeMirror 6 (markdown editing)
  • Renderer: unified/remark pipeline (consistent with mdlib.dev rendering)
  • UI framework: TBD (React, Svelte, or Solid)
  • Diff view: TBD (diff2html or custom)
  • IPC with MD Bot: Unix domain socket or named pipe

9. Design Decisions (Resolved)

Decision 1: Update strategy — Heading-based section targeting

mdbot_update_document supports three modes:

  • markdown (full replacement) — Default. Agent provides complete new content.
  • append — Adds content to the end of the document. Covers quick additions.
  • section + section_content — Target a specific section by its heading text (e.g., section="## Architecture"). Agent only reads and returns the section being changed, not the whole document.

Rationale: AI agents are excellent at identifying sections by heading — it’s deterministic (headings are unique within a document), maps naturally to markdown structure, and avoids the fragility of Notion-style text-range matching. Section targeting also solves the large document problem: agents only need to handle the section they’re changing.

Decision 2: Projects & tags — Create freely, configurable later

Agents can create new projects and tags on the fly. This reduces friction and avoids agents failing because a needed tag doesn’t exist. In a future release, add a user setting to restrict to existing-only for users who want tighter control over their library organisation.

Decision 3: Concurrent access — Last-write-wins + auto-snapshot

Every write (from the app UI or MCP) automatically creates a version snapshot before saving. If both sides edit simultaneously, the last writer wins — but nothing is ever lost because the previous version is always preserved. Simple, pragmatic, recoverable.

Rationale: File locking causes agents to fail randomly when the user has a doc open. Optimistic locking adds retry complexity agents handle poorly. Queue + notify defeats autonomous agents. For the 95% case (agent writes while user isn’t editing that specific file), last-write-wins just works. Version history is the safety net for the 5% case. Optimistic locking can be added as a future upgrade.

Decision 4: Open source — Open protocol, closed implementation

Publish the complete MCP tool/resource schema as an open specification so anyone can build compatible servers. Keep MD Bot’s implementation proprietary. This gives the community transparency and interoperability without allowing direct forks of our codebase. The open spec also positions MD App/mdlib.dev as the standard for markdown-agent interaction.

MD Bot sends a system notification (via node-notifier) with a deep link (mdbot://documents/my-spec). The desktop app registers the mdbot:// URL scheme during installation. User clicks the notification when ready; the app opens to that document.

Rationale: Simpler than IPC between MD Bot and the desktop app (no sockets/pipes needed). Less intrusive for the user (notification vs. forced window focus). The URL scheme registration is needed anyway for mdlib.dev integration (clicking links from the web).

Decision 6: Large documents — Smart truncation + configurable threshold

Default threshold: 32KB (~8,000 tokens). Below that, return the full document (covers 95% of cases). Above it, truncate at a heading boundary (not mid-paragraph) and include: “Document truncated at 32KB. Full content available at mdbot://documents/{slug}. Use section parameter to read specific sections.”

Users can adjust the threshold in settings. This pairs naturally with heading-based section targeting — large documents are exactly where the section parameter shines.


Ship First (Week 1-2)

MD Bot (standalone MCP server)

  • 6 core tools: mdbot_list_documents, mdbot_get_document, mdbot_create_document, mdbot_update_document, mdbot_search, mdbot_publish
  • 2 basic resources: mdbot://documents, mdbot://documents/{slug}
  • SQLite library with FTS5 full-text search
  • mdlib.dev publish integration (API calls, edit key storage)
  • Standalone binary: npx mdbot --library ~/Documents/mdbot
  • Smart truncation for large documents (32KB threshold)
  • Auto version snapshots on every write

MD App (desktop editor)

  • Library view (list/grid of documents with search, sort, filter)
  • Split editor/preview (CodeMirror 6 + remark renderer)
  • “Connect to Claude” button (writes MCP config JSON for Claude Desktop/Code/Cursor)
  • “Connect mdlib.dev” settings (API key entry, stored in OS keychain)
  • Basic publish UI (publish button, status indicator, link copy)
  • mdapp:// URL scheme registration (for deep links from MD Bot notifications)

Shared

  • ~/.mdbot/ data directory (both products read/write)
  • config.json schema
  • library.db SQLite schema

Ship Second (Week 3-4)

MD Bot

  • Semantic search (mode: "semantic" via local embeddings)
  • Version history tools (mdbot_get_versions, mdbot_get_diff)
  • Project/tag management tools (mdbot_list_projects, mdbot_list_tags)
  • Prompts (mdbot_write_similar, mdbot_review_document)
  • Resource subscriptions (real-time document change notifications)
  • mdbot_sync_from_mdlib (pull published docs into local library)
  • OS notifications with deep links on document create/update

MD App

  • Version history UI (visual diff viewer, restore button)
  • Project/tag sidebar (create, rename, filter)
  • Document notifications panel (agent activity feed)
  • Review mode (accept/reject sections, inline comments)

Ship Third (Week 5+)

MD Bot

  • mdbot_restore_version
  • Template tools (mdbot_list_templates, mdbot_get_template, mdbot_create_from_template)
  • Multi-agent inbox (watch folders for incoming .md files)
  • Advanced prompts (mdbot_summarize_project, mdbot_changelog)

MD App

  • Template browser and editor
  • Export suite (PDF, DOCX, HTML from markdown)
  • Settings for: read-only MCP mode, truncation threshold, project/tag creation permissions
  • Auto-update system