Run the whole mcpeye stack with Docker Compose. Your data never leaves your infrastructure.
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.
git clone https://github.com/mcpeye/mcpeye.gitcd mcpeyecp .env.example .env # fill in the values belowdocker 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:
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.)
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=3001WEB_PORT=3000DOCS_PORT=3333
Variable
Required
Notes
DATABASE_URL
yes
Postgres connection string. Defaults match the bundled compose file.
REDIS_URL
yes (worker)
Redis connection string for the worker queue.
ANTHROPIC_API_KEY
for reports
Optional. Used only by the worker to summarize sessions and cluster reports.
MCPEYE_INGEST_SECRET
SDK side
The per-project secret from “Create project”. The SDK sends it to authenticate; there is no shared server-wide secret.
MCPEYE_INGEST_URL
SDK consumers
Where the SDKs send captured calls. Not needed by the server itself.
MCPEYE_TELEMETRY
no
Anonymous deployment ping. On by default; set off to disable. See Deployment ping.
ANTHROPIC_MODEL
no
Override the summarizer model. Defaults to claude-haiku-4-5-20251001.
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:
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.
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.
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.