Skip to content

Architecture

$ architecture · 6 min read · updated 2026-04-22

developer machineMCP clientscapsol CLIregistry processdashboard + MCP URL workflowregistry (any host)Express + helmet + rate-limit + authMCP server (HTTP)/mcp/:capsuleIddashboard/FilteredStore (role + prefix)boxes.json + shares.jsonpersistent volume → $CAPSOL_DATA_DIRdeployremote agent / teammate / CIMCP client over HTTP
One registry process, one HTTP MCP surface, one storage format.

The MCP server is one factory function mounted by the registry at /mcp/:capsuleId. The public product surface is HTTP: dashboard, REST API, and Streamable HTTP MCP.

Everything durable lives under CAPSOL_DATA_DIR. Each capsule keeps the same plain-file .capsol/ layout internally. No database.

ConcernOn diskOwned by
Built-in entriesknowledge/<scheme>/<path>.{md,json} or skills/<path>.mdDotfileStore
Custom entriesentries/<scheme>/<path>.<ext>DotfileStore
Per-agent memorymemory/<agent>.jsonMemoryStore
Audit loglogs/YYYY-MM-DD.jsonlEventLogger
Binary payloadsblobs/<sha256>DotfileStore
  • Writes atomic (temp-file + rename(2)).
  • Reads plain file reads.
  • Boring on purpose. Nothing to corrupt.

createServer(opts) returns an MCP server with the capsol tools registered:

interface CreateServerOptions {
boxRoot: string; // path to .capsol/
memoryDir?: string; // defaults to <boxRoot>/memory
cwd?: string; // working dir reported to clients
shareConfig?: { // present in registry mode
role: "reader" | "appender" | "writer" | "owner";
uri_prefixes?: string[]; // allow list
denied_prefixes?: string[]; // deny list
};
}

The registry resolves the share, verifies its token when present, passes shareConfig into the factory, and wraps storage with FilteredStore.

One-way mirror. Wraps the base store and:

  1. Rejects writes the share’s role doesn’t permit.
  2. Hides reads not matching the allow list or matching the deny list.
  3. Deny wins on conflict.

Downstream (DotfileStore, filesystem, audit log) doesn’t know whether it’s filtered. That asymmetry is the point of the one-factory design — no duplicated storage code for scoping.

  • Per agent, keyed off MCP clientInfo.name.
  • Flat string→string map in memory/<agent>.json.
  • Atomic writes (same temp + rename).
  • Never surfaced to other agents. Never pushed on deploy.
  • Identifier is isolation, not security. Two agents sharing a name share memory — expected.

Every tool call emits one jsonl line:

{"ts":"2026-04-21T10:42:00Z","agent":"claude-code","action":"write","uri":"docs://api","version":3,"metadata":{"connection_id":"csl_7f2a","principal_id":"prn_..."}}

Append-only. Date-based rotation. Retention is a cron job, not a capsol concern. Registry exposes recent events via GET /v1/capsules/:id/logs (owner only) and a dashboard SSE feed.

registry/server.ts is an Express 5 app wrapping the MCP factory with:

  • Auth. Operator login for the dashboard/admin API, OAuth MCP grants, approved enrollment credentials, and hash-only credential storage.
  • Rate limits. Three buckets, 429 + Retry-After on overflow.
  • Grant spine. OAuth, dashboard-created connections, and agent enrollments all flow through GrantRequestRecord before an MCP credential becomes usable.
  • Persistence. boxes.json, shares.json, credentials.json, principals.json, OAuth clients, grants, access profiles, approval policy, and enrollment/cursor records.
  • Health. GET /health and GET /ready.
  • Dashboard. SPA talking to /api/* and /v1/*.

No database, no job queue, no workers. Host a single Node process, host capsol.

Capsule content is untrusted. The server wraps every read with a risk-class-dependent preamble and a provenance footer (writer, commit-at-write, cited anchors). Neither band is optional or writer-controlled.

See Trust.

  • No embeddings. Structured URIs, not vector chunks.
  • No database. Plain files.
  • No background jobs. Synchronous within the request.
  • No multi-tenant billing. Self-hosted; billing is the operator’s problem.
  • src/server/server.tscreateServer factory.
  • src/server/tools.ts — tool schemas.
  • src/storage/dotfile-store.ts — primary store.
  • src/storage/filtered-store.ts — role + prefix wrapper.
  • src/storage/memory-store.ts — per-agent memory.
  • registry/server.ts — HTTP shell.
  • registry/auth.ts, registry/rate-limit.ts — auth + limits.