An MCP server that exposes a running Prometheus instance (or compatible backend, e.g. Thanos) to LLM clients as typed tools and resources, plus search over an embedded snapshot of the official Prometheus docs (Bleve-indexed at startup). README.md is the canonical reference for the tool list, install methods, auth, telemetry, and flags — when a change affects any of those, update the README rather than duplicating it here.
Standard Prometheus tooling: Makefile includes Makefile.common and drives promu; CI is prometheus/promci (.github/workflows/ci.yml). Release artifacts are built in CI, not locally.
| Task | Command |
|---|---|
| Tests | make test |
| Single test while iterating | go test -race -run TestName ./pkg/mcp/ |
| Lint | make lint |
| Full pre-commit check (style, license headers, lint, yamllint, tidy, build, test) | make |
| Build host binary | make build |
| Refresh embedded docs snapshot | make docs |
| List project-specific targets | make help |
Things CI enforces that are easy to miss locally:
- The docs embed.
cmd/prometheus-mcp/main.godeclares//go:embed all:external/docs, and that directory is gitignored —make docspopulates it from a pinnedprometheus/docstarball (DOCS_VERSIONin theMakefile). On a fresh clone, rawgo build ./.../go test ./...fails with a "no matching files" embed error; run any make build/test target once and rawgocommands work from then on (package-scoped commands likego test ./pkg/mcp/don't need the snapshot at all). - License headers. Every source file needs the Apache-2.0 header (
check_licenseruns as part of baremake). Copy it from any existing.gofile when creating a new one. - No drift. CI runs
makeand thengit diff --exit-code— un-tidiedgo.mod/go.sumor anything else the build regenerates must be committed. - golangci-lint (version taken from
make print-golangci-lint-version) andgovulncheckalso gate PRs. - The Go version is pinned in three places —
go.mod,.promu.yml, and the builder image inci.yml. Bump them together.
cmd/prometheus-mcp/main.go— kingpin flag definitions (every flag is also settable via an auto-generated env var, viaDefaultEnvars()), transport selection (--mcp.transport=stdio|http; the stdio loop lives here, HTTP mountsmcp.NewStreamableHTTPHandlerat/mcp), the docsgo:embed, and the exporter-toolkit web server (metrics, pprof, landing page, embedded docs at/docs/). Version info comes fromprometheus/common/version, populated by promu ldflags.pkg/mcp/— the server proper.ServerContainer(server.go) is the DI struct every handler hangs off; tool definitions live intools.go, input types intypes.go, handlers inhandlers.go, toolset composition inregistration.go, MCP resources inresources.go, docs search indocs.go/docs_updater.go. The MCP instructions blob sent to clients ispkg/mcp/assets/instructions.md.- Backend toolsets derive from the base Prometheus toolset: each backend has a
<backend>RemovedToolslist and aninit<Backend>Toolsetinitializer that prunes unsupported tools and adds backend-specific ones (initThanosToolsetis the canonical example). To add a backend, extendPrometheusBackends, add the removal list + initializer, and wire it intogetToolset's switch — don't fork a parallel map.CoreToolsalways loads even when--mcp.toolsnarrows the set;PrometheusTsdbAdminToolsis the destructive set gated behind--dangerous.enable-tsdb-admin-tools. - Docs subsystem: the Bleve index is built at startup from the embedded docs FS;
--docs.auto-updateswaps it at runtime, hencedocsMu sync.RWMutexinServerContainer— read underRLock, write underLock. - Supporting:
pkg/prometheus/(API client builder,UserAgent(), time parsing),internal/metrics/(metrics registry + namespace).
- Input type in
types.go: struct withjsonschematags and aLogValue() slog.Valuemethod so structured logs group fields cleanly. ReuseTimeRangeInput,TruncatableInputwhere applicable. - Tool definition in
tools.go:*mcp.ToolwithAnnotations.ReadOnlyHintorAnnotations.DestructiveHint(use theptr(true)helper). Zero-input tools useInputSchema: emptyInputSchema, not the SDK'sEmptyInput— OpenAI strict-schema workaround, see #119. - Handler method on
*ServerContainerinhandlers.go, signature(ctx, req, input) (*mcp.CallToolResult, any, error). Get clients vias.GetAPIClient(ctx)— see the HTTP plumbing gotcha below; never reach for default client/transport fields directly. - Register in
initPrometheusToolset()inregistration.go; add the tool's name tothanosRemovedToolsif Thanos doesn't support it. LeaveCoreToolsalone unless the tool must load even when--mcp.toolsis narrowed. - Test in
handlers_test.go:api_mock_test.gohas the mock Prometheus, andmcptest.NewTestServerprovides an in-memory client↔server MCP session.registration_test.gopins expected toolset contents — update its expectations whenever tools are added or removed. - Update the tool table in
README.md.
- Time inputs accept epoch seconds, RFC3339, or Go duration strings relative to now (
5m,1h30m). UseParseTimestampOrDuration(pkg/prometheus) /parseTimeWithDefault(handlers.go) — don't hand-parse. - HTTP plumbing.
s.GetAPIClient(ctx)returns both the prom Go client and the matchinghttp.RoundTripper;authContextMiddlewaremakes the RoundTripper request-scoped when anAuthorizationheader is present, so going around it bypasses per-request auth. For anything outsidepromv1.API, calls.doHTTPRequest(ctx, method, rt, path, expectJSON), ors.doManagementAPICall(ctx, method, path)for/-/...endpoints — both share the connection pool, emittarget_path-labelled telemetry, and surface 404s asErrEndpointNotSupported. Reference implementations:ThanosStoresHandler(/api/v1/...), the management API handlers (/-/...). - TSDB admin tools (
PrometheusTsdbAdminTools) setDestructiveHintand only register when--dangerous.enable-tsdb-admin-toolsis set;delete_seriesadditionally requires bothstart_timeandend_timeto prevent accidental full-data deletion. Preserve both properties when touching these handlers. - Metrics register through
metrics.Registrywithprometheus.BuildFQName(metrics.MetricNamespace, ...)— never the global registry. --mcp.enable-toon-outputserializes tool results as TOON instead of JSON — don't hard-code JSON shape assumptions in end-to-end tests.
charts/prometheus-mcp-server has its own CI (.github/workflows/helm-chart.yaml, chart-testing) that runs only when charts/** or grafana/** change; check locally with make helm-lint / make helm-template. charts/prometheus-mcp-server/dashboards/ is generated and gitignored — the dashboard source of truth is grafana/*.json, synced in by make helm-sync-dashboards.