Package Exports
- thetacoach-crm-mcp
- thetacoach-crm-mcp/server.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (thetacoach-crm-mcp) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
ThetaCoach CRM MCP Server v11.3.0
Local-first sync architecture for 500x faster CRM operations
Claude AI integration for ThetaCoach CRM with instant writes (0-1ms) and background sync to Supabase.
๐ What's New in v11.3.0
LOCAL-FIRST ARCHITECTURE - 500x Performance Improvement
- Before (v10.x): 200-500ms per write (network latency)
- After (v11.2.0): 0-1ms per write (instant SQLite)
- Claude Flow: 10 updates in 10ms (vs 2-5 seconds network-first)
Key Features:
- โก Write to SQLite first, sync to Supabase in background
- ๐ Pull from Supabase at start of each MCP call (ensures consistency)
- ๐ฏ Field-level timestamps for conflict resolution (last-write-wins)
- ๐ Token-limited reads (25k max) prevent Claude slowdown
- ๐ Background sync queue with retry (exponential backoff)
- โ Parallel execution safe (tested with unit tests)
๐๏ธ Architecture
The Sync Cycle (Every MCP Call)
1. MCP Tool Called
โ
2. Sync Supabase โ SQLite (~200ms one-time per call)
โ
3. Work on SQLite (0-1ms instant)
โ
4. Return result to Claude (capped at 25k tokens)
โ
5. Background: Sync SQLite โ Supabase (async, non-blocking)Why This Works
Consistency Guarantee:
- Claude makes MCP calls sequentially (one after another)
- By the time Claude finishes processing and makes the next call, background sync has completed
- Each call starts with
syncFromSupabase()โ SQLite is always fresh
Parallel Execution (Claude Flow):
- When Claude Flow executes MCP calls in parallel, SQLite WAL mode handles it safely
- Tested with 10 parallel writes: 0.04ms/write, no conflicts, 100% success rate
- Field-level timestamps prevent conflicts (different fields = independent updates)
- See
test-parallel-execution.mjsfor verification
Conflict Resolution
Field-Level Timestamps (Last-Write-Wins):
// Example: Two devices edit same lead
Device A: Updates discovery_notes at timestamp 1000
Device B: Updates discovery_notes at timestamp 2000
Result: Device B wins (newer timestamp)
Device A: Updates discovery_notes at timestamp 1000
Device B: Updates rational_notes at timestamp 1005
Result: Both succeed (different fields, no conflict)Why This is Acceptable for CRM:
- Single user per lead (sales rep owns their prospects)
- Independent fields (discovery vs rational rarely edited simultaneously)
- Fast sync (background sync completes in 5-10 seconds)
- Low conflict probability (same field edited on two devices within 10s is rare)
๐ฆ Quick Start (30 seconds)
# 1. Install the MCP server
npm install -D thetacoach-crm-mcp@latest
# 2. Update database schema to v2.6 (CRITICAL!)
# Open: https://thetacoach.biz/ONE-SHOT-CRM-SETUP.sql
# Copy entire file โ Paste in Supabase SQL Editor โ Run
# This adds field-level timestamps (*_updated_at columns)
# 3. Create .env.local with credentials
cat > .env.local <<EOF
CRM_SUPABASE_URL="https://xxxxx.supabase.co"
CRM_SUPABASE_SERVICE_ROLE_KEY="eyJ..."
EOF
# 4. Register with Claude Code
claude mcp add thetacoach-crm ./node_modules/.bin/thetacoach-crm
# 5. Restart Claude Code (Cmd+Q โ reopen)
# 6. Test it!
# In Claude Code, say: "List my CRM leads"Done! Writes are now instant (0-1ms).
๐ Sync Strategy Deep Dive
Sync-on-Each-Call Pattern
Every MCP call follows this pattern:
async function handleUpdateCard(args) {
// STEP 1: Sync from Supabase to SQLite (200ms, ensures fresh data)
await syncFromSupabase(supabase, db, 'leads', { field: 'email', value: args.email });
// STEP 2: Write to SQLite (0-1ms, instant)
writeLocal(db, 'leads', { field: 'email', value: args.email }, {
discovery_notes: args.discovery_notes,
rational_notes: args.rational_notes
});
// STEP 3: Return immediately (don't wait for background sync)
return { success: true, latency: '0-1ms' };
// STEP 4: Background sync happens automatically via SyncQueue
}Batching & Chunking
Token-Limited Reads (25k Max):
// List leads with automatic token limiting
const leads = batchRead(
db,
'SELECT * FROM leads WHERE stage = ? ORDER BY lead_score DESC',
['discovery'],
{
maxTokens: 25000,
estimateTokens: (row) => JSON.stringify(row).length / 4 // 1 token โ 4 chars
}
);
// Returns: Array of leads, capped at 25k tokens to prevent Claude slowdownWhy This Matters:
- Battle cards are huge (25k+ tokens each)
- Without capping, reading 100 leads = 2.5M tokens = Claude slowdown
- With capping, reading stops at 25k tokens = instant response
Field Projection (Reduce Tokens):
// Request only the fields you need
const leads = listLeads({
fields: ['id', 'email', 'name', 'company', 'lead_score', 'stage'],
limit: 10
});
// Returns: 200 tokens vs 25k tokens (full battle card)Background Sync Queue
Retry with Exponential Backoff:
class SyncQueue {
async process() {
while (this.queue.length > 0) {
const item = this.queue.shift();
try {
await this.syncToSupabase(item);
// Mark as synced in SQLite
} catch (error) {
// Retry with exponential backoff (max 5 retries)
if (item.retries < 5) {
const delay = Math.min(1000 * 2 ** item.retries, 60000);
setTimeout(() => this.add({ ...item, retries: item.retries + 1 }), delay);
} else {
// Mark as error in SQLite (_sync_error column)
}
}
}
}
}Backoff Schedule:
- Retry 1: 1 second
- Retry 2: 2 seconds
- Retry 3: 4 seconds
- Retry 4: 8 seconds
- Retry 5: 16 seconds
- Max: 60 seconds
- After 5 retries: Give up, log error
๐งช Testing Parallel Execution
Run the included unit test:
node test-parallel-execution.mjsTest Results:
๐ Test 1: Sequential writes (baseline)
โฑ๏ธ Time: 0.18ms
๐ Avg per write: 0.02ms/write
๐ Test 2: Parallel writes (Claude Flow simulation)
โฑ๏ธ Time: 0.38ms
๐ Avg per write: 0.04ms/write
โ
Successes: 10/10
โ Failures: 0/10
๐ Test 3: Data consistency check
โ
All field-level timestamps are valid
โ
Sync status correctly marked as pending
๐ Test 4: Concurrent reads during writes
โฑ๏ธ Time: 0.19ms
โ๏ธ Writes: 5
๐ Reads: 5
โ
No deadlocks or blocking
โ
ALL TESTS PASSED - Parallel execution is safe!Key Insights:
- SQLite WAL mode handles concurrent access correctly
- Field-level timestamps prevent conflicts
- Writes are 0.02-0.04ms each (no blocking)
- Reads don't block writes
- Safe for Claude Flow parallel execution
๐ Database Schema v2.6
Requirements:
- Run ONE-SHOT-CRM-SETUP.sql v2.6 in Supabase
- Download: https://thetacoach.biz/ONE-SHOT-CRM-SETUP.sql
- Safe to re-run (idempotent)
What v2.6 Adds:
-- Field-level timestamps (when each field was last updated)
ALTER TABLE leads ADD COLUMN IF NOT EXISTS discovery_notes_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS rational_notes_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS emotional_notes_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS solution_notes_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS commitment_notes_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS product_positioning_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS outreach_a_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS outreach_b_updated_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS notes_updated_at BIGINT;
-- Sync status columns
ALTER TABLE leads ADD COLUMN IF NOT EXISTS _sync_status TEXT DEFAULT 'synced';
ALTER TABLE leads ADD COLUMN IF NOT EXISTS _last_synced_at BIGINT;
ALTER TABLE leads ADD COLUMN IF NOT EXISTS _sync_error TEXT;
-- Index for performance
CREATE INDEX IF NOT EXISTS idx_leads_sync_status ON leads(_sync_status) WHERE _sync_status = 'pending';Verify Schema Version:
-- In Supabase SQL Editor
SELECT column_name FROM information_schema.columns
WHERE table_name = 'leads'
AND column_name LIKE '%_updated_at';
-- Should return 9 rows (field timestamps)๐ SQLite Cache Location
Default:
~/.thetacoach-crm/cache-{hash}.db- Each Supabase URL gets isolated cache (prevents cross-project contamination)
- Example:
https://abc.supabase.coโcache-abc12345.db
Custom:
export CRM_SQLITE_PATH=/custom/path/to/cache.dbMulti-Project Support:
- Each project folder has its own
.env.localwith Supabase credentials - MCP searches upward for
.env.local(like Git searching for.git) - Cache switches automatically based on Supabase URL
- No Claude Code restart needed when switching projects
๐ง MCP Tools Reference
crm-list-leads - List Leads with Token Limiting
Token-aware pagination:
// Default: Summary fields only (reduces tokens)
crm-list-leads({ limit: 10, offset: 0 })
// Custom fields: Request only what you need
crm-list-leads({
fields: ['id', 'email', 'name', 'company', 'lead_score', 'stage'],
limit: 20,
offset: 0,
stage: 'discovery',
min_score: 70
})Automatic token capping:
- Reads stop at 25k tokens
- Prevents Claude slowdown
- Returns
{ leads: [...], capped: true/false }
crm-update-card - Update Battle Card (Instant)
crm-update-card({
email: 'john@acme.com',
discovery_notes: 'Full discovery section text...',
rational_notes: 'ROI calculations...',
emotional_notes: 'Personal stakes...'
})
// Returns: { success: true, latency: '0-1ms', email: 'john@acme.com' }What Happens:
- Sync from Supabase (200ms)
- Write to SQLite (0-1ms) with field-level timestamps
- Return immediately
- Background sync queues the update to Supabase
crm-update-checklist - Update Phase Checklist
crm-update-checklist({
email: 'john@acme.com',
phase: 'discovery',
items: [
'ASK: Of your 30 portfolio companies, what % fail? (hear: 20-30%)',
'SHARE: Pattern I see across portfolio-backed founders...',
'CALCULATE TOGETHER: 1 company saved = $500K portfolio value'
]
})
// Returns: Instantly updated checklist with auto-generated IDsAffordance System:
- Automatically sorts bullets by usefulness (personalization ร impact ร phase)
- Only Tier 1 (ultra-specific) and Tier 2 (context-specific) items
- No generic Tier 3/4 items (waste of checklist space)
๐จ Troubleshooting
Issue: "Writes are slow" or "Still seeing 200ms latency"
Fix: You're running old version or schema not updated
# 1. Verify package version
npm list thetacoach-crm-mcp # Must show v11.2.0+
# 2. Verify schema has sync columns
# In Supabase SQL Editor:
SELECT column_name FROM information_schema.columns
WHERE table_name = 'leads' AND column_name = '_sync_status';
# Should return 1 row
# 3. If either is wrong, update both:
npm install thetacoach-crm-mcp@latest
# Run SQL: https://thetacoach.biz/ONE-SHOT-CRM-SETUP.sql
# Restart Claude CodeIssue: "Column does not exist" errors
Fix: Database schema is out of date
# Run the LATEST SQL from the website (not an old copy):
open https://thetacoach.biz/ONE-SHOT-CRM-SETUP.sql
# Copy ENTIRE file โ Paste in Supabase SQL Editor โ Run
# Safe to re-run (idempotent, only adds missing columns)Issue: "Sync not working" or "Changes not appearing in Supabase"
Debug sync status:
// In Claude, say: "CRM sync status"
// Should show:
{
pending: 0,
status: 'synced',
lastSync: '2025-11-04T21:45:00Z'
}Check:
- Network connectivity (can you access Supabase?)
- Service role key is correct in
.env.local - Local SQLite cache exists:
ls ~/.thetacoach-crm/
๐ Documentation
- CHANGELOG.md - Release notes for v11.2.0
- IMPLEMENTATION-GUIDE.md - Architecture deep dive (350 lines)
- test-parallel-execution.mjs - Unit tests for parallel safety
- migrations/ - SQLite migration scripts
๐ Links
- SQL Schema: https://thetacoach.biz/ONE-SHOT-CRM-SETUP.sql
- GitHub: https://github.com/wiber/thetadrivencoach/tree/main/packages/thetacoach-crm-mcp
- Support: https://github.com/wiber/thetadrivencoach/issues
๐ License
MIT
v11.3.0 - Local-first sync for 500x faster CRM operations