Skip to content
Guide

MCP Server

LeftFold exposes an MCP 2.1-compatible server so you can create and manage content directly from Claude Desktop, Cursor, or any MCP client.

Overview

The MCP server runs as a Supabase Edge Function at /functions/v1/mcp. It speaks the Model Context Protocol over HTTP with JSON-RPC, and supports OAuth-based authentication for clients that implement it.

A vanity URL is also available at https://leftfold.io/mcp, which proxies to the edge function. Use whichever is more convenient for your client.


Connecting Claude Desktop

Claude Desktop supports MCP servers natively with OAuth authentication. Add the following to your Claude Desktop configuration at ~/Library/Application Support/Claude/claude_desktop_config.json (macOS) or the equivalent on your platform:

claude_desktop_config.json
{
  "mcpServers": {
    "leftfold": {
      "url": "https://leftfold.io/mcp"
    }
  }
}

Restart Claude Desktop. On first use, it will walk you through OAuth sign-in automatically — no API key needed. See the OAuth flow section below for details.

If your client doesn't support OAuth, you can pass an API key directly via the Authorization header instead. See API Keys & Scopes.


OAuth connection flow

When Claude Desktop (or any OAuth-capable MCP client) connects for the first time, the following happens automatically:

  1. Discovery — the client fetches /.well-known/oauth-protected-resource from LeftFold, which returns the Supabase Auth server URL and supported scopes
  2. Authorization — the client initiates an OAuth 2.1 code flow, opening a browser window for you to sign in with your LeftFold account (email, Google, or GitHub)
  3. Consent — you see the requested scopes (e.g. mcp:read, mcp:write) and approve access
  4. Token exchange — the client exchanges the authorization code for a JWT and stores it locally
  5. Ready — all subsequent tool calls include the JWT as a bearer token. The MCP server validates it, resolves your workspace, and enforces scope and subscription gating per tool

After the initial sign-in, reconnection is automatic — no re-authentication needed unless the token expires.


Connecting Cursor

Cursor supports MCP servers through its settings. Go to Settings → MCP Servers, click Add Server, and enter:

  • Name: LeftFold
  • URL: https://leftfold.io/mcp
  • Authorization: Bearer sk_your_api_key

stdio-only Clients

Some MCP clients only support the stdio transport (not HTTP). For those, use the mcp-remote bridge:

{
  "mcpServers": {
    "leftfold": {
      "command": "bunx",
      "args": [
        "mcp-remote",
        "https://leftfold.io/mcp",
        "--header",
        "Authorization: Bearer sk_your_api_key"
      ]
    }
  }
}

This starts a local process that translates stdio to HTTP, forwarding requests to the LeftFold MCP server.


Available Tools

The MCP server exposes tools for managing your workspace, content, agents, and mailbox. Tools map directly to the HTTP API and CLI — the same operations are available across all three surfaces.

Workspace & project

ToolScopeDescription
whoamiprofile:readReturns your user profile and workspace
list_projectsmcp:readList all projects in the workspace
create_projectmcp:writeCreate a new project

Content

ToolScopeDescription
aggregate.definemcp:writeRegister a new aggregate type with traits and fields
aggregate.schemamcp:readFetch the full schema for an aggregate type
aggregate.listmcp:readList content by aggregate type (folded to current state)
aggregate.getmcp:readGet the folded state of a specific aggregate
aggregate.appendmcp:writeAppend an event to any aggregate
aggregate.appendBatchmcp:writeAppend multiple events in a single transaction
aggregate.historymcp:readFetch the raw event stream for an aggregate
aggregate.searchmcp:readSemantic search across the knowledge graph
aggregate.relatemcp:writeCreate ad-hoc relationships between aggregates
querymcp:readRun a fold pipeline query with full expression support

Mailbox

ToolScopeDescription
mailbox.sendmcp:writeSend a task to a user or agent with metadata, priority, and idempotency
mailbox.listmcp:readList tasks (filter by status, assignee, project, source)
mailbox.getmcp:readGet a task with messages and artifacts
mailbox.approvemcp:writeApprove an enrichment
mailbox.rejectmcp:writeReject with a reason
mailbox.editmcp:writeEdit an artifact and approve the edited version
mailbox.defermcp:writeDefer for later review
mailbox.askmcp:writeSend a follow-up question to the agent
mailbox.assignmcp:writeReassign to a different user or agent
mailbox.readmcp:writeMark a task as read
mailbox.unreadmcp:writeMark a task as unread

Files

ToolScopeDescription
files.uploadmcp:writeUpload a file (base64) with optional aggregate association
files.listmcp:readList files by project, aggregate, or prefix
files.urlmcp:readGenerate a signed download URL (1 hour)
files.deletemcp:writeRemove a file from storage

Agents

ToolScopeDescription
agent.registermcp:writeRegister a new external agent
agent.listmcp:readList all agents in the workspace
agent.cardmcp:readGet an agent's card metadata
agent.rostermcp:readPending, unread, and in-progress counts per agent

Run whoami after connecting to verify your setup. It returns your workspace name, user email, and active scopes.


Authentication

The MCP server accepts both sk_ API keys and Supabase OAuth JWTs as bearer tokens. The token determines which workspace and scopes the client has access to.

Read-only tools require mcp:read. Write tools require mcp:write (paid). Some tools have additional scope requirements noted in their descriptions.

If a tool requires a scope the key doesn't have, the server returns an MCP error with a clear message. If the scope requires a paid subscription and the trial has expired, you'll see a 402-equivalent error.


Local Development

For local development, start the Supabase stack and point your MCP client at the local function URL:

# Start the local Supabase stack
bun db:start

# Start the Next.js dev server (for the vanity URL)
bun dev

Then configure your MCP client to use the local URL:

{
  "mcpServers": {
    "leftfold-local": {
      "url": "http://127.0.0.1:54321/functions/v1/mcp",
      "headers": {
        "Authorization": "Bearer sk_your_local_key"
      }
    }
  }
}

You can also test directly with curl:

curl -X POST http://127.0.0.1:54321/functions/v1/mcp \
  -H "Authorization: Bearer sk_your_local_key" \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0",
    "id": 1,
    "method": "tools/list"
  }'