clarity-mcp
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@clarity-mcpshow me traffic for the last 48 hours by device"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
clarity-mcp
A production-capable MCP server (Model Context Protocol, official
@modelcontextprotocol/sdk, Streamable HTTP transport) that exposes:
Microsoft Clarity aggregate analytics — cached, quota-managed access to the Clarity Data Export API.
Clarity recording metadata — import from the CSV you export in the Clarity dashboard (metadata only; see the limitation below).
Video analysis — ingest a video from an HTTPS URL, transcribe it, extract and analyze keyframes, and answer timestamp-grounded questions.
Stack (unchanged from the prototype): React Router 7 + TypeScript + Prisma + PostgreSQL, plus a separate worker process and FFmpeg for video.
Critical limitation: Clarity session replays are NOT available
Microsoft's documented Clarity Data Export API exposes aggregate metrics only. It does not return session replays, and Clarity "recordings" are DOM/event reconstructions, not MP4 files. This server:
Does not scrape Clarity, call undocumented endpoints, or bypass auth.
Imports recording metadata from the CSV a user exports in the Clarity dashboard (Recordings → Export). Each
recordingUrlis the Clarity dashboard deep-link, preserved as-is — not a downloadable media file.Never claims a replay can be retrieved through the API.
To analyze actual video, use the video tools with a real video URL (a screen recording, an uploaded MP4, etc.) — not a Clarity link.
Related MCP server: DB Analytics & Query Platform
Architecture
┌──────────────┐ POST /mcp (JSON-RPC 2.0, Streamable HTTP)
MCP ───▶ React Router │◀─── bearer auth, Origin/Host check, body limit
client │ app/routes │
│ /mcp.ts │──▶ @modelcontextprotocol/sdk (McpServer, stateless
└──────┬───────┘ WebStandard transport, JSON responses)
│
┌───────┴───────────────────────────────────────────┐
│ tools │
│ • Clarity aggregate → app/clarity.server.ts │
│ cache + atomic quota ledger + snapshots │
│ • Clarity recordings → app/clarity/* │
│ tolerant CSV parser + Prisma │
│ • Video → app/video/*, enqueue only │
└───────────────┬───────────────────────────────────┘
│ ProcessingJob rows (PostgreSQL)
▼
┌──────────────────┐ FOR UPDATE SKIP LOCKED
│ worker (npm run │ claims one job at a time
│ worker) │ stages: downloading → probing →
│ │ extracting_audio → transcribing →
│ FFmpeg + providers│ extracting_frames → analyzing_frames →
└────────┬─────────┘ synthesizing → completed
▼
storage (filesystem volume) + Prisma models
source video, JPEG keyframes TranscriptSegment, ExtractedFrame,
VideoAnalysis, provenanceProviders are behind interfaces (app/providers/types.ts):
speech-to-text, vision, synthesis/QA. The shipped implementation is OpenAI
(OPENAI_API_KEY). Missing credentials produce a clear configuration
error — never a fabricated transcript or analysis. A mock provider exists
for tests only.
Storage is behind StorageProvider (app/storage/types.ts). Only a
filesystem driver ships; an S3-compatible driver can be added without touching
callers. Full video binaries are never stored in PostgreSQL.
MCP protocol notes
Transport: the SDK's
WebStandardStreamableHTTPServerTransportin stateless JSON mode.POST /mcpis preserved.GET /mcpreturns405(no standalone SSE stream in stateless mode — permitted by the spec).DELETE /mcpis accepted.Protocol version is negotiated by the SDK (currently up to
2025-11-25). The server never falsely advertises2024-11-05.Provided by the SDK: protocol negotiation, tool discovery/among invocation, JSON-RPC framing and error codes, input validation against each tool's Zod schema, structured tool results (
structuredContent), and text/image content blocks (get_video_framereturns an image block).Added by this server: bearer auth (
MCP_BRIDGE_SECRET, constant-time compare), Origin/Host allow-lists, request-body size limit, and a compatibility shim that normalizes a lenientAcceptheader for simple one-shot JSON-RPC clients (disable withMCP_STRICT_ACCEPT=true).
Environment variables
See .env.example for the annotated full list. Summary:
Variable | Default | Purpose |
| — | Postgres connection string (required) |
| — | Bearer secret for every MCP call (required) |
| (all) | Comma list of allowed browser Origins |
| (all) | Comma list of allowed Host headers |
|
| Require spec-strict |
|
| Max JSON-RPC body size |
| — | Clarity Data Export API JWT |
|
| Freshness window for cached responses |
|
|
|
|
| CSV import limits |
|
| Object storage |
|
| Max downloaded video size |
|
| Max video duration |
|
| Forced max interval between keyframes |
|
| Hard cap on keyframes per video |
|
| FFmpeg scene-change sensitivity |
|
| Download timeouts |
|
| Redirect limit (re-validated each hop) |
| off-prod: | Permit |
|
| Binary locations |
|
| Worker identity in job locks |
|
| Idle poll interval |
|
| Per-job wall-clock timeout |
|
| Reclaim a crashed worker's job after |
|
|
|
| — | OpenAI credentials |
|
| Model IDs |
|
| Force mock providers outside tests |
| — | Enables DB-backed test suites |
Local development
Requires Node 20 (.nvmrc), PostgreSQL, and FFmpeg (for video).
npm install
cp .env.example .env # fill DATABASE_URL, MCP_BRIDGE_SECRET, CLARITY_API_TOKEN
npx prisma migrate deploy # or: npx prisma migrate dev
npm run dev # http://localhost:3000 (MCP at POST /mcp)
# in a second terminal, for video jobs:
npm run workerDatabase migrations
A real initial migration is committed at
prisma/migrations/20260906000000_init/. On a fresh database:
npx prisma migrate deploy # creates every table + enumAll DateTime columns are timestamptz so job scheduling and quota logic are
correct regardless of the DB server's local time zone.
Docker
export MCP_BRIDGE_SECRET=$(openssl rand -hex 32)
export CLARITY_API_TOKEN=... # Clarity Data Export token
export OPENAI_API_KEY=... # only needed for real video analysis
docker compose up --buildBrings up db (Postgres), app (web/MCP, runs prisma migrate deploy on
start), and worker. They share a clarity_storage volume mounted at
/data/storage. Scale workers:
docker compose up --scale worker=3The Dockerfile is a multi-stage build: a full npm ci for the build
stage, then npm prune --omit=dev; the runtime image is node:20-bookworm-slim
with ffmpeg installed, prod dependencies only, running as the non-root node
user. NODE_ENV=production is set only in the runtime stage.
Worker
npm run worker # tsx app/worker.tsClaims jobs with
SELECT ... FOR UPDATE SKIP LOCKED— run as many as you like; a job is processed by exactly one worker.A crashed worker's job is reclaimed after
WORKER_STALE_LOCK_MS.Failures keep their error message and retry with exponential backoff up to
maxAttempts(3), then the job and video are markedfailed.Graceful
SIGINT/SIGTERM(waits briefly for an in-flight job).
MCP client configuration
Streamable HTTP endpoint, bearer auth:
{
"mcpServers": {
"clarity": {
"type": "http",
"url": "https://your-host.example/mcp",
"headers": { "Authorization": "Bearer <MCP_BRIDGE_SECRET>" }
}
}
}Tools
Clarity aggregate analytics (unchanged from the prototype)
clarity_live_insights, get_traffic, get_engagement_time,
get_scroll_depth, get_popular_pages, get_dead_clicks, get_rage_clicks,
get_quickback_clicks, get_excessive_scroll, get_script_errors,
get_error_clicks, clarity_history, clarity_quota_status,
list_dimensions.
The 10 metric tools share one cached fetch per
(numOfDays + dimensions)— asking for all of them costs 0 extra API calls.Dimensions are de-duplicated (a repeated dimension is dropped;
meta.droppedDuplicateDimensionsreports it). Max 3 distinct.Quota reservation is atomic: a single conditional
UPDATEincrements the per-UTC-day counter only while it is below 10, so concurrent refreshes can never exceed the daily allowance.
Cache refresh policy (CLARITY_REFRESH_POLICY):
Situation | Result |
Fresh cache (age < TTL) | returned immediately, |
Stale + quota remains, | refresh now, return fresh data |
Stale + quota remains, | stale data + |
Stale + quota exhausted | stale data + |
No cache + quota exhausted |
|
| always calls the API if any quota remains |
Stale data is never returned with meta.stale=false.
clarity_history returns snapshots of rolling 1–3 day aggregate
windows (fetchedAt labels each). Overlapping snapshots are point-in-time
captures — do not sum them as daily data. Every response repeats this note.
Clarity recording metadata
import_clarity_recordings — provide exactly one of csvText /
csvUrl (both or neither is rejected). csvUrl gets the same SSRF and
size checks as video URLs. Rows are deduplicated by recording URL, else
session ID, else a hash of the row. replaceExisting: true wipes the table
first. Returns { inserted, updated, skipped, invalidRows, duplicatesInFile, totalRows, headerMap, note }. The parser tolerates Clarity column-name
variations (e.g. Recording link / Recording URL / Replay URL;
Duration / Duration (s) / Duration ms; Country / Country/Region /
Geo). The complete original row is stored in a JSON raw field.
search_clarity_recordings — filters: from/to (start time),
entryUrl, exitUrl, url (either), device, browser, country,
minDurationMs, hasRageClicks, hasDeadClicks, hasScriptErrors; paginate
with limit/offset (returns nextOffset). Each result carries the original
recordingUrl.
get_clarity_recording — by id, sessionId, or recordingUrl (exactly
one). Includes the full original CSV row.
Video
Tool | Purpose |
| Queue a video from an HTTPS |
| Stage ( |
| Executive summary, full description, transcript summary, important events, visible text/OCR, detected objects/interfaces, problems, recommendations, timestamp citations, model provenance. |
| Paginated segments ( |
| Answers only from stored transcript + keyframes + analysis. Every claim carries supporting timestamps; states explicitly when evidence is insufficient. Optional |
| Nearest extracted JPEG keyframe to |
Video is never sent inline as base64. submit_video returns before any
processing; the worker does the work.
Supported formats & limits
Video containers: whatever the target FFmpeg build accepts (mp4/mov/webm/ mkv/mpeg/avi/…). Response
Content-Typemust be a video type orapplication/octet-stream; FFprobe must find a positive duration.Size ≤
VIDEO_MAX_BYTES(500 MB default), duration ≤VIDEO_MAX_DURATION_SEC(3 h default).Keyframes: scene-change detection combined with a forced maximum interval; hard cap
VIDEO_MAX_KEYFRAMES(interval auto-widens so the cap holds).CSV ≤
CLARITY_CSV_MAX_BYTES(25 MB), ≤CLARITY_CSV_MAX_ROWS.
Security & privacy
Remote URLs (videoUrl, csvUrl):
HTTPS required (plain
http://only forlocalhostwhenALLOW_INSECURE_URLS=true).DNS is resolved and every answer checked; private, loopback, link-local, CGNAT and cloud-metadata ranges (IPv4 + IPv6) are blocked in production.
The policy is re-applied after every redirect; redirects are capped.
Connection + total-download timeouts; byte cap enforced from both
Content-Lengthand the live stream.Response
Content-Typeis validated; FFprobe validates the actual media.Filenames are sanitized; temp files are deleted after processing.
Authorization headers and secrets are never logged. Bearer comparison is constant-time.
Personal data. Clarity applies masking to recordings, but imported metadata, and especially video transcripts and extracted frames, may still contain personal or sensitive information (names, emails on screen, faces, voices, internal URLs). Treat the database and the storage volume as sensitive:
Restrict
MCP_BRIDGE_SECRETto trusted callers; put the endpoint behind TLS and, ideally, network controls.Set a retention policy: periodically delete old
VideoAssetrows (cascades to segments/frames/analysis) and their storage prefixes; pruneClarityRecordingandInsightsSnapshot.Keep presigned
videoUrlTTLs short. Presigned/credentialed URLs are stored to allow asynchronous processing but are redacted from all tool output.The storage volume holds the original video and JPEG keyframes — encrypt it at rest and limit access.
Example JSON-RPC calls
All calls: POST /mcp, headers Content-Type: application/json and
Authorization: Bearer $MCP_BRIDGE_SECRET.
// initialize (the SDK negotiates the protocol version)
{"jsonrpc":"2.0","id":1,"method":"initialize",
"params":{"protocolVersion":"2025-06-18","capabilities":{},
"clientInfo":{"name":"my-client","version":"1.0.0"}}}
// discover tools
{"jsonrpc":"2.0","id":2,"method":"tools/list"}
// --- Clarity aggregate ---
{"jsonrpc":"2.0","id":3,"method":"tools/call",
"params":{"name":"get_traffic","arguments":{"numOfDays":2,"dimension1":"OS","dimension2":"Country/Region"}}}
{"jsonrpc":"2.0","id":4,"method":"tools/call",
"params":{"name":"clarity_live_insights","arguments":{"numOfDays":3,"forceRefresh":true}}}
{"jsonrpc":"2.0","id":5,"method":"tools/call","params":{"name":"clarity_quota_status"}}
// --- Clarity recordings ---
{"jsonrpc":"2.0","id":6,"method":"tools/call",
"params":{"name":"import_clarity_recordings",
"arguments":{"csvUrl":"https://example.com/exports/clarity-recordings.csv"}}}
{"jsonrpc":"2.0","id":7,"method":"tools/call",
"params":{"name":"import_clarity_recordings",
"arguments":{"csvText":"Recording link,Session ID,Duration,Device\nhttps://clarity.microsoft.com/player/p/abc/s1,s1,00:01:30,Desktop","replaceExisting":false}}}
{"jsonrpc":"2.0","id":8,"method":"tools/call",
"params":{"name":"search_clarity_recordings",
"arguments":{"from":"2026-02-01T00:00:00Z","device":"Desktop","hasRageClicks":true,"limit":25,"offset":0}}}
{"jsonrpc":"2.0","id":9,"method":"tools/call",
"params":{"name":"get_clarity_recording","arguments":{"sessionId":"s1"}}}
// --- Video ---
{"jsonrpc":"2.0","id":10,"method":"tools/call",
"params":{"name":"submit_video",
"arguments":{"videoUrl":"https://example.com/screen-recording.mp4",
"title":"Checkout walkthrough","language":"en",
"analysisPrompt":"focus on checkout friction","extractVisuals":true}}}
{"jsonrpc":"2.0","id":11,"method":"tools/call",
"params":{"name":"get_video_status","arguments":{"videoId":"<videoId>"}}}
{"jsonrpc":"2.0","id":12,"method":"tools/call",
"params":{"name":"get_video_analysis","arguments":{"videoId":"<videoId>","detail":"detailed"}}}
{"jsonrpc":"2.0","id":13,"method":"tools/call",
"params":{"name":"get_video_transcript","arguments":{"videoId":"<videoId>","startTime":"1:30","endTime":"2:15","limit":100,"offset":0}}}
{"jsonrpc":"2.0","id":14,"method":"tools/call",
"params":{"name":"query_video",
"arguments":{"videoId":"<videoId>","question":"When does the user hit an error at checkout?"}}}
{"jsonrpc":"2.0","id":15,"method":"tools/call",
"params":{"name":"get_video_frame","arguments":{"videoId":"<videoId>","timestamp":"00:01:42.000"}}}End-to-end video flow
SECRET=your_mcp_bridge_secret
call() { curl -s localhost:3000/mcp -H "Authorization: Bearer $SECRET" \
-H 'Content-Type: application/json' -d "$1"; }
# 1. submit -> returns videoId + jobId, immediately
VID=$(call '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"submit_video","arguments":{"videoUrl":"https://example.com/clip.mp4"}}}' \
| jq -r '.result.structuredContent.videoId')
# 2. poll status until "completed" (the worker must be running)
call "{\"jsonrpc\":\"2.0\",\"id\":2,\"method\":\"tools/call\",\"params\":{\"name\":\"get_video_status\",\"arguments\":{\"videoId\":\"$VID\"}}}" | jq '.result.structuredContent.stage'
# 3. read the analysis
call "{\"jsonrpc\":\"2.0\",\"id\":3,\"method\":\"tools/call\",\"params\":{\"name\":\"get_video_analysis\",\"arguments\":{\"videoId\":\"$VID\"}}}" | jq '.result.structuredContent'
# 4. ask a grounded question
call "{\"jsonrpc\":\"2.0\",\"id\":4,\"method\":\"tools/call\",\"params\":{\"name\":\"query_video\",\"arguments\":{\"videoId\":\"$VID\",\"question\":\"What products appear on screen?\"}}}" | jq '.result.structuredContent'Testing
npm test # unit + contract suites (no services needed;
# DB / ffmpeg suites self-skip with a notice)
npm run test:integration # boots a throwaway embedded Postgres, applies the
# migration, and runs EVERY suite including the
# end-to-end pipeline (uses bundled static ffmpeg)To run the DB suites against your own Postgres:
TEST_DATABASE_URL=postgres://... npx prisma migrate deploy
TEST_DATABASE_URL=postgres://... npm testCoverage: MCP init/discovery/list/call, JSON-RPC + auth + origin errors, existing Clarity tools, CSV parsing + column variations + dedupe, search filters + pagination, atomic quota reservation under concurrency, stale-cache behavior, video job state machine (exclusive claim, retry/backoff, permanent fail), transcript pagination + windowing, timestamp conversion, nearest-frame, missing provider credentials, download size + MIME enforcement, SSRF + redirect protection, and a full download→transcribe→frames→analyze→query pipeline. All provider calls are mocked; no test makes a paid API call. The test video is generated with ffmpeg at runtime — no binary fixture is committed.
License
MIT — see LICENSE.
This server cannot be deployed
Maintenance
Related MCP Connectors
MCP server for querying and analyzing data from ad platforms, analytics tools, and spreadsheets
Analytics for MCP servers. Query your tool calls, first-call success, retries and schema cost.
Your whole business as one MCP server: analytics, CRM, SEO, ads, revenue. Scoped per data class.
Read-only Yandex Metrika MCP. Query visits, sources, geo, devices and more in plain language.
Related MCP Servers
- AlicenseAqualityFmaintenanceA Model Context Protocol server that lets you fetch Microsoft Clarity analytics data through Claude for Desktop or other MCP-compatible clients, with support for filtering by dimensions and retrieving various metrics.35,741 npm117MIT
- FlicenseNot gradedqualityDmaintenanceA FastAPI-based server that enables executing SQL queries, managing database connections, and retrieving analytics reports through MCP-integrated endpoints. It allows users to interact with database schemas, performance metrics, and access logs using structured queries.-
- FlicenseNot gradedqualityCmaintenanceRead-only MCP server exposing Microsoft Clarity analytics data as tools for ChatGPT Agent Builder.-
- AlicenseNot gradedqualityCmaintenanceMCP server for Microsoft Clarity Data Export API, providing tools to retrieve traffic, popular pages, engagement metrics, and user behavior insights such as dead clicks, rage clicks, and script errors. Supports multiple projects with daily quota management and shared caching.MIT