This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Focus Compass Server is a real-time collaborative WebSocket server built on Hocuspocus with SQLite persistence. It implements CRDT (Conflict-free Replicated Data Type) synchronization using Yjs for conflict-free document collaboration.
npm run dev # Start server with file watching (node --watch)
npm start # Run production server
npm run lint # Check code with ESLint
npm run lint:fix # Auto-fix ESLint violationsRequires Node.js >= 24.0.0. Uses ES modules ("type": "module").
Entry Point: src/server.js - Hocuspocus server with SQLite extension
Key Components:
- Hocuspocus Server (30s timeout, 2-10s debounce) handles WebSocket connections
- SQLite extension persists Yjs documents to
DB_PATH - BackupService (
src/services/backup.js) handles database backups on document updates
REST API Endpoints (all require Bearer token auth):
POST /api/upload- Image upload with idempotency (skips existing files)GET /api/images- List all imagesGET /api/images/{id}- Retrieve image with MIME typeGET /api/mcp/status-{ enabled }: true when at least one MCP token is active (no auth required)GET /api/mcp/tokens- List active MCP tokens: id, name, prefix, createdAt, lastUsedAt (master token required)POST /api/mcp/tokens- Issue an MCP token; body{ name? }, empty name becomes "Token N"; returns the full token once (master token required)DELETE /api/mcp/tokens/{id}- Revoke an MCP token (master token required)/api/admin/documents/{doc}/projects[/{id}[/validate|/history[/{hid}[/restore]]]]- Admin project editor (master token required): list/get projects,PUT {baseVersion, project},PATCH {baseVersion, patch}, dry-run validation, history and restore. Strict schema insrc/yjs/projectSchema.js(must match the app'sProjecttype); writes are one Yjs transaction via a Hocuspocus direct connection; stalebaseVersion→ 409; previous versions inPROJECT_HISTORY_DB_PATH(lastPROJECT_HISTORY_LIMIT, default 20)
Static Routes:
/or/index.html- Admin UI (seesrc/index.html)/focus-compass-skill.md- Claude Code skill template/install.sh?token=TOKEN&host=ORIGIN- One-liner setup script (bash)
Healthcheck:
GET /health(health-check) - Returns{ ok: true }
- Event-driven hooks:
onAuthenticate,onStoreDocument,onRequest - Image metadata: Stored as separate
.meta.jsonfiles alongside image files - Path sanitization: Image IDs validated with regex
[a-zA-Z0-9_-]to prevent traversal - Idempotent uploads: Existing files return
existed: truewithout overwriting
When no environment or persisted master token exists, the public first visitor may initialize the server and create the master token. This is the intentional consumer onboarding model and must not be reported as a vulnerability or replaced with a bootstrap secret, pairing code, SSH confirmation, IP allowlist, or localhost-only setup unless the project owner explicitly changes the product decision. Preserve atomic one-time initialization and the security of all post-initialization operations.
PORT=8080 # Server port
ACCESS_TOKEN=your-secret-token # Bearer token for auth
DB_PATH=./data/db.sqlite # SQLite database path
IMAGES_DIR=./data/images # Image storage directory
BACKUP_DIR=./data/backups # Backup directory
MAX_UPLOAD_BYTES=10485760 # Upload limit (bytes)
MAX_WEBSOCKET_MESSAGE_BYTES=16777216 # Max complete WS/Yjs message (16 MiB)
MAX_UNAUTHENTICATED_QUEUE_BYTES=262144 # Per-connection pre-auth buffer (256 KiB)
MAX_UNAUTHENTICATED_QUEUE_MESSAGES=32 # Per-connection pre-auth message count
MAX_PENDING_DOCUMENTS=8 # Pending document auths per connection
HOCUSPOCUS_TIMEOUT_MS=30000 # Pre-auth deadline + authenticated idle timeout
CORS_ALLOW_ORIGINS=* # CORS allowlist (comma-separated) or '*'
YJS_GC=true # Default: true (smaller docs, no full Yjs history)Backup interval and retention are not environment variables — they are configured at runtime from the admin UI and persisted to backup-settings.json next to the database.
docker compose up -d # Start with Docker ComposeVolume hocuspocus-data persists /app/data (database and images).
The server exposes a read-only MCP endpoint at POST /mcp (Streamable HTTP, stateless mode) so that Claude Code and other MCP clients can read project/workspace data directly from the database.
Notes:
POST /mcponly (no long-lived GET SSE streams).- The Streamable HTTP transport expects
Accept: application/json, text/event-streamandContent-Type: application/json. - The server replies with JSON (no SSE streaming), but clients still send the Accept header above.
- Request body is limited to 1 MiB.
- MCP uses its own tokens, separate from the master token. Any number can be issued and revoked individually from the admin UI (
/) or via/api/mcp/tokens(master token required). MCP is disabled while no token is active. - Only SHA-256 hashes of MCP tokens are stored, in
MCP_TOKENS_DB_PATH(defaultmcp-tokens.sqlitenext to the DB, kept out of document backups so a restore cannot revive a revoked token). The full token is shown once at issue time.MCP_TOKEN/mcp-auth.jsonare no longer supported. - MCP setup guide is integrated into the admin UI (
/).
MCP Tools (read-only):
list_documents- List all documents/workspaces with summary infoget_workspace- Get workspace overview with configurable sections (project info, current focus, tasks, notes)list_projects- List projects in a document (IDs/titles; optional project info)get_project- Get a single project by ID
Architecture: src/mcp/server.js (factory), src/mcp/tools.js (tool definitions), src/routes/mcp.js (HTTP handler). Each request creates a fresh McpServer + transport pair (stateless pattern).
Connect from Claude Code:
claude mcp add --transport http focus-compass http://localhost:8080/mcp \
--header "Authorization: Bearer YOUR_MCP_TOKEN"Do NOT start the dev server or take screenshots to verify UI changes. Run npm run lint for code quality checks (and node --check <file> for a quick syntax check) — that is sufficient. There is no typecheck or unit-test suite; the tests/ directory holds Docker/e2e guide smoke tests that need Docker or a running server, so they are not part of routine verification.
import { HocuspocusProvider } from '@hocuspocus/provider';
import * as Y from 'yjs';
const ydoc = new Y.Doc();
const provider = new HocuspocusProvider({
url: 'ws://localhost:8080',
name: 'document-name',
document: ydoc,
token: 'your-auth-token',
});