Skip to main content
Glama
sriramsv

Graph CRM MCP Server

by sriramsv

Graph CRM

A personal, single-user CRM that visualizes contacts and their relationships as a graph. Node/Express + Prisma backend (SQLite by default), vanilla JS frontend (no build step), WebAuthn passkey login, and an MCP server exposing the full API so Claude can read/write the CRM directly.

Setup

npm install
cp .env.example .env
npx prisma migrate deploy
npm start              # http://localhost:3000

First visit walks you through creating a passkey (Face ID / Touch ID / security key / device screen lock). See .env.example for optional configuration (custom port, RP_ID/ORIGIN if not running on localhost:3000, etc).

npm run dev             # auto-restart on file changes
npx prisma migrate dev --name <name>   # after editing prisma/schema.prisma

There's no test suite or linter — see CLAUDE.md for architecture notes if you're working on this codebase.

Related MCP server: Dex MCP Server

Docker

docker build -t graph-crm .
docker run -p 3000:3000 -v graph-crm-data:/app/data graph-crm

The /app/data volume holds everything stateful: the SQLite database, the session store, and generated secrets (session secret, passkey data lives in the DB itself). Without a mounted volume, all of that is lost when the container is removed.

If you're serving this behind a domain other than localhost:3000, set RP_ID and ORIGIN (see .env.example) — WebAuthn passkeys are bound to the origin they were created on and won't work if it changes.

Swapping the database backend

The app uses Prisma, so moving off SQLite doesn't require touching any application code — only prisma/schema.prisma and the connection string. Everything else (family.js, the REST API, mcp-server.js, the frontend) talks to Prisma's generated client, not to SQLite directly.

General steps, then Postgres/MySQL specifics below:

  1. Change the provider in prisma/schema.prisma's datasource block.

  2. Point DATABASE_URL at the new database.

  3. Delete prisma/migrations/ and generate a fresh initial migration against the new provider (npx prisma migrate dev --name init) — the existing migration history was written for SQLite's SQL dialect and won't replay cleanly on a different engine. There's no automatic data carry-over; if you need existing contacts, export them first (Settings → API keys → generate one → pull data via the list_contacts/get_directory endpoints or MCP tools, then re-import after switching).

  4. npx prisma generate to rebuild the client.

Postgres

docker run -d --name graph-crm-pg -e POSTGRES_PASSWORD=devpass -e POSTGRES_DB=graphcrm -p 5432:5432 postgres:16

prisma/schema.prisma:

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

.env:

DATABASE_URL="postgresql://postgres:devpass@localhost:5432/graphcrm"
rm -rf prisma/migrations
npx prisma migrate dev --name init
npm start

For a hosted Postgres (Supabase, Neon, RDS, etc.), just swap the connection string — most give you a ready-made postgresql://... URL. Neon/Supabase's pooled connection strings work fine with Prisma; if you hit prepared-statement errors behind a pooler, add ?pgbouncer=true to the URL (Prisma's documented workaround).

MySQL

docker run -d --name graph-crm-mysql -e MYSQL_ROOT_PASSWORD=devpass -e MYSQL_DATABASE=graphcrm -p 3306:3306 mysql:8

prisma/schema.prisma:

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

.env:

DATABASE_URL="mysql://root:devpass@localhost:3306/graphcrm"
rm -rf prisma/migrations
npx prisma migrate dev --name init
npm start

Provider-specific things worth checking after switching

  • The Credential.publicKey field is Bytes — MySQL maps this to LONGBLOB, Postgres to BYTEA; both work fine with Prisma's client, no schema changes needed.

  • ApiKey.lastUsedAt and other DateTime? fields are nullable on all providers. Postgres stores timestamptz under Prisma's default mapping; MySQL's DATETIME has no timezone awareness — only matters if you care about exact cross-timezone display of lastUsedAt/createdAt.

  • Both Postgres and MySQL enforce foreign keys and unique constraints the same way SQLite does here (onDelete: Cascade, @@unique), so the cascade-delete behavior used throughout family.js/server.js (deleting a contact cleans up their relationships, interactions, credentials, etc.) carries over unchanged.

  • Running Postgres/MySQL in Docker alongside the app: add both as services in a docker-compose.yml (not included here — this repo ships a single-container Dockerfile since SQLite needs no separate DB service) and point DATABASE_URL at the DB service's hostname instead of localhost.

F
license - not found
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/sriramsv/graph-crm'

If you have feedback or need assistance with the MCP directory API, please join our Discord server