Skip to content

Quickstart

LeftFold connects to your AI through MCP (Model Context Protocol). Setup takes under a minute:

  1. Create an account and choose your handle
  2. Copy your MCP URL: https://leftfold.io/your-handle
  3. Add the URL to your AI client (see guides below)
  4. Start talking — your AI handles the rest

No SDK. No database provisioning. No configuration files. One URL is the entire integration.

Connect Claude (claude.ai)

Claude supports MCP servers through Connectors (formerly Integrations).

  1. Open claude.ai and go to Settings
  2. Navigate to Connectors
  3. Click Add Connector and enter your MCP URL
  4. Authorize LeftFold when prompted

Claude Connectors documentation →

Connect ChatGPT

ChatGPT supports MCP servers as tool connectors.

  1. Open ChatGPT
  2. Go to Settings → Connectors (or Tools & Connectors)
  3. Add a new MCP connector with your LeftFold URL
  4. Authorize when prompted

OpenAI MCP documentation →

Connect Cursor

Cursor supports MCP servers in its settings.

  1. Open Cursor Settings (Cmd+,)
  2. Navigate to MCP Servers
  3. Click Add Server
  4. Set type to sse and URL to your LeftFold endpoint
~/.cursor/mcp.json
{
  "mcpServers": {
    "leftfold": {
      "url": "https://leftfold.io/your-handle"
    }
  }
}

Cursor MCP documentation →

Connect Claude Code

Claude Code (CLI) supports MCP servers via its settings file.

~/.claude/settings.json
{
  "mcpServers": {
    "leftfold": {
      "url": "https://leftfold.io/your-handle"
    }
  }
}

Or add it via the CLI:

claude mcp add leftfold https://leftfold.io/your-handle

Claude Code MCP documentation →

How it works

LeftFold is an event-sourced domain modeling system. Instead of storing current state (like a key-value store), it stores an immutable sequence of events — facts about what happened. Current state is derived by "folding" (replaying) events in order.

Your AI is simultaneously the client, the domain expert, and the decision-maker. It follows a decision cycle for every conversation:

  1. Orient — call list_aggregates to see what domain concepts exist
  2. Analyze — does the new information fit an existing aggregate?
  3. Decide — use an existing event type, evolve a schema, or define a new aggregate
  4. Record — call append_events with validated data
  5. Query — use the query tool to answer questions from folded state
  6. Reflect — did any facts emerge that should be recorded?

Aggregates & events

An aggregate is a domain concept with a distinct lifecycle — Contact, Order, Project, Decision. Each aggregate is a consistency boundary: events within it are ordered and atomic.

An event is an immutable fact — ContactCreated, OrderShipped, DecisionAccepted. Events are past-tense, self-describing, and timestamped.

Design principles

  • Prefer fewer, well-defined aggregates (3–7 serves most domains)
  • Aggregates are singular PascalCase nouns: Contact, not Contacts
  • Events are past-tense: OrderShipped, not ShipOrder
  • Cross-aggregate relationships use ID references, resolved at query time via $lookup
  • Aggregate IDs should be descriptive: contact-jane-smith, not a random UUID

Schema evolution

Schemas evolve over time via revise_event_definition. New fields must be optional or have defaults. Existing events automatically receive defaults when folded. Migration reasoning is stored permanently.

Queries

Queries use a MongoDB-style aggregation pipeline. The AI formulates the query; LeftFold executes it server-side and caches the results.

Get current state of all contacts
[
  { "$match": { "aggregate_type": "Contact" } },
  { "$fold": {} }
]
Filter after fold (active contacts only)
[
  { "$match": { "aggregate_type": "Contact" } },
  { "$fold": {} },
  { "$match": { "status": "active" } }
]
Cross-aggregate join
[
  { "$match": { "aggregate_type": "Order" } },
  { "$fold": {} },
  { "$lookup": {
    "from": "Contact",
    "localField": "contact_id",
    "foreignField": "_instance",
    "as": "contact"
  }}
]
Group and count
[
  { "$match": { "aggregate_type": "Order" } },
  { "$fold": {} },
  { "$group": { "_id": "status", "count": { "$count": {} } } },
  { "$sort": { "count": -1 } }
]

Scopes

Scopes are optional isolated domains within your account. Each scope has its own aggregate types, events, schemas, and query cache. Data in one scope is invisible to other scopes.

When to use scopes: When you have multiple independent domains — a business AND a side project, or separate client engagements.

When not to use scopes: When you have one purpose. The unscoped general space is simpler. Your AI won't suggest scopes until you mention a second domain.

MCP tools reference

ORIENTATION

guide Returns the usage guide. Call at the start of your first conversation.

list_scopes Returns all scopes. Called first in every conversation.

list_aggregates Returns the current world model summary.

get_aggregate_detail Full schema history and instance list for an aggregate type.

SCHEMA MANAGEMENT

create_scope Create a new isolated domain.

define_aggregate Register a new aggregate type with initial event definitions.

revise_event_definition Add new fields to an event type (backward-compatible).

DATA OPERATIONS

append_events The only write operation. Records events atomically.

query Primary read path. MongoDB-style aggregation pipeline with caching.

get_events Raw event retrieval for debugging and audit trails.

audit Complete event history for a specific aggregate instance.

Query pipeline stages

$match Filter events (pre-fold) or documents (post-fold). Supports $eq, $ne, $gt, $gte, $lt, $lte, $in, $nin, $exists.

$fold Core operation. Replays events per instance, producing current state via last-write-wins merge.

$lookup Join across aggregates. Target aggregate is auto-folded.

$group Aggregate with accumulators: $sum, $count, $avg, $min, $max, $push.

$project Select, exclude, or rename fields. Supports dot-notation.

$sort Order results. 1 for ascending, -1 for descending.

$limit Cap result count.