Repository navigation
feat(prometheus): add search tools for metric names, label names and label values - #1273
tcp13equals2 wants to merge 1 commit into
Conversation
…label values Add search_prometheus_metric_names, search_prometheus_label_names and search_prometheus_label_values. They use the experimental Prometheus/Mimir search API (/api/v1/search/*) for fuzzy discovery, which the existing list_prometheus_* tools cannot do. This is the equivalent of grafana/gcx#1374. - Read the NDJSON stream with a 10 MiB limit. A stream without a trailer returns the results read so far, marked incomplete. - Validate inputs before any request, and give a hint when the search API is not enabled on the datasource. - Reject VictoriaMetrics and Cloud Monitoring datasources, which have no search API. - Bump the test Prometheus image from 3.11.3 to 3.15.0 and enable the search-api feature, because the API starts at Prometheus 3.13.0.
|
|
sd2k
left a comment
There was a problem hiding this comment.
Love this, but to keep token usage down I think it would be good to merge this into the list_ tools with a new search parameter, and only hit the search API if the search field is set. If the search API isn't available on the server we can just return an error to that effect, which is what the search_ tools would do anyway.
If we do that, some considerations:
- Return type: the list_ tools return plain strings, while search returns objects with score and metadata. One option is to keep returning strings unless includeMetadata is set.
- Paging: list_prometheus_metric_names has a page parameter, but the search API has no cursor. Either reject page > 1 when search is set, or document that it doesn't apply.
Claude found a few other minor things:
- tools/prometheus_search.go:35: the fuzzThreshold description says "0-100" but doesn't say that with the default jarowinkler algorithm, 0 turns fuzzy matching off and keeps only substring matches. An agent that sets 0 to get more results will get fewer.
- tools/prometheus_search.go:~315: if the stream ends with no trailer, the tool sets HasMore=true as well as Incomplete=true. The HasMore field comment tells the agent to raise the limit, but the Incomplete comment says that won't help. If the 10 MiB cap is hit, an agent will retry with a bigger limit and make it worse. The fix is either to leave HasMore false in that case or to update the field comment.
- tools/prometheus_search.go:31,56: the parameter names don't match the list_ tools. Search uses label where list_prometheus_label_values uses labelName, and search takes matches as PromQL strings where the list_ tools take []Selector. The schema rejects unknown fields (additionalProperties: false), so an agent that sends labelName from habit gets a schema error. (this won't matter if we merge the tools)
|
Hey Ben thanks for taking a look and the feedback! A couple of initial comments on merging the search and list tools ...
Do you still think that given these API differences they should be abstracted behind the 3 existing If the concern is about token usage, could this Prometheus Search be in it's own category and an optional thing to enable? I don't know too much about this MCP world, so I will follow which ever direction you think is best here. Thanks again for taking a look! |
|
@tcp13equals2 Ah that changes things, I think if things are going to evolve that much then it's worth having separate tools. You're right about the separate categories too, we could use
I think we can get this in though. In other news, the v2 branch changed from the mark3labs/mcp-go SDK to the official Go SDK so there are quite a few conflicts, sorry about that 🙈 |
|
Thanks @sd2k ... I'll get some further updates to these search APIs into Prometheus / Mimir, do another round of gcx updates and then update this PR to address your comments / leverage the latest API updates. Thank you for your comments and direction! |
What this changes
Adds fuzzy search of Prometheus/Mimir metric names, label names and label values, through the experimental search API (
/api/v1/search/*). Today thelist_prometheus_*tools match only exact names or a regex, so an assistant has to either have a strong idea of a specific metric/label/value or it has to do a broad query and then curate the full result set client side.This fuzzy search capability allows for fuzzy string matching and relevance sort ordering. This allows for metrics, labels and values to be discovered without knowing the correct/exact spelling or without knowing the exact metric name ordering.
This is the MCP equivalent of grafana/gcx#1374.
Fixes #1274
New or changed tools
Proposal issue: #1274
search_prometheus_metric_namesprometheuscategory)search_prometheus_label_namesprometheuscategory)search_prometheus_label_valuesprometheuscategory)Token cost: About +5.58% over the default tool set - this API provides a range of search options which raises this size.
Why not extend an existing tool? The search API is a different endpoint (
/api/v1/search/*), with fuzzy matching, a relevance score, sorting and an NDJSON stream. Iflist_prometheus_*took search parameters, those tools would change behaviour, change their errors, and fail on servers without the API, which is the default on self-hosted Prometheus and Mimir.The three tools mirror the existing
list_prometheus_metric_names/_label_names/_label_valuestools and gcx's three commands. A single tool with atargetparameter costs less (an estimated +2.77%). It was considered, and three tools were chosen for clearer names and for a schema that enforcessearchandlabelwhere each one is required.Read-only safety: Read-only. The tools send only GET requests to the search endpoints. They are discovery tools, like
list_prometheus_label_values, so--disable-querydoes not remove them.TestProcessTools_DisableQueryRemovesOnlyQueryToolschecks this.Implementation notes
defaultResponseLimitBytes). The API contract says clients must accept an EOF without a trailer, so in that case the tool returns the results it has read, withincomplete: trueand a warning. A mid-stream error line returns an error.prometheusBackendnow also holds anhttp.Clientbuilt on the same transport chain (auth, datasource fallback transport). It is needed becauseapi.Client.Doin client_golang reads the whole body with no size limit.promql/parser), the time range, the enums and their ranges,sortBy=scorewithout search, andsortDirwithoutalpha.limit=0is never sent, because Mimir reads it as unlimited and Prometheus rejects it.list_prometheus_*tool. It handles Mimir's 404feature_not_enabledand Prometheus'sunavailable: search API disabled.prometheusBackend) and Cloud Monitoring datasources are rejected with the same fallback hint.sha256:efd719c9…753e, fromdocker buildx imagetools inspect prom/prometheus:v3.15.0), and--enable-feature=search-apiis added to both starts intestdata/prometheus-entrypoint.sh. The search API starts at Prometheus 3.13.0.How you tested it
make test-unitpasses. The new tests intools/prometheus_search_test.gocover query parameter encoding and defaults, every validation rule, multi-batch streams,has_more, EOF without a trailer, the size limit, the mid-stream error line, duplicate warnings, context cancellation, both "API not enabled" hints after the datasource fallback retry, and rejection of VictoriaMetrics.TestSearchPrometheusIntegrationpasses (metric names with score and metadata, label names on a selector, label values). The existingTestPrometheusToolsandTestPrometheusQueriesalso pass on the new image. I also ran the full integration suite locally. Loki and InfluxDB pass, and so does everything else that started. MySQL and CloudWatch did not run, because other containers on my machine held their ports (3306 and 4566), so CI is the check for those.TestSearchPrometheusCloudtargetsgrafanacloud-promon mcptests. I have not run it locally (no service-account token). I checked with gcx that the search API is enabled on that datasource: a metric-names search foralertsand a label-names search for{__name__=~".+"}both returned results.make lintpasses (0 issues).Checklist
make lintandmake test-unitpass locally--disable-write(no write operations)--disable-query(and--disable-writetoo, if the query is passed through unfiltered) (discovery tools, kept under--disable-querylike thelist_prometheus_*tools)