AI-powered engineering operations platform. Centralizes service monitoring, metrics, incident management, and exposes infrastructure to Claude via the Model Context Protocol (MCP).
- Backend: FastAPI, Python 3.12, SQLAlchemy 2.0 (async), Alembic, PostgreSQL 16 + pgvector, Redis 7, APScheduler + arq.
- AI: Anthropic SDK, MCP Python SDK.
- Frontend: React 18 + TypeScript + Vite + shadcn/ui (Phase 3).
- Infra: Docker, Docker Compose, GitHub Actions, AWS (EC2/RDS/S3), Terraform, Nginx (Phase 4).
devpulse/
├── backend/ FastAPI app, workers, MCP server (one Python package)
├── frontend/ React app (Phase 3)
├── infrastructure/ Terraform, nginx, postgres init scripts
├── docs/ Architecture, ADRs, runbooks
├── docker-compose.yml Local dev orchestration
└── Makefile Common developer commands
See docs/adr/ for architecture decisions.
Requirements: Docker, Docker Compose v2, GNU Make.
cp .env.example .env # then edit secrets
make up # starts postgres, redis, api
curl http://localhost:8000/healthz
make logs # tail container logs
make down # stop everythingRun tests / lint / typecheck:
make test
make lint
make typecheckCI (.github/workflows/ci.yml) runs ruff, mypy, and the full pytest suite
with coverage on every push/PR to main. You can run the same checks
locally with uv, from backend/:
cd backend
uv sync --extra dev # install runtime + dev dependencies
uv run ruff check . # lint
uv run ruff format --check . # formatting
uv run mypy app # strict type check
uv run pytest --cov=app --cov-report=term-missing --cov-report=html
# open backend/htmlcov/index.html for the full coverage reportIntegration tests spin up ephemeral Postgres/Redis containers via testcontainers, so Docker must be running locally — no other setup or secrets required.
| Phase | Area | Status |
|---|---|---|
| Phase 1 | Core backend (auth → incidents) | Done (auth, orgs, services, metrics, alerts, incidents) |
| Phase 2 | AI layer (Claude triage, MCP, RAG) | In progress — Claude-backed incident triage and an MCP server shipped; RAG next |
| Phase 3 | Frontend | — |
| Phase 4 | Production infrastructure (AWS) | In progress — pagination, rate limiting, Claude timeouts/retries, prod secret guard shipped; deployment + observability next |
| Phase 5 | Polish, docs, demo | — |
Every incident can be triaged by Claude on demand:
POST /api/v1/organizations/{org_id}/incidents/{incident_id}/triage
GET /api/v1/organizations/{org_id}/incidents/{incident_id}/triage # history, newest first
Claude gets the incident's title, severity, status, linked service (if any),
and description, and returns a structured assessment via a forced tool
call (summary, suggested_severity, confidence) — not freeform text —
so the response shape is reliable regardless of model phrasing. Each
triage run is persisted (incident_triages table) rather than overwriting
the last one, so an incident keeps a full triage history as new
information comes in.
Requires ANTHROPIC_API_KEY (see .env.example). Backed by
app/modules/ai/. Both triage and postmortem generation share one
per-org, per-minute rate limit (ANTHROPIC_RATE_LIMIT_PER_MINUTE, default
20) — see Production hardening.
POST /api/v1/organizations/{org_id}/incidents/{incident_id}/postmortem
GET /api/v1/organizations/{org_id}/incidents/{incident_id}/postmortem # history, newest first
Claude writes the postmortem strictly from an incident's recorded facts —
title, severity, status, open/resolve timestamps, linked service, description,
and its full triage history — and is explicitly instructed not to invent a
root cause, metric, or system name the data doesn't support. If the facts
don't establish a root cause, the postmortem says so plainly and names what
information would be needed, rather than guessing one. Like triage, every
generation is persisted (postmortems table) rather than overwriting the
last one.
Requires ANTHROPIC_API_KEY (see .env.example). Backed by
app/modules/ai/.
backend/mcp_server/ exposes incidents, services, triage, and postmortem
generation to any MCP client (Claude Desktop, or anything else speaking the
protocol) as tools: list_incidents, get_incident, create_incident,
update_incident_status, triage_incident, list_incident_triages,
generate_postmortem, list_postmortems, list_services.
It's a separate, lightweight process from the API itself — it holds no database connection of its own. Every tool call is a plain HTTP request against the already-running DevPulse API, so it inherits the exact same authorization rules (org membership, etc.) as every other API client, rather than duplicating that logic.
cd backend
uv sync --extra dev
DEVPULSE_MCP_ACCESS_TOKEN=<a valid access token>
DEVPULSE_MCP_ORG_ID=<the org to scope every call to>
DEVPULSE_MCP_API_BASE_URL=http://localhost:8000/api/v1 # default; override for a deployed API
uv run devpulse-mcpPoint an MCP client's stdio config at uv run devpulse-mcp (run from
backend/, with the three env vars above set) to connect it.
Moving past demo-level behavior, in progress:
- Pagination — every list endpoint whose result set grows without
bound (incidents, services, alert events, metrics) returns a
{items, total, limit, offset}page (limitcapped at 200) instead of an unbounded bare array. Config objects whose cardinality is inherently small and human-bounded (orgs a user belongs to, org members, alert rules) intentionally keep returning a bare list. - Rate limiting — triage and postmortem generation (the two endpoints
that call the paid Claude API) share one Redis-backed, per-org,
per-minute budget (
ANTHROPIC_RATE_LIMIT_PER_MINUTE), so a bug or an abusive client can't run up unbounded API spend. A 429 includesRetry-After. - Claude request timeout + retries — the Anthropic SDK's own default
read timeout is 600s, which would hold a DB session open for minutes on
a stalled call;
ANTHROPIC_REQUEST_TIMEOUT_SECONDS(default 30) andANTHROPIC_MAX_RETRIES(default 2, the SDK's own default made explicit) bound that. - Prod secret guard —
Settingsrefuses to construct whenAPP_ENV=prodandSECURITY_JWT_SECRETis still the default or the.env.exampleplaceholder (or under 32 chars), so a deployment can't silently go live signing tokens with a secret anyone could read in this repo.
Not yet done: an actual deployment target (there's a Dockerfile, but
nothing is live anywhere), error tracking (Sentry or equivalent), and
request correlation IDs.