SupaLedger speaks the Model Context Protocol — the same language Claude uses to read filesystems, query databases, and call APIs. Read trial balances, age receivables, hand work to Sam, propose journal entries — every call org-scoped, audit-attributed, and HIPAA-aware.
https://jztjsserurylfjqfbntx.supabase.co/functions/v1/mcp-server/mcp
Three verbs across the entire surface. Read business state. Delegate messy work to one of seven AI personas. Export reports as PDFs with one-hour signed URLs.
Trial balance, P&L, balance sheet, AR / AP aging — summary or detail. Search journals, contacts, invoices, bills. Org context with active modules.
Drop a natural-language task on the supervisor agent. Sam routes to Avery, Casey, Riley, Sage, Quinn, or Morgan — and you get the result with child tasks attached.
Render any of five reports to PDF with a 60-minute signed URL. Or stage a write — record_expense, create_invoice, post_journal_entry — for the user to confirm in-app.
agent_proposals confirm gate. External agents propose; SupaLedger personas execute.
Every authenticated request resolves a client_id for audit attribution. OAuth tokens are org-scoped at consent time — the chosen org id lands in the JWT as mcp_org_id so every tool call is automatically org-bounded.
custom_access_token_hook injects mcp_org_idargs.org_id == jwt.mcp_org_idquery_business_aix-mcp-client-id tags audit log entriesmcp_external_clientsSL-AUTH-600mcp:anonymousmcp_connector is a reserved tombstone24 tools as of 2026-05-07. Every tool requires org_id. Every response carries a _meta envelope with server_time, api_version, and freshness notes — use it to invalidate cached answers.
Pick a transport, set a header, call a tool. The MCP TypeScript & Python SDKs handle the JSON-RPC framing — but raw curl works too.
# 1 · List available tools — service-role bearer curl -X POST "https://jztjsserurylfjqfbntx.supabase.co/functions/v1/mcp-server/mcp" \ -H "Authorization: Bearer $SUPALEDGER_SERVICE_ROLE_KEY" \ -H "Content-Type: application/json" \ -H "Accept: application/json, text/event-stream" \ -H "x-mcp-client-id: my-integration-name" \ -d '{ "jsonrpc": "2.0", "id": 1, "method": "tools/list" }' # 2 · Pull a P&L for a known org curl -X POST "…/mcp-server/mcp" \ -H "Authorization: Bearer $SUPALEDGER_SERVICE_ROLE_KEY" \ -H "x-mcp-client-id: my-integration-name" \ -d '{ "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "accounting_get_profit_and_loss", "arguments": { "org_id": "<uuid>", "start_date": "2026-01-01", "end_date": "2026-03-31", "format": "summary" } } }'
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"; const transport = new StreamableHTTPClientTransport( new URL("https://jztjsserurylfjqfbntx.supabase.co/functions/v1/mcp-server/mcp"), { requestInit: { headers: { Authorization: `Bearer ${process.env.SUPALEDGER_SERVICE_ROLE_KEY}`, "x-mcp-client-id": "my-integration-name", }, }, }, ); const mcp = new Client({ name: "my-app", version: "1.0.0" }); await mcp.connect(transport); // 1. Discover orgs the caller can access const orgs = await mcp.callTool({ name: "platform_get_org_context", arguments: {}, }); // 2. Pull a P&L for one of them const pl = await mcp.callTool({ name: "accounting_get_profit_and_loss", arguments: { org_id: "<uuid from step 1>", start_date: "2026-01-01", end_date: "2026-03-31", format: "summary", }, });
from mcp.client.streamable_http import streamablehttp_client from mcp import ClientSession import os, json async with streamablehttp_client( "https://jztjsserurylfjqfbntx.supabase.co/functions/v1/mcp-server/mcp", headers={ "Authorization": f"Bearer {os.environ['SUPALEDGER_SERVICE_ROLE_KEY']}", "x-mcp-client-id": "my-integration-name", }, ) as (r, w, _): async with ClientSession(r, w) as session: await session.initialize() result = await session.call_tool( "accounting_get_balance_sheet", {"org_id": "<uuid>", "format": "summary"}, ) print(json.loads(result.content[0].text))
Server time, API version, and freshness notes — auto-injected on every tool response. Use _meta.server_time to invalidate cached answers, and read _meta.notes for staleness caveats specific to that tool.
Every tool call writes one row to platform.mcp_audit_log. Free-text fields (query, instruction, title, subject) are never stored in plaintext — they're replaced with a redaction stub. Structural args are preserved verbatim for forensics.
All errors return a structured envelope with a stable code string. Treat the code as the contract — error message text may change between minor versions.
Per-org and per-user limits are enforced via mcp_audit_log count queries. Failures fail-closed with SL-MCP-503.
SupaLedger does not transit your prompts to any LLM. Sam internally uses Claude when processing a delegate_to_sam task; the task UI shows exactly what was sent. Every external request is auditable by org admins.
All data lives in Supabase Postgres, US West 2. Logical replication for backups; no cross-region replication.
Every tool defaults to format: "summary". Detail mode (PII like emails, phones, line items) only on explicit caller request.
Flagged orgs are silently filtered out of org-context listings; direct calls against their org_id return SL-AUTH-001.
Caller-supplied free-text never stored in plaintext. SHA-256 prefix preserved for duplicate detection; structural args verbatim.
Every delegate_to_sam string is run through a jailbreak-phrase redactor and wrapped in <external_instruction> markers.
Org admins can query platform.mcp_audit_log for every external request — actor type, client id, tool, timestamps.
Status as of 2026-05-07. Phases 1 → 4 done. Phase 5 evolution capabilities partially landed; OAuth Dashboard toggle & Anthropic Connectors Directory submission still in flight.
Connect Claude to your SupaLedger org in a coffee break. Free until May 30, 2026 — service-role key on signup, OAuth on Dashboard toggle.