Skip to main content
mcpeye is self-host only. There is no hosted tier. You run Postgres, Redis, the API, the worker, and the dashboard — all in your own stack, with your own LLM key. The only thing that ever leaves your infrastructure is an anonymous deployment ping (a once-daily aggregate count, on by default) — see Deployment ping for exactly what it sends and how to turn it off. Your tool calls, prompts, and results never leave your Postgres.

Run the stack

git clone https://github.com/mcpeye/mcpeye.git
cd mcpeye
cp .env.example .env   # fill in the values below
docker compose up
docker compose up brings up everything. A one-shot migrate service runs first and creates/updates the schema automatically (idempotent — re-running docker compose up applies zero migrations and everything comes back up), so there’s no manual db:push or db:migrate. An LLM key is optional: ingest and session replay work without one. For local development you usually only need the datastores:
docker compose up -d postgres redis

Services and ports

ServicePortEnv varPurpose
Dashboard3000WEB_PORTRead sessions and Intent Gap Reports.
Ingest API3001API_PORTReceives batched tool calls from the SDKs.
Docs3333DOCS_PORTThis site, when run locally.
Postgres5432Stores tool calls, sessions, summaries, reports.
Redis6379BullMQ queue for the summarizer worker.
migrateOne-shot: creates/updates the schema, then exits.
WEB_PORT / API_PORT set the host port (the containers always listen on 3000/3001 internally), so set them in .env if a port is already taken. Boot order: Postgres/Redis become healthy → migrate runs to completion → the API, worker, and dashboard start against an already-migrated database. (Requires Docker Compose v2 for service_completed_successfully.)

Environment variables

Copy .env.example to .env and set these. You control every one of them.
# Postgres (matches docker-compose defaults)
DATABASE_URL=postgresql://mcpeye:mcpeye@localhost:5432/mcpeye

# Redis (BullMQ queue for the summarizer worker)
REDIS_URL=redis://localhost:6379

# LLM provider — OPTIONAL. Used ONLY by the worker to cluster sessions into
# reports. BYO key; you control spend. Leave unset to run without LLM summaries.
# ANTHROPIC_API_KEY=sk-ant-xxxxxxxx

# Anonymous deployment ping — on by default. Set to off to disable entirely.
# MCPEYE_TELEMETRY=off

# Where SDKs send captured tool calls.
MCPEYE_INGEST_URL=http://localhost:3001

# Per-project ingest secret the SDK sends (from "Create project" / POST /projects).
# Set this in your INSTRUMENTED server, not the stack.
MCPEYE_INGEST_SECRET=your-per-project-ingest-secret

# Ports — host port for each service (override if they collide locally)
API_PORT=3001
WEB_PORT=3000
DOCS_PORT=3333
VariableRequiredNotes
DATABASE_URLyesPostgres connection string. Defaults match the bundled compose file.
REDIS_URLyes (worker)Redis connection string for the worker queue.
ANTHROPIC_API_KEYfor reportsOptional. Used only by the worker to summarize sessions and cluster reports.
MCPEYE_INGEST_SECRETSDK sideThe per-project secret from “Create project”. The SDK sends it to authenticate; there is no shared server-wide secret.
MCPEYE_INGEST_URLSDK consumersWhere the SDKs send captured calls. Not needed by the server itself.
MCPEYE_TELEMETRYnoAnonymous deployment ping. On by default; set off to disable. See Deployment ping.
ANTHROPIC_MODELnoOverride the summarizer model. Defaults to claude-haiku-4-5-20251001.

Privacy

Your data stays in your stack. Tool arguments, results, and the prompts behind them are written only to your Postgres. The only data that ever leaves your stack is (a) your LLM calls to the provider you configured, and (b) — unless you set MCPEYE_TELEMETRY=off — a once-daily anonymous deployment ping containing only a random id, a date, and a tool-call count. No prompts, args, results, or tool names are ever sent anywhere.
mcpeye makes exactly two kinds of outbound call:
  1. The worker talking to your LLM provider, using the key you supply, only to summarize sessions and build reports — never on the per-call hot path. If you don’t set an LLM key, ingest and session replay still work; you just won’t get the summaries and Intent Gap Reports.
  2. The deployment ping described below (opt-out).
The SDKs redact obvious secrets before sending, and they only ever send to the MCPEYE_INGEST_URL you configure.

Deployment ping

The worker sends a once-daily anonymous ping so the project can measure how many self-host deployments are piping real traffic. It is on by default and easy to turn off. What it sends — and nothing else:
{ "anonId": "a random uuid, generated once on this deployment",
  "day": "YYYY-MM-DD (UTC)",
  "toolCalls": 1234 }
That is the entire payload: a random id with no link to you, a date, and an integer count of tool calls for that day. It never includes project names, tool names, arguments, intents, results, prompts, or your LLM key. The count is read from your tool_calls table as a single aggregate. How it behaves: the worker logs its status on every boot — either [telemetry] anonymous deployment ping ON … or [telemetry] OFF. A persisted cursor means a restart re-pings only days strictly before today (no double-counting), and if the network is unreachable it logs once and retries tomorrow without ever blocking job processing. Turn it off completely:
MCPEYE_TELEMETRY=off
With telemetry off, the anon id is never even created and nothing is sent. Point it at your own collector instead with MCPEYE_TELEMETRY_URL.

What the LLM pass produces

The Intent Gap Report — the asks your tools couldn’t deliver.