Backend-only Node.js/TypeScript service that exposes Fathom meeting data through both REST APIs and a Model Context Protocol (MCP) interface. It is built with production-grade auth, logging, error handling, and test coverage so you can lift it into your own infrastructure with minimal work.
Highlights
- API Key + OAuth 2.0 Authorization Code authentication with automatic refresh.
- REST + MCP parity for meetings list/summary/transcript and webhook handling.
- Webhook signature verification (HMAC-SHA256) and consistent error envelopes.
- Optional mock data mode for offline/local development.
- Architecture
- Quickstart
- Configuration
- Authentication & Webhooks
- REST API Reference
- MCP JSON-RPC Reference
- Testing Strategy
- Manual Sanity Checks
- Extensibility Notes
flowchart LR
Client[["REST / MCP Clients"]] -->|HTTP / JSON-RPC| App["Express App"]
App --> Auth["multiAuth Middleware"]
Auth -->|API Key| FathomClient["FathomClient"]
Auth -->|OAuth| OAuthSvc["OAuth Service"]
OAuthSvc --> TokenStore[/"InMemory Token Store"/]
FathomClient -->|Live requests| FathomAPI[/"Fathom Cloud API"/]
FathomClient -->|Fallback| MockData[/"Deterministic mock data"/]
App --> WebhookRouter["Webhook Router"]
WebhookRouter --> SigVerifier["FathomWebhookService HMAC check"]
App --> MCPRouter["MCP Router (JSON-RPC)"]
Everything funnels through the same service layer, ensuring feature parity between REST and MCP. When outbound traffic to Fathom is blocked (e.g., CI, local dev), the client can automatically fall back to mock payloads.
- Node.js 20+
- npm 10+
npm install # install deps
npm run build # compile TypeScript -> dist
npm run dev # ts-node + mock data enabled by default
npm test # run unit + integration suitesThe server listens on PORT (default 4000).
Create a .env file (or export the variables) before running:
| Variable | Description | Default |
|---|---|---|
PORT |
HTTP port | 4000 |
SERVICE_NAME |
Log identifier | mcp-backend |
FATHOM_BASE_URL |
Fathom REST origin | https://api.fathom.video |
FATHOM_ALLOWED_API_KEYS |
Comma-separated API-key allow list (optional) | (empty) |
FATHOM_WEBHOOK_SECRET |
Shared secret for HMAC verification | change-me |
FATHOM_OAUTH_CLIENT_ID / FATHOM_OAUTH_CLIENT_SECRET |
OAuth client credentials | demo placeholders |
FATHOM_OAUTH_TOKEN_URL |
Token endpoint | Fathom default |
FATHOM_USE_MOCKS |
Serve deterministic mock data (great for local/offline) | true when NODE_ENV!==production |
Example .env:
PORT=4000
SERVICE_NAME=mcp-backend
FATHOM_WEBHOOK_SECRET=whsec_xxx
FATHOM_OAUTH_CLIENT_ID=your-client-id
FATHOM_OAUTH_CLIENT_SECRET=super-secret
FATHOM_ALLOWED_API_KEYS=demo-api-key-1,demo-api-key-2
FATHOM_USE_MOCKS=true
sequenceDiagram
participant Client
participant API as MCP Backend
participant Auth as multiAuth
participant OAuth as OAuthService
participant Fathom as Fathom API
participant Mock as Mock Data
Client->>API: HTTP / JSON-RPC + headers
API->>Auth: validate headers
alt api-key
Auth->>API: {mode: api-key, apiKey, userId}
else oauth
Auth->>OAuth: getValidAccessToken(userId)
OAuth-->>Auth: access token (refresh if needed)
Auth->>API: {mode: oauth, userId}
end
API->>Fathom: fetch meetings/summary/transcript
alt network failure & mocks enabled
Fathom--X API: error
API->>Mock: serve deterministic payload
else success
Fathom-->>API: real payload
end
API-->>Client: JSON response
- Fathom sends
X-Fathom-Signature = HMAC_SHA256(secret, rawBody). - Express stores the raw body, we recompute the hash, and reject mismatches with
401. - Valid events are logged and handed off for further processing.
| Endpoint | Description |
|---|---|
GET /health/live |
Liveness probe. |
GET /api/fathom/meetings |
List meetings for the authenticated user. Supports limit/cursor. |
GET /api/fathom/meetings/:id/summary |
Summary/highlights/action items. |
GET /api/fathom/meetings/:id/transcript |
Full transcript with pagination/limit. |
POST /webhook/fathom |
Receives meeting webhooks (signature required). |
# List meetings via API key mode (mock data when FATHOM_USE_MOCKS=true)
curl "http://localhost:4000/api/fathom/meetings?limit=1" \
-H 'x-auth-mode: api-key' \
-H 'x-user-id: demo-user' \
-H 'x-fathom-api-key: <your-api-key>'
# Meeting summary (works for OAuth or API key)
curl http://localhost:4000/api/fathom/meetings/meeting-demo-1/summary \
-H 'x-auth-mode: api-key' \
-H 'x-user-id: demo-user' \
-H 'x-fathom-api-key: <your-api-key>'
# Transcript with pagination
curl "http://localhost:4000/api/fathom/meetings/meeting-demo-1/transcript?limit=2" \
-H 'x-auth-mode: api-key' \
-H 'x-user-id: demo-user' \
-H 'x-fathom-api-key: <your-api-key>'
# Webhook (replace signature with HMAC)
curl -X POST http://localhost:4000/webhook/fathom \
-H 'Content-Type: application/json' \
-H 'x-fathom-signature: <hmac>' \
-d '{"event":"meeting.completed","meetingId":"abc123"}'Send JSON-RPC payloads to POST /mcp with Content-Type: application/json.
{
"jsonrpc": "2.0",
"id": 1,
"method": "list_meetings",
"params": {
"auth": {
"mode": "api-key",
"userId": "user-123",
"apiKey": "demo-api-key-1"
},
"limit": 10
}
}{
"jsonrpc": "2.0",
"id": "hook-1",
"method": "handle_webhook_event",
"params": {
"signature": "<hmac>",
"event": {
"event": "meeting.completed",
"meetingId": "abc123"
}
}
}Error responses follow JSON-RPC conventions (code, message, optional data).
| Type | Suite | What it validates |
|---|---|---|
| Unit | src/modules/auth/__tests__/oauthService.test.ts |
OAuth code exchange, refresh, and error handling logic. |
| Unit | src/modules/fathom/__tests__/webhookService.test.ts |
Pure HMAC verification logic. |
| Unit | src/middleware/__tests__/multiAuth.test.ts |
Header validation, allow-list enforcement, request context injection. |
| Integration (HTTP) | src/modules/fathom/__tests__/fathomRouter.test.ts |
REST controllers wired through createApp, covering API key & OAuth paths plus pagination/validation failures. |
| Integration (HTTP) | src/modules/fathom/__tests__/fathomMock.e2e.test.ts |
Full end-to-end flows mirroring the curl commands (meetings list + summary + transcript) while mock mode is enabled. |
| Integration (HTTP) | src/modules/fathom/__tests__/webhookController.test.ts |
Webhook router enforcing raw-body capture, signature verification, and success path. |
| Integration (JSON-RPC) | src/mcp/__tests__/mcpRouter.test.ts |
MCP interface happy paths plus error cases (unknown methods, missing auth, invalid JSON-RPC version). |
Run everything with npm test (27 specs across 7 suites).
With the dev server running (mock mode on):
curl http://localhost:4000/health/live
curl "http://localhost:4000/api/fathom/meetings?limit=1" -H 'x-auth-mode: api-key' -H 'x-user-id: demo-user' -H 'x-fathom-api-key: <key>'
curl http://localhost:4000/api/fathom/meetings/meeting-demo-1/summary -H 'x-auth-mode: api-key' -H 'x-user-id: demo-user' -H 'x-fathom-api-key: <key>'
curl "http://localhost:4000/api/fathom/meetings/meeting-demo-1/transcript?limit=2" -H 'x-auth-mode: api-key' -H 'x-user-id: demo-user' -H 'x-fathom-api-key: <key>'The outputs should match the deterministic mock payloads (handy for demos/automated smoke tests).
- Token Persistence: swap
InMemoryOAuthTokenStorewith Redis/Dynamo/etc. for multi-instance deployments. - Mock-to-live switch: set
FATHOM_USE_MOCKS=falsein staging/production to hit real Fathom APIs. - Webhook fan-out: plug
FathomWebhookService.handleEventinto queues or workflows as needed. - Multi-tenancy: the authentication context travels with each request (
req.authContext), making it straightforward to plug in tenant routing or per-user storage. - Observability: structured Pino logs already redact sensitive headers; pipe them into your log stack for alerting/metrics.
Enjoy shipping Fathom data safely across REST, MCP, and webhooks 🚀