DevPulse

mcp
Guvenlik Denetimi
Uyari
Health Uyari
  • License — License: MIT
  • Description — Repository has a description
  • Active repo — Last push 0 days ago
  • Low visibility — Only 7 GitHub stars
Code Gecti
  • Code scan — Scanned 12 files during light audit, no dangerous patterns found
Permissions Gecti
  • Permissions — No dangerous permissions requested

Bu listing icin henuz AI raporu yok.

SUMMARY

AI-powered engineering-ops platform: service monitoring, metrics, and incident management, exposed to Claude via MCP.

README.md

DevPulse

DevPulse is an engineering-ops backend that tracks services, metrics, alerts, and incidents for an organization, then hands that incident data to Claude for triage and postmortem drafting, both through a REST API and through a native MCP server so any MCP client (Claude Desktop included) can query and act on the same data. It is not a demo wrapped around a chatbot: it has org-scoped auth, Redis-backed rate limiting on every paid AI call, pagination on every unbounded list endpoint, a prod-safe secret guard, and a test suite that spins up real Postgres and Redis containers instead of mocking the database away.

CI
License
Python
FastAPI
SQLAlchemy
Ruff
mypy
Coverage

What it does

DevPulse models the core of an incident-management tool (think a small internal PagerDuty slice): organizations and members, services, metrics, alert rules and alert events, and incidents with a status lifecycle. On top of that it adds an AI layer backed by the Anthropic API:

  • Incident triage. Claude reads an incident's title, severity, status, linked service, and description, and returns a structured summary, a suggested severity, and a confidence score, enforced through a forced tool call rather than parsed from freeform text. Every run is appended to an incident_triages table, so an incident keeps a full triage history, not just the latest guess.
  • Postmortem generation. Claude drafts a postmortem strictly from the incident's recorded facts (timestamps, severity, linked service, full triage history) and is explicitly instructed to say "the data doesn't support a root cause" rather than invent one. Every draft is persisted, same as triage.
  • MCP server. A separate process (backend/mcp_server/) exposes incidents, services, triage, and postmortems as MCP tools, so a model talking over the Model Context Protocol can list incidents, create one, trigger a triage, or pull a postmortem, all through the same authenticated REST API everything else uses.

Architecture

flowchart LR
    subgraph Clients
        UI[REST API client]
        MCPClient[MCP client, e.g. Claude Desktop]
    end

    subgraph DevPulse Backend
        API[FastAPI app<br/>auth, orgs, services,<br/>metrics, alerts, incidents]
        AI[AI module<br/>triage + postmortem service]
        RL[Redis rate limiter<br/>per-org, per-minute]
        MCP[MCP server<br/>stdio process, no DB of its own]
    end

    PG[(PostgreSQL 16<br/>+ pgvector)]
    REDIS[(Redis 7)]
    CLAUDE[Anthropic API<br/>Claude, forced tool calls]

    UI -->|HTTPS + JWT| API
    MCPClient -->|MCP over stdio| MCP
    MCP -->|HTTPS + same access token<br/>same org-scoped authz| API
    API --> PG
    API --> AI
    AI --> RL
    RL --> REDIS
    AI -->|structured tool-call request| CLAUDE
    CLAUDE -->|summary, severity, confidence| AI
    AI -->|persist triage / postmortem| PG

The MCP server deliberately holds no database connection of its own. Every tool call it makes is a plain HTTP request against the already-running API, so it inherits the exact same org-membership authorization as any other client instead of duplicating that logic in a second place.

Inside the API, each domain (app/modules/*) is a self-contained router → service → models slice; the only module every other one depends on is auth (for CurrentUser), and ai is the one exception that reaches sideways into incidents.service to read the facts it triages or summarizes:

flowchart TB
  AUTH["modules/auth/<br/>router, service, security"]

  subgraph Domain["Sibling domain modules"]
    SVC["modules/services/"]
    MET["modules/metrics/"]
    ALR["modules/alerts/"]
    INC["modules/incidents/"]
  end

  SVC --> AUTH
  MET --> AUTH
  ALR --> AUTH
  INC --> AUTH

  AI["modules/ai/<br/>router, service, client"] --> AUTH
  AI -->|reads incident facts| INC
  AI --> CLAUDE[("Anthropic API")]

  MCPC["mcp_server/client.py"] -->|plain HTTPS, bearer token, no DB access| INC
  MCPC -->|plain HTTPS| SVC

  AUTH --> DB[("PostgreSQL")]
  SVC --> DB
  MET --> DB
  ALR --> DB
  INC --> DB
  AI --> DB

Features

  • Org-scoped auth with argon2id password hashing and JWT access tokens.
  • Services, metrics, alert rules, alert events, and incidents, each with its own router, service layer, and SQLAlchemy models.
  • Claude-backed incident triage with a persisted history (incident_triages), driven by a forced tool call so the response shape never depends on how the model phrases things.
  • Claude-backed postmortem generation, grounded only in recorded incident facts, with a persisted history (postmortems).
  • An MCP server (devpulse-mcp) exposing list_incidents, get_incident, create_incident, update_incident_status, triage_incident, list_incident_triages, generate_postmortem, list_postmortems, and list_services as MCP tools.
  • Every unbounded list endpoint (incidents, services, alert events, metrics) returns {items, total, limit, offset} instead of a bare array; small, human-bounded collections (org members, alert rules) intentionally stay as plain lists.
  • A Redis-backed, fixed-window rate limit shared by triage and postmortem generation, scoped per organization per minute, returning 429 with Retry-After when exceeded.
  • An explicit Anthropic request timeout and retry count instead of the SDK's 600 second default, so a stalled Claude call can't hold a database session open for minutes.
  • A prod secret guard: Settings refuses to construct when APP_ENV=prod and the JWT signing secret is still a default, a placeholder, or under 32 characters.

Tech stack

Layer Technology
API FastAPI, Python 3.12, Pydantic v2
Database PostgreSQL 16 with pgvector, SQLAlchemy 2.0 (async), Alembic migrations
Cache / queue Redis 7, arq, APScheduler
Auth argon2id (argon2-cffi), PyJWT
AI Anthropic SDK (forced tool calls), MCP Python SDK
Testing pytest, pytest-asyncio, testcontainers (real Postgres/Redis in CI), freezegun
Tooling ruff, mypy (strict), uv, Docker Compose, GitHub Actions
Logging structlog (structured JSON)

Frontend (React 18, TypeScript, Vite, shadcn/ui) is planned for a later phase and is not implemented yet, so there is no UI to screenshot today; everything above is exercised through the REST API, the test suite, and the MCP server.

Running it locally

Requirements: Docker, Docker Compose v2, GNU Make.

cp .env.example .env       # then edit secrets, including ANTHROPIC_API_KEY
make up                    # starts postgres, redis, api
curl http://localhost:8000/healthz
make logs                  # tail container logs
make down                  # stop everything

Tests, lint, and type-check:

make test
make lint
make typecheck

Or without Docker, from backend/ with uv:

cd backend
uv sync --extra dev
uv run ruff check .
uv run ruff format --check .
uv run mypy app
uv run pytest --cov=app --cov-report=term-missing --cov-report=html

Integration tests spin up ephemeral Postgres and Redis containers via testcontainers, so Docker needs to be running, but no manual setup or secrets are required for them.

To run the MCP server against a running API:

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> \
uv run devpulse-mcp

Benchmarks

Reproducible locally with backend/scripts/run_bench.sh (seeds an org with 200 incidents, then runs ApacheBench against three endpoints that exercise different layers of the stack). Measured against the make up Docker stack (API + Postgres + Redis, no external AI calls) on an Apple M1, 8 cores:

Endpoint What it exercises Requests/sec Median p95 p99
GET /organizations/{org_id}/incidents (c=10, n=500) JWT auth, org-scoped paginated query 339 25ms 55ms 114ms
POST /organizations/{org_id}/incidents (c=10, n=300) JWT auth, org-scoped insert 350 25ms 40ms 107ms
POST /auth/login (c=10, n=200) argon2id password verification 15 587ms 1637ms 1995ms

The login endpoint is ~25x slower than the authenticated CRUD paths, deliberately: argon2id's memory-hard hashing is the whole point of that cost, not a bug to optimize away. The Claude-backed triage and postmortem endpoints are excluded from this benchmark because they call a real, metered Anthropic API; the rate limiter and timeout/retry settings described below exist precisely because that cost isn't something to load-test casually.

These numbers describe this machine and this seed, not a production SLA; rerun run_bench.sh on your own hardware before citing a number elsewhere.

What's technically interesting here

  • Forcing structure out of an LLM, not parsing it out. Both triage and postmortem generation use Anthropic's tool-call mechanism to force Claude into a fixed response shape (summary, suggested_severity, confidence for triage) instead of asking for JSON in a prompt and hoping. That removes an entire class of "the model added a markdown fence around its JSON" bugs that string-parsed LLM integrations tend to hit in production.
  • An MCP server with no database of its own. Rather than building a second data-access layer for MCP clients, mcp_server/ is a thin stdio process that calls the real API over HTTP using a real access token. That one design choice means the authorization rules (who can see which org's incidents) only exist in one place, and the MCP surface can never drift out of sync with the REST API's behavior as the product grows.
  • Treating the Claude API as a cost center, not just a feature. The rate limiter, the explicit request timeout (overriding the SDK's 600-second default), and the retry count are all aimed at one failure mode that most AI side-projects never think about: a bug or an abusive client quietly running up real money against a paid API while holding database connections open. Those three pieces plus the prod secret guard are the difference between "AI demo" and "AI feature with a production incident budget."

Project status

Phase Area Status
Phase 1 Core backend (auth through incidents) Done
Phase 2 AI layer (Claude triage, MCP, RAG) In progress: triage, postmortems, and the MCP server are shipped; RAG is next
Phase 3 Frontend Not started
Phase 4 Production infrastructure (AWS) In progress: pagination, rate limiting, Claude timeouts/retries, and the prod secret guard are shipped; deployment and observability are next
Phase 5 Polish, docs, demo Not started

See docs/adr/ for the architecture decision records behind these choices.

Yorumlar (0)

Sonuc bulunamadi