Skip to content

feat(mcp): query Google Cloud metrics live with PromQL - #1478

Open
JeremyFunk wants to merge 9 commits into
feat/gcp-promql-widgetfrom
feat/gcp-promql-mcp
Open

JeremyFunk wants to merge 9 commits into
feat/gcp-promql-widgetfrom
feat/gcp-promql-mcp

Conversation

@JeremyFunk

@JeremyFunk JeremyFunk commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator

The agent can now read a customer's Google Cloud Monitoring metrics live, through Google's PromQL API, with two MCP tools. Nothing is ingested; the 5-minute poller and its gcp.* warehouse metrics stay as they are.

Stacked on #1475 (backend, GcpPromQlService) → #1477 (live-query access) → feat/gcp-promql-widget (dashboard widget). Merge after those, by merge commit.

Tools

  • find_gcp_metrics: search (substring of the metric type) and/or metric (one type to describe), project, limit. Search reads one project's metric list; describe returns kind, value type, unit, monitored resource types, labels and a starter query: sum(rate(…[5m])) for DELTA/CUMULATIVE, avg(…) for GAUGE, histogram_quantile(0.95, sum by (le) (rate(…_bucket[5m]))) for DISTRIBUTION, none for STRING. It adds a monitored_resource matcher when the metric has more than one resource type, because Google refuses the selector otherwise. Output FindGcpMetricsOutput.
  • query_gcp_promql: query, kind (timeseries default or instant at end_time), start_time/end_time (default 1 h, at most 6 weeks, Google's usual retention), bucket_seconds (60–86400, default from the window like the other tools, about 100 points; a finer one is widened to at most 500 points per series, which also bounds the chat payload), projects. Output QueryGcpPromQlOutput: the series sorted by last value, capped at 100 for the chat payload, plus totalSeries, projects and failures.
  • The model reads the top 20 series with last/min/avg/max, the projects that could not be read, and a note that each project is queried on its own (aggregation stays within a project, every series carries project_id). Both descriptions say to aggregate with sum by/avg by because Google bills per series returned, and that these are live reads, separate from list_metrics/query_data.
  • Both tools are public (MCP transport included), read-only, openWorld. The org always comes from the request's tenant.

Errors

Service error MCP error
invalid query (Google's message), too many series, too many projects, project not connected, unknown metric type McpInvalidInputError with the message
no Google service account key on the deployment McpUnavailableError (gcp_live_query)
no Google Cloud connection McpUnavailableError (gcp_connection)
every project failed (reader account missing, API off, denied…) McpNotReadyError, retry in 300 s, with the per-project cause and fix
the connections hold no project McpUnavailableError (gcp_connection)
upstream / persistence McpQueryError

Other changes

  • The ai Worker binds MAPLE_GCP_SERVICE_ACCOUNT_KEY the same way api does (optionalSecret); GcpPromQlService joins the MCP runtime services.
  • Chat: query_gcp_promql range results draw as the query chart (points merged into buckets by time); instant results as a table. find_gcp_metrics uses the generic renderer.
  • maple://instructions gains one tool-selection line; the MCP reference page lists both tools.

Decisions

  • One discovery tool with two modes instead of separate search and describe tools, to keep the catalog small.
  • "No connection" is McpUnavailableError, not McpNotReadyError: retrying cannot help until someone connects a project, which is what that error kind says.
  • Both tools are public: an MCP client with an org credential can trigger live Google reads billed to that org's own host project, like other public read tools; the 500-series cap and the edge cache bound it.
  • "Every project failed" is McpNotReadyError: most causes are fixed by a setup-script run whose grant takes minutes to apply, and the message names the fix.

Not verified without a live system

  • Chat: "Which Cloud Run services had the most requests in the last hour?" → find_gcp_metrics search=run.googleapis.com/request → describe → query_gcp_promql with a sum by (service_name) query; the chart renders.
  • A metric with several resource types (serviceruntime.googleapis.com/api/request_count) gets a working starter query.
  • An org without a Google Cloud connection gets the unavailable answer; a connection whose reader account is missing gets the setup-script message.
  • Over MCP with an API key, the tools list and answer for that key's org only.

View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need.

find_gcp_metrics searches a project's Cloud Monitoring metric types or
describes one with a starter query; query_gcp_promql runs PromQL over a
window or at an instant through GcpPromQlService. The chat renders range
results as a chart. The ai Worker binds MAPLE_GCP_SERVICE_ACCOUNT_KEY.
Explicit bucket_seconds is validated, not clamped; one error tail for both
query kinds; the describe output reuses the descriptor's fields; label
values are quoted and descriptions capped; gauge distributions skip rate;
chart series keep __name__ so none collide.
A bucket_seconds too fine for the window widens to 500 points instead of
a 400, which also bounds the chat payload. No project to read is
unavailable, not a retry. Describe suggestions skip names Google would
refuse; the instant lookback hint shows only on instant queries.
@maple-review-bot

maple-review-bot Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 8/10 · likely safe to merge
Every hunk read; the step widening, window bounds and error mapping are pinned by tests, but the chat render branch is untested and the live Google path cannot run here.
quality 100/100 · no findings · tests partial · risk medium · 3/3 new units observable

Two public MCP tools that run PromQL live against the org's own Google Cloud Monitoring projects, with a chart for range results and a table for instant ones. Wiring, error mapping and the chart rows are consistent with the existing api path; none of the open McpQueryError, SandboxSpawnError, IntegrationsUpstreamError or GithubAppError issues is addressed, and the new mapper adds one more source of McpQueryError on Google upstream failures.

  • query_gcp_promql runs PromQL live per connected project, capped at 100 series
  • find_gcp_metrics searches one project's metric types or describes one
  • Google failures map to typed MCP errors; all-projects-failed is McpNotReadyError
  • Chat draws range results as the query chart, instant results as a table

Before merge

  • Secret · Set MAPLE_GCP_SERVICE_ACCOUNT_KEY in the ai Worker's prd and dev secret stores (unset answers gcp_live_query unavailable) · apps/ai/src/worker.ts:65

Production impact

Open errors in the changed files
Issue Service Occurrences File
SandboxSpawnError maple-chat 52 apps/ai/src/mcp/dispatcher.ts
@maple/mcp/errors/McpQueryError maple-chat 45 apps/ai/src/mcp/tools/registry.ts
@maple/mcp/errors/McpQueryError maple-chat 36 apps/ai/src/mcp/tools/registry.ts
SandboxSpawnError maple-chat 29 apps/ai/src/mcp/dispatcher.ts
SandboxSpawnError maple-chat 23 apps/ai/src/mcp/dispatcher.ts
@maple/mcp/errors/McpQueryError maple-ai 18 apps/ai/src/mcp/tools/registry.ts
SandboxSpawnError maple-chat 17 apps/ai/src/mcp/dispatcher.ts
SandboxSpawnError maple-chat 15 apps/ai/src/mcp/dispatcher.ts
@maple/http/errors/IntegrationsUpstreamError maple-chat 15 apps/ai/src/mcp/tools/registry.ts
@maple/api/vcs/GithubAppError maple-chat 14 apps/ai/src/mcp/tools/registry.ts

After this merges, Maple checks whether they stop.

Telemetry this change adds and removes (2)
  • ➕ span name McpTool.findGcpMetrics · apps/ai/src/mcp/tools/gcp-promql.ts:130
  • ➕ span name McpTool.queryGcpPromQl · apps/ai/src/mcp/tools/gcp-promql.ts:327
What was checked
  • Every read passes the request's orgId (gcp-promql.ts:154, :329); tenant scoping is the service's
  • Step is held >= 60 s and widened to 500 points; a 42-day window still fits the request schema
  • tool-renderer.tsx:173 handles both kinds and falls through to the generic renderer otherwise
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
query_gcp_promql (MCP tool handler) Server yes Effect.fn("McpTool.queryGcpPromQl") with Effect.annotateCurrentSpan orgId/kind/seriesCount (gcp-promql.ts:338, :365)
find_gcp_metrics (MCP tool handler) Server yes Effect.fn("McpTool.findGcpMetrics") with an orgId annotation (gcp-promql.ts:130, :156)
Google Monitoring PromQL reads, one per project Client yes spans and edge caching live in GcpPromQlService (base branch); this diff only puts it on the MCP runtime (dispatcher.ts:299)

72448fa · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@coderabbitai

coderabbitai Bot commented Oct 14, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration
  • Configuration used: defaults
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: c0284413-8aec-4166-904f-92363817b4f2

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Maple review

🟢 Confidence 7/10 · likely safe to merge
The tool handler, error mapping and step widening are pinned by tests; only the chat renderer's instant branch is untested and shows the wrong point.
quality 90/100 · 1 warning · tests partial · risk medium · 3/3 new units observable

Adds two public MCP tools that read Google Cloud Monitoring metrics live through Google's PromQL API (find_gcp_metrics, query_gcp_promql), wires GcpPromQlService and its service-account key into the ai Worker, and renders range results as a chart in chat. Safe to merge once the instant table reads the value at end_time rather than the oldest point.

  • Registers find_gcp_metrics and query_gcp_promql as public MCP tools
  • query_gcp_promql widens a too-fine bucket_seconds to at most 500 points per series
  • Google's errors map onto McpInvalidInputError, McpUnavailableError, McpNotReadyError
  • Chat draws range results with QueryChart, instant results as a table

Before merge

  • Secret · Set MAPLE_GCP_SERVICE_ACCOUNT_KEY in the ai Worker's prd and dev secret stores; unset answers both tools as unavailable · apps/ai/src/worker.ts:65

Findings

🟠 Warning · F1 · Chat instant table shows the oldest point, not the value at end_time

correctness · apps/web/src/components/ai-elements/renderers/tool-renderer.tsx:192

An instant query whose selector is a range vector (sum(rate({"..."}[5m]))) comes back as a matrix with several points per series, and points[0] is the earliest, so the chat table prints a value from as far back as the selector's range while the tool's own table (apps/ai/src/mcp/tools/gcp-promql.ts:388, last point) and the "instant: one value per series at end_time" contract report the latest. Read the last point, as the tool does.

							String(series.points.at(-1)?.[1] ?? "-"),
🤖 Prompt to fix this finding with an AI agent
Findings from an automated review of commit 890f83efe07072ccabb19ffef4e035478db3cb7c. Verify each one against the current code before changing anything, fix only those that still apply, and keep each fix to the lines it names.

---

F1 · Warning · correctness · apps/web/src/components/ai-elements/renderers/tool-renderer.tsx:192
Chat instant table shows the oldest point, not the value at `end_time`
An `instant` query whose selector is a range vector (`sum(rate({"..."}[5m]))`) comes back as a matrix with several points per series, and `points[0]` is the earliest, so the chat table prints a value from as far back as the selector's range while the tool's own table (`apps/ai/src/mcp/tools/gcp-promql.ts:388`, last point) and the "instant: one value per series at end_time" contract report the latest. Read the last point, as the tool does.
Replace those lines with:
							String(series.points.at(-1)?.[1] ?? "-"),

Production impact

Telemetry this change adds and removes (2)
  • ➕ span name McpTool.findGcpMetrics · apps/ai/src/mcp/tools/gcp-promql.ts:130
  • ➕ span name McpTool.queryGcpPromQl · apps/ai/src/mcp/tools/gcp-promql.ts:327
What was checked
  • Both tools read the org from CurrentMcpTenant, never a parameter (gcp-promql.ts:154, 329)
  • Widened step stays far under the schema's 11,000-point cap (GCP_PROMQL_MAX_POINTS, gcp-promql.ts:336)
  • Search asks for limit + 1 and truncates on the extra row (gcp-promql.ts:164-171)
Observability coverage: 3 of 3 changes observable
Change Kind Observable Evidence
MCP tool call find_gcp_metrics Server yes Handler span Effect.fn("McpTool.findGcpMetrics") plus the dispatcher's maple.mcp.tool attribution (dispatcher.ts:122)
MCP tool call query_gcp_promql Server yes Handler span Effect.fn("McpTool.queryGcpPromql"), maple.ai.kind and result.seriesCount attributes
Google Cloud Monitoring PromQL read per project Client yes GcpPromQlService.queryRange/queryInstant Effect.fn spans via the Google client in packages/backend

890f83e · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple-review-bot to ask about one.

@maple-review-bot maple-review-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 inline note from Maple's review. The score and summary are in the review comment above.

headers: ["Series", "Value"],
rows: output.data.series.map((series) => [
seriesKey(series.labels),
String(series.points[0]?.[1] ?? "-"),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Warning

Chat instant table shows the oldest point, not the value at end_time

F1 · Warning · correctness

An instant query whose selector is a range vector (sum(rate({"..."}[5m]))) comes back as a matrix with several points per series, and points[0] is the earliest, so the chat table prints a value from as far back as the selector's range while the tool's own table (apps/ai/src/mcp/tools/gcp-promql.ts:388, last point) and the "instant: one value per series at end_time" contract report the latest. Read the last point, as the tool does.

Suggested change
String(series.points[0]?.[1] ?? "-"),
String(series.points.at(-1)?.[1] ?? "-"),
🤖 Prompt to fix with an AI agent
In `apps/web/src/components/ai-elements/renderers/tool-renderer.tsx:192`: Chat instant table shows the oldest point, not the value at `end_time`.

An `instant` query whose selector is a range vector (`sum(rate({"..."}[5m]))`) comes back as a matrix with several points per series, and `points[0]` is the earliest, so the chat table prints a value from as far back as the selector's range while the tool's own table (`apps/ai/src/mcp/tools/gcp-promql.ts:388`, last point) and the "instant: one value per series at end_time" contract report the latest. Read the last point, as the tool does.

Replace those lines with:

							String(series.points.at(-1)?.[1] ?? "-"),

Verify the problem exists at that location before changing it, and keep the fix to those lines.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant