Skip to content

feat(mcp): [TKC-6706] add Insights board tools - #8395

Closed
vsukhin wants to merge 13 commits into
mainfrom
feat/mcp-insights-boards
Closed

vsukhin wants to merge 13 commits into
mainfrom
feat/mcp-insights-boards

Conversation

@vsukhin

@vsukhin vsukhin commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator

Let MCP clients list, view, create, update and delete Insights boards, add, change and remove the reports on them, and render the numbers a board shows.

Report params have no backend schema: the Control Plane stores them opaquely and only the dashboard interprets them. pkg/mcp/boards ports the dashboard's rules - param validation and defaults, the translation of a report into the org-scoped /insights/* query that renders it, and the board layout - so what the tools write renders and edits in the dashboard. The Control Plane's HandlerClient uses the same package, and testdata/translation_cases.json pins the translation.

Boards are organization-scoped and the Control Plane refuses API tokens on every board endpoint. The APIClient refuses a tkcapi_ token before sending anything, and the tools return an error that points to testkube login. Every write reads the board first and resends its description, because the Control Plane clears the description of any update that omits it; removing a report sends the recomputed layout, because the Control Plane leaves the report's cell behind.

Pull request description

Checklist (choose whats happened)

  • breaking change! (describe)
  • tested locally
  • tested on cluster
  • added new dependencies
  • updated the docs
  • added a test

Breaking changes

Changes

Fixes

Let MCP clients list, view, create, update and delete Insights boards,
add, change and remove the reports on them, and render the numbers a
board shows.

Report params have no backend schema: the Control Plane stores them
opaquely and only the dashboard interprets them. pkg/mcp/boards ports
the dashboard's rules - param validation and defaults, the translation
of a report into the org-scoped /insights/* query that renders it, and
the board layout - so what the tools write renders and edits in the
dashboard. The Control Plane's HandlerClient uses the same package, and
testdata/translation_cases.json pins the translation.

Boards are organization-scoped and the Control Plane refuses API tokens
on every board endpoint. The APIClient refuses a tkcapi_ token before
sending anything, and the tools return an error that points to
`testkube login`. Every write reads the board first and resends its
description, because the Control Plane clears the description of any
update that omits it; removing a report sends the recomputed layout,
because the Control Plane leaves the report's cell behind.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@testkubebot

testkubebot Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

✅ Testkube GitHub Integration

Review based on commit ac49267.

All tests and quality gates passed.


Phase Status
Test Workflow Execution ✅ Passed
Quality Gate ✅ Passed

6 workflows executed

✅ lint-go passed in 4m21s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:51:24 UTC

✅ lint-proto passed in 19s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:47:21 UTC

✅ integration-tests passed in 7m25s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:54:28 UTC

✅ unit-tests passed in 4m24s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:51:26 UTC

✅ verify-crds passed in 1m28s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:48:30 UTC

✅ verify-protobuf passed in 13s
🚀 28. Sep. 2026 - 15:47:02 UTC / 🏁 28. Sep. 2026 - 15:47:15 UTC


Manage this Integration

@vsukhin

vsukhin commented Sep 23, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

@greptile-apps

greptile-apps Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

RetriggerConfidence Score: 5/5

[Medium risk] Adds Insights board management tools to the MCP server.

The PR appears safe to merge based on the reviewed changes.

Summary

The PR adds nine Insights board tools, shared report/query rules, and conditional board updates. The changes since the previous review clarify API-token handling, preserve report-description clearing, pin retries to a board ID, and make rendering and debug collection follow the board layout. No new actionable issue was established.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  MCP[Board tools] --> Client[Board client]
  Client --> CP[Control Plane]
  MCP --> Rules[Shared board rules]
  Rules --> Queries[Insight queries]
  CP -->|board and version| MCP
  MCP -->|conditional update| CP
Loading

Reviews (5) · Last reviewed commit: "docs(mcp): say that only older Control P..."

Comment thread pkg/mcp/boards/query.go Outdated
Comment thread pkg/mcp/tools/boards.go Outdated
…nditional

render_board anchored relative report ranges to midnight UTC, while the
dashboard anchors them to the viewer's local midnight, so a user outside
UTC got a shifted interval and different numbers. It now takes an IANA
timeZone and computes the dashboard's boundary there, subtracting the
duration in fixed minutes as the dashboard does across a daylight-saving
change. The time zone database is embedded for images without one.

Every board write resent a description, and report removal a layout,
taken from an earlier read, so a concurrent edit between the read and
the write was silently overwritten. Writes now go through writeBoard:
each sends expectedUpdatedAt, the updatedAt it read; the Control Plane
refuses a stale write with 409, both clients turn that into
ErrBoardChanged, and the write is rebuilt from a fresh read, up to
three times. A Control Plane that predates the field ignores it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vsukhin

vsukhin commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

A conditional board write compared updatedAt, but updatedAt is not a
safe concurrency token: two writes can land on the same timestamp, so a
write could leave behind the very token a stale request still holds,
and that request would then pass the check and overwrite the newer
edit.

The Control Plane now keeps a version that every write to a board
increments. Board writes send expectedVersion, the version they read,
instead of expectedUpdatedAt, and send none when the board came back
without a version, so against an older Control Plane they stay
unconditional as before.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vsukhin

vsukhin commented Sep 24, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved compatibility, concurrency, serialization, filtering, rendering, and documentation issues remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 High severity · 2 Medium severity · 1 Low severity

Open (5)
What changed in this PR

Adds MCP support for managing and rendering organization-scoped Insights boards.

Changes:

  • Adds board CRUD, report management, layout, and rendering tools.
  • Adds validation, query translation, timezone handling, formatting, and API support.
  • Updates tests and documentation.
File Summary
pkg/​mcp/​tools/​descriptions.go Board tool descriptions
pkg/​mcp/​tools/​boards.go Board handlers and concurrency logic
pkg/​mcp/​tools/​boards_test.go Board tool tests
pkg/​mcp/​server.go Registers board tools
pkg/​mcp/​README.md Board documentation
pkg/​mcp/​formatters/​insights.go Shared series formatting
pkg/​mcp/​formatters/​boards.go Board and report formatting
pkg/​mcp/​formatters/​boards_test.go Formatter tests
pkg/​mcp/​client.go MCP client interfaces
pkg/​mcp/​boards/​timezone.go Timezone parsing
pkg/​mcp/​boards/​testdata/​translation_cases.json Query translation fixtures
pkg/​mcp/​boards/​reports.go Report validation and normalization
pkg/​mcp/​boards/​query.go Report query translation
pkg/​mcp/​boards/​layout.go Board layout handling
pkg/​mcp/​boards/​boards_test.go Board package tests
pkg/​mcp/​boards/​board.go Board models and report ordering
pkg/​mcp/​api.go Board API methods
pkg/​mcp/​api_boards_test.go API client tests
ARCHITECTURE.md Architecture documentation
AGENTS.md Repository guidance

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pkg/mcp/client.go
Comment thread pkg/mcp/tools/boards.go
Comment thread pkg/mcp/boards/reports.go Outdated
Comment thread pkg/mcp/tools/boards.go
Comment thread pkg/mcp/README.md Outdated
vsukhin and others added 6 commits September 24, 2026 17:15
…iewer time zone behavior'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
The docs said every board write is conditional on the version it read,
but delete_board is not, and deliberately: it resends nothing it read,
deletes by the ID it resolved, and the Control Plane checks delete
rights against the board as it is at delete time. Say that the
guarantee covers updates, and that a delete removes the board whatever
changed since the read, as deleting in the dashboard does.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ar existing report descriptions'

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
After a write lost the race, writeBoard read the board again by what
the caller passed. When that was a slug and the concurrent change was
to the slug, the retry failed to find the board, or found another board
that had taken the old slug over and rebuilt the write against it. The
retry now reads the board by the ID the first read resolved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1a59d74 dropped omitempty from ReportDraft.Description, so a report
create or update now always carries its description and an empty one
clears the report's. The request-shape test still expected the field
to be absent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
render_board runs its report queries in parallel on one context, and
both clients record each call in that context's DebugInfo, whose Data
map is not safe for concurrent use. With debug on, rendering a board
with several reports could crash with concurrent map writes, and the
recorded URL and status were whichever query finished last.

Each report query now gets its own DebugInfo when debugging is on, and
they are merged into the call's under "reports", keyed by report ID,
once all are done. DebugInfo moves to the mcpcontext leaf package so
the tools can use it; package mcp keeps an alias and its functions, so
existing callers, the Control Plane's included, are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Unresolved issues affect filtering, rendering, pagination, output bounds, and slug uniqueness.

Review effort: Lite
Findings: None

Resolved since last review (5)
Previously missed (2)

In code that hasn't changed since last review

Medium severity Exclude unplaced reports from default render

pkg/​mcp/​tools/​boards.go:696

OrderedReports appends unplaced reports after the placed ones, so this loop currently queries and returns data for reports that the dashboard does not show. That contradicts render_board's purpose of returning the board's displayed numbers; keep unplaced in the response but exclude those reports from the default render (while still allowing an explicitly requested reportId to render).

Low severity Update stale conflict token comment

pkg/​mcp/​tools/​boards.go:39

This comment still describes the conflict token as updatedAt, but the request now carries ExpectedVersion and the retry logic is explicitly version-based. Please update the comment so it does not document a field that is no longer used.

render_board rendered every report on the board, including those the
layout leaves out, which the dashboard does not show. The default
render now covers the placed reports only; the left-out ones are still
named under "unplaced", and one asked for with reportId is rendered.

Also correct the ErrBoardChanged comment, which still described the
conflict token as updatedAt.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

Unresolved authentication, pagination, filter validation, debug aggregation, and slug uniqueness issues remain.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 1 High severity

Open (1)

Comment thread pkg/mcp/api.go
…thods

The insight endpoints accept API tokens, so nothing was exposed, but
QueryBoardInsights is a board method and was the only one on the
APIClient that did not refuse a tkcapi_ token before sending. The
Control Plane's client already refuses it; now both do.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot review overview

🔵 Needs a closer look

Unresolved moderate findings affect query translation, validation, and concurrent slug creation.

Review effort: Lite
Findings: None

Resolved since last review (1)
Previously missed (1)

In code that hasn't changed since last review

Medium severity Make slug uniqueness checks atomic with board creation

pkg/​mcp/​tools/​boards.go:285

This availability check is not atomic with the subsequent create. Since the Control Plane does not validate a supplied slug on create, two concurrent calls can both observe available=true and create boards with the same slug, making slug-based board operations ambiguous. Slug uniqueness needs an atomic server-side check, or the create path must handle a race by retrying with a generated/different slug.

…ption

The Control Plane now keeps a board's description when an update omits
it (testkube-cloud-api 686e25242). The tools still resend it, since
older Control Planes clear it; say that instead of describing the
clearing as current behavior.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vsukhin

vsukhin commented Sep 25, 2026

Copy link
Copy Markdown
Collaborator Author

@greptile

@vsukhin
vsukhin marked this pull request as ready for review September 25, 2026 16:23
@vsukhin
vsukhin requested a review from a team as a code owner September 25, 2026 16:23
@vsukhin
vsukhin marked this pull request as draft September 28, 2026 14:49
Make this branch identical to origin/feat/mcp-boards/6-docs (c526ef6), the top of the
stacked branches that replace it, so both carry the same code.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@vsukhin vsukhin closed this Sep 28, 2026
@vsukhin
vsukhin deleted the feat/mcp-insights-boards branch September 28, 2026 15:54
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.

2 participants