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:
{
"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:
- Discovery — the client fetches
/.well-known/oauth-protected-resourcefrom LeftFold, which returns the Supabase Auth server URL and supported scopes - 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)
- Consent — you see the requested scopes (e.g.
mcp:read,mcp:write) and approve access - Token exchange — the client exchanges the authorization code for a JWT and stores it locally
- 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
| Tool | Scope | Description |
|---|---|---|
| whoami | profile:read | Returns your user profile and workspace |
| list_projects | mcp:read | List all projects in the workspace |
| create_project | mcp:write | Create a new project |
Content
| Tool | Scope | Description |
|---|---|---|
| aggregate.define | mcp:write | Register a new aggregate type with traits and fields |
| aggregate.schema | mcp:read | Fetch the full schema for an aggregate type |
| aggregate.list | mcp:read | List content by aggregate type (folded to current state) |
| aggregate.get | mcp:read | Get the folded state of a specific aggregate |
| aggregate.append | mcp:write | Append an event to any aggregate |
| aggregate.appendBatch | mcp:write | Append multiple events in a single transaction |
| aggregate.history | mcp:read | Fetch the raw event stream for an aggregate |
| aggregate.search | mcp:read | Semantic search across the knowledge graph |
| aggregate.relate | mcp:write | Create ad-hoc relationships between aggregates |
| query | mcp:read | Run a fold pipeline query with full expression support |
Mailbox
| Tool | Scope | Description |
|---|---|---|
| mailbox.send | mcp:write | Send a task to a user or agent with metadata, priority, and idempotency |
| mailbox.list | mcp:read | List tasks (filter by status, assignee, project, source) |
| mailbox.get | mcp:read | Get a task with messages and artifacts |
| mailbox.approve | mcp:write | Approve an enrichment |
| mailbox.reject | mcp:write | Reject with a reason |
| mailbox.edit | mcp:write | Edit an artifact and approve the edited version |
| mailbox.defer | mcp:write | Defer for later review |
| mailbox.ask | mcp:write | Send a follow-up question to the agent |
| mailbox.assign | mcp:write | Reassign to a different user or agent |
| mailbox.read | mcp:write | Mark a task as read |
| mailbox.unread | mcp:write | Mark a task as unread |
Files
| Tool | Scope | Description |
|---|---|---|
| files.upload | mcp:write | Upload a file (base64) with optional aggregate association |
| files.list | mcp:read | List files by project, aggregate, or prefix |
| files.url | mcp:read | Generate a signed download URL (1 hour) |
| files.delete | mcp:write | Remove a file from storage |
Agents
| Tool | Scope | Description |
|---|---|---|
| agent.register | mcp:write | Register a new external agent |
| agent.list | mcp:read | List all agents in the workspace |
| agent.card | mcp:read | Get an agent's card metadata |
| agent.roster | mcp:read | Pending, 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 devThen 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"
}'