Skip to Content
Architecture

Architecture

MarkDocs is a full-stack collaborative markdown editor.

Stack

LayerTechnology
FrontendNext.js, React, Tailwind CSS, shadcn/ui
EditorCodeMirror 6, Yjs, WebSockets
BackendNext.js API routes, custom WebSocket server
DatabasePostgreSQL via Prisma
AuthHandle-based with bcrypt + JWT (jose), API keys
CLICommander.js, chalk, ora
MCP Server@modelcontextprotocol/sdk (stdio)
DeploymentDocker Compose

Project Structure

markdocs/ ├── src/ │ ├── app/ # Next.js pages and API routes │ │ ├── api/ │ │ │ ├── auth/ # Login, signup, logout │ │ │ ├── documents/ # CRUD, content, edit, collaborators, history │ │ │ ├── comments/ # Reply, resolve, unresolve, delete │ │ │ ├── suggestions/ # Accept, reject │ │ │ ├── keys/ # API key management │ │ │ ├── invites/ # Invite link generation │ │ │ └── users/ # User listing │ │ ├── dashboard/ # Document list │ │ ├── doc/[id]/ # Collaborative editor │ │ └── settings/ # API keys, invites, profile │ ├── components/ # React components │ └── lib/ # Shared utilities, auth, prisma, yjs ├── cli/ │ └── src/ │ ├── index.ts # CLI commands (Commander) │ ├── mcp.ts # MCP server (23 tools, 2 resources) │ ├── client.ts # HTTP client for the API │ └── config.ts # ~/.markdocs/config.json management ├── prisma/ │ └── schema.prisma # Database schema ├── server.ts # Custom server (Next.js + WebSocket) ├── Dockerfile # App container └── docker-compose.yml # Full stack (app + postgres)

Auth

Three auth methods, resolved in priority order:

1. MARKDOCS_API_KEY env var → Bearer token 2. ~/.markdocs/config.json → Saved API key or JWT 3. Session cookie → Browser sessions

The CLI login command authenticates with handle/password, receives a JWT, and saves it locally. The setup command uses that JWT to create a persistent API key. All CLI and MCP calls then use the API key automatically.

API routes use getAuth() which checks Bearer tokens first (as API key hash, then as JWT), falling back to session cookies for browser clients.

Data Flow

Real-Time Editing (Browser)

Browser A ──► WebSocket ──► Yjs Server ──► WebSocket ──► Browser B Snapshot to DB (on disconnect)

Documents use Yjs  CRDTs for conflict-free real-time editing. The browser editor is CodeMirror 6 with y-codemirror.next for collaborative binding. Changes sync over WebSockets and persist as Yjs state snapshots to PostgreSQL.

CLI and MCP (Agents)

Agent / CLI markdocs <command> HTTP + API Key ──► API Routes ──► Prisma ──► PostgreSQL Yjs snapshot (read/write)
AI Agent ◄── stdio ──► markdocs mcp ── HTTP ──► MarkDocs API

The CLI and MCP server share the same HTTP client and credential store (~/.markdocs/config.json). The MCP server translates JSON-RPC tool calls over stdio into REST API requests.

Text-Based Edit API

The /api/documents/:id/edit endpoint lets agents edit by quoting visible text instead of counting character positions. The server reads the current Yjs document, resolves text targets to positions, applies content operations atomically, then applies review operations (comments, suggestions) in order.

Agent sends: { "op": "replace", "find": "old text", "replace": "new text" } Server reads Yjs snapshot Finds "old text" at position 42 Replaces via Yjs Returns updated document content

Database Schema

Key models:

  • User — handle, passwordHash, name, avatarUrl
  • Document — title, visibility, creatorId, orgId
  • DocumentCollaborator — userId, documentId, role (editor/viewer)
  • DocumentSnapshot — Yjs binary state for a document
  • Comment — content, fromPos, toPos, parentId (threaded replies), resolved, resolvedBy
  • Suggestion — originalText, suggestedText, fromPos, toPos, status (pending/accepted/rejected)
  • EditHistory — action, diff, source (web/api/mcp), metadata
  • ApiKey — keyHash, prefix, userId, expiresAt, revokedAt
  • Invite — code, createdBy, claimedBy, expiresAt
Last updated on