Skip to content

WIP: feat: add timeseries visualization for range queries via MCP Apps#30

Draft
falox wants to merge 13 commits intorhobs:mainfrom
falox:mcp-apps-timeseries
Draft

WIP: feat: add timeseries visualization for range queries via MCP Apps#30
falox wants to merge 13 commits intorhobs:mainfrom
falox:mcp-apps-timeseries

Conversation

@falox
Copy link

@falox falox commented Feb 9, 2026

Summary

Adds interactive timeseries chart visualization for execute_range_query results via the MCP Apps protocol. When used with an MCP Apps-capable client, range queries now return an embedded Chart.js visualization instead of raw JSON, providing an intuitive graphical view of metrics data with theme support, interactive legends, and responsive design.

light dark

IMPORTANT: remove this vibe-hack before to merge!

Key Features

Chart UI (pkg/mcp/ui/)

  • Interactive Chart.js visualization for timeseries data with PatternFly v6 color palette
  • Theme support (light/dark) synchronized with host application
  • Interactive legend with click-to-toggle series visibility and drag-to-scroll
  • Smart X-axis formatting: shows dates when range > 24h, times otherwise (deduplicated)
  • Copy button for PromQL queries
  • Responsive layout that adapts to container size
  • Optional title parameter for custom chart headings

Test Harness (cmd/test-mcp-apps/)

  • Standalone development server for chart UI iteration without needing MCP client or Prometheus
  • Configurable test data: theme, series count (1/3/5/10), time range (30min - 3 days)
  • URL persistence: browser refresh preserves selections
  • Live reload: UI file changes picked up on browser refresh (no restart needed)

Technical Implementation

  • MCP Apps protocol: full lifecycle implementation (ui/initialize, ui/resource-teardown, size notifications)
  • Embedded assets: Chart.js v4 UMD + custom date adapter + app.js inlined via go:embed
  • Graceful degradation: non-MCP clients still receive JSON text results
  • Zero external dependencies: no CDN, fully self-contained HTML

Testing

Test Harness (no dependencies)

make test-mcp-apps  # Open http://localhost:9199
image

With MCP Client

  • Run go run ./cmd/obs-mcp/ --listen 127.0.0.1:9100 --auth-mode kubeconfig --insecure
  • Connect with an MCP Apps-capable client
  • Call execute_range_query with a valid PromQL query
  • Verify chart renders with correct data and theme

Backward Compatibility

Non-MCP clients (stdio, HTTP without Apps support) continue to receive JSON text results unchanged.

falox added 13 commits February 7, 2026 15:00
Implement interactive HTML chart visualization for execute_range_query
results using the MCP Apps extension (io.modelcontextprotocol/ui).

Changes:
- Upgrade mcp-go to v0.44.0-beta.3 for MCP Apps support (protocol
  version 2025-11-25 with _meta serialization)
- Add embedded uPlot-based chart HTML (pkg/mcp/ui/chart.html)
- Register ui://timeseries-chart resource in MCP server
- Link execute_range_query tool to chart UI via _meta.ui.resourceUri

The chart app uses the passive pattern: it receives RangeQueryOutput
data via ui/notifications/tool-result and renders multi-series line
charts with timestamp formatting and metric labels. The implementation
is backward compatible - non-UI clients continue receiving JSON text
results unchanged.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Modify cardinality checks to skip when TSDB endpoint is unavailable,
allowing guardrails to work with Thanos instances. Add helper function
isTSDBUnsupported() to detect 404 client errors from TSDB API calls.

When MaxMetricCardinality or MaxLabelCardinality guardrails are enabled,
queries to Thanos would previously fail because Thanos does not expose
the /api/v1/status/tsdb endpoint. Now the cardinality checks are skipped
when the endpoint returns a client error, while other TSDB errors still
fail as expected.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Replace custom charting implementation with Chart.js v4 for MCP Apps
timeseries visualization. Adds responsive design features and better
theming support.

Key changes:
- Chart.js v4 with custom date adapter (no external dependencies)
- Responsive tick counts based on viewport width
- Percentage unit support with Y-axis title and 0-100 range
- Horizontal-only grid lines with X-axis tick marks
- Hidden legend, truncated tooltip labels for small screens
- Transparent background with card-only styling
- Query display with mixed font styles (label + monospace value)

New files:
- pkg/mcp/ui/{app.js,styles.css,chart.min.js,date-adapter.js}
- cmd/test-chart: standalone dev server on http://localhost:9199
- Added Unit field to RangeQueryOutput for Y-axis formatting

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Move percent unit label from Y-axis title to dedicated element
above the chart for cleaner presentation. Remove "Query:" prefix
from query display, showing only the PromQL query string.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Add a 'title' input parameter to execute_range_query so the LLM can
provide a human-readable chart title (e.g., "API Error Rate Over Last
Hour"). The title is displayed above the chart when provided and hidden
when omitted. Update the test harness with a title selector dropdown.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Add min-height to chart card (300px) and chart wrapper (200px) so the
UI requests sufficient space from the host iframe. Fix ResizeObserver
to report full outer dimensions (offsetWidth/Height + margins) instead
of contentRect, which excluded border and margin from the reported size.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Start the chart card hidden (display: none) and report zero size to
the host so the iframe occupies no space. Show the card only when
tool-result delivers valid structuredContent, and collapse it again
on resource-teardown.

Update test harness to simulate this: remove auto-send on load,
add Clear button for teardown, resize iframe dynamically based on
size-changed notifications, and remove duplicate Resend button.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Add a custom HTML legend below the chart canvas that displays
dataset labels with color swatches. Clicking a legend item
toggles the corresponding dataset visibility. The legend is
scrollable with a max height and updates on chart render,
no-data, and chart destroy events.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Remove the unit label, percent-specific Y-axis formatting, and all
related code from the chart UI, output schema, and test harness.
Increase title font size and spacing between title and chart.
Auto-load chart data on iframe initialization.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Add a copy-to-clipboard button with inline SVG icon before the
PromQL query. Make the query and legend areas drag-to-scroll with
grab cursor. Restructure query footer as flex container so padding
is respected on both sides. Update test harness with text input
for title and revised time range options.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Show date labels (deduplicated) on the X-axis when the time range
exceeds 24 hours, instead of repeating ambiguous time-only labels.

Rebuild chart HTML on every /chart request so UI file changes are
picked up on browser refresh without restarting the server.

Persist test harness selections (theme, series, range, title) in URL
query parameters for refresh-safe state.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
Rename cmd/test-chart to cmd/test-mcp-apps and update the Makefile
target accordingly. Use a single metric name for all sample series
so values share a consistent scale.

Signed-off-by: Alberto Falossi <afalossi@redhat.com>
@openshift-ci
Copy link

openshift-ci bot commented Feb 9, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by: falox
Once this PR has been reviewed and has the lgtm label, please assign rexagod for approval. For more information see the Code Review Process.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@iNecas
Copy link
Contributor

iNecas commented Feb 9, 2026

@falox what's the state of you reviewing the code after the vibe-coding part? Just to avoid the maintainers being the first ones to actually be confronted with the code?

</div>

<script>
var dark = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

That's a lot of javascript hidden inside a string: deserves to be pulled to a separate file and embedded here instead.

Copy link
Contributor

Choose a reason for hiding this comment

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

and there already is the app.js. What is the reason part of it is there and part here? I don't feel like vibe-reviewing the PR until it's critically reviewed by the person whos name is in the commit message :)

@iNecas
Copy link
Contributor

iNecas commented Feb 9, 2026

Side-note: we would need an appropriate mechanism to pull js dependencies in, I don't think we can get away just embedding chart.js.min without any attempt for proper package dependency management.

@falox
Copy link
Author

falox commented Feb 9, 2026

@falox what's the state of you reviewing the code after the vibe-coding part? Just to avoid the maintainers being the first ones to actually be confronted with the code?

Please wait until I remove the WIP: prefix. There are many open points. The current goal is to have something to experiment with and test the limits of the approach.

@iNecas iNecas marked this pull request as draft February 9, 2026 10:04
@iNecas
Copy link
Contributor

iNecas commented Feb 9, 2026

Ok, converted to draft.

@rexagod
Copy link
Member

rexagod commented Feb 11, 2026

Please ignore the points below if these will become irrelevant and addressed after this PR is made ready for review.


My $0.02: I'd expect the MCP clients to send minimal data, for instance, populating a structured schema, while the JS embedding is done by the server (LCS) itself. This not only reduces the payload size propagated, but also shields MCP teams from avoidable CSS and JS maintenance burden (even if the chart.js library used here is pinned to a version, minified and vendored).

Additionally, how does this relate to https://issues.redhat.com/browse/GIE-376? Is the approach seen here the proposed solution for that ticket?

@openshift-merge-robot
Copy link
Collaborator

PR needs rebase.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants