Repository navigation
Conversation
There was a problem hiding this comment.
🟡 Changes recommended
The new skip/write logic introduces a couple of correctness risks (notably around initial DB visibility on discovery failure and snapshot determinism) that should be addressed before merging.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Pull request overview
This PR reduces unnecessary Postgres churn from MCP tool discovery by caching the last persisted tool list “snapshot” in the controller reconciler and skipping StoreToolServer / RefreshToolsForServer when the discovered tool set and description are unchanged across polling cycles.
Changes:
- Added an in-memory per-(server, groupKind) tool snapshot cache to avoid repeated no-op writes during periodic tool discovery.
- Evicted cached snapshots when ToolServer resources are deleted.
- Added unit tests covering snapshot equality, change detection, and cache eviction.
File summaries
| File | Description |
|---|---|
| go/core/internal/controller/reconciler/reconciler.go | Adds snapshot caching + eviction to skip unchanged Postgres writes during MCP tool polling. |
| go/core/internal/controller/reconciler/reconciler_test.go | Adds unit tests for snapshot determinism, change detection, and cache eviction behavior. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 2
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
4f194a9 to
c1602cf
Compare
There was a problem hiding this comment.
🟡 Changes recommended
Snapshot hashing currently ignores json.Marshal errors (risking incorrect “unchanged” detection) and there are unresolved cache/DB-healing edge cases that should be addressed or explicitly accepted.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
go/core/internal/controller/reconciler/reconciler.go:1181
- The early-return cache is purely in-memory. If the ToolServer/tools rows are deleted or the DB is reset while this controller process keeps running, subsequent reconciles will keep skipping the
StoreToolServer/RefreshToolsForServerwrites until the tool list changes (or the controller restarts), leaving DB-backed APIs empty/stale. Consider adding a TTL-based forced refresh (e.g., re-write at least once per N intervals) or validating row existence before skipping writes.
go/core/internal/controller/reconciler/reconciler.go:1211 json.Marshalerrors are currently ignored when computing the snapshot. If marshaling ever fails (e.g., a future field type becomes non-marshalable),bbecomes nil and the hash degenerates to a constant, which can incorrectly treat different tool lists as "unchanged" and skip required DB updates. Handle the error with a deterministic fallback that still depends on the tool data.
- Files reviewed: 2/2 changed files
- Comments generated: 1
- Review effort level: Lite
EItanya
left a comment
There was a problem hiding this comment.
🤖 AI-generated review.
- Invalidate the cached snapshot before attempting changed writes — reconciler.go:1184–1189.
StoreToolServercommits separately from the tool refresh. If snapshot A is cached, writing B updates the server row, and the refresh fails, A remains cached. Rolling back to A then skips persistence indefinitely, leaving B’s metadata in the database. An uncertain commit outcome can also leave stale tools. Evict the snapshot before attempting the writes; remember it only after both succeed.
Reproduced through real MCP discovery with injected database failures. Existing reconciler tests pass; both regression cases and the full package pass with the one-line fix applied through a scratch overlay. No live PostgreSQL testing was performed.
Signed-off-by: Jet Chiang <[email protected]>
Keep failed MCP servers visible in the toolserver API without repeating the write, and make duplicate tool names hash deterministically. Signed-off-by: Jet Chiang <[email protected]>
Signed-off-by: Jet Chiang <[email protected]>
Signed-off-by: Jet Chiang <[email protected]>
f423a4d to
1f470b9
Compare
The discovery-disabled path stored the server and cleared its tools on every periodic refresh, a Postgres write every 60 s for a row that never changes. Reuse the tool-snapshot cache from kagent-dev#2796: the empty inventory is written when the snapshot changes and skipped otherwise. The snapshot is taken over an empty, non-nil tool list so it differs from the row ensureToolServerRow writes after a failed discovery. Signed-off-by: Timo Derstappen <[email protected]>
…y=disabled without listing its tools (backport to release/v0.10.x) (#2753) Backport of #2752 to `release/v0.10.x` (the 0.10 reconciler layout differs from `main`, so this is a separate patch of the same change). ## Problem A `RemoteMCPServer` that authenticates every caller has no credential to offer the controller. Agents forward the caller's token at run time (`KAGENT_PROPAGATE_TOKEN`), but the controller's own discovery request (`initialize` + `tools/list`) carries no bearer: the server answers 401 and the CR stays `Accepted=False (ReconcileFailed: … Unauthorized)` for good, although every agent that references it works. `spec.headersFrom` is not a way out: its values are resolved into each agent's config and applied last by the runtime (`headerRoundTripper.RoundTrip`, `go/adk/pkg/mcp/registry.go`), so a controller credential there would replace the propagated caller token in every agent. ## Change - `kagent.dev/discovery=disabled` — the label `predicates.DiscoveryDisabledPredicate` already reads to keep a Service or `MCPServer` out of auto-discovery — now also opts a `RemoteMCPServer` out of tool discovery: `ReconcileKagentRemoteMCPServer` skips the listing, stores the server in the database with no tools, and reports `Accepted=True` with reason `DiscoveryDisabled` (message names the label), clearing `status.discoveredTools`. Any other value, or no label, keeps discovery as it is. - The TLS Secret hash is still computed and published with the `DiscoveryDisabled` condition (agents fold it into their rollout hash), and a broken `spec.tls` Secret reference still fails the server. - The empty inventory is written once. The periodic refresh (60 s) re-enters the opt-out path, and the tool-snapshot cache from #2796 skips the Postgres write while the server and its (empty) tool list are unchanged; a spec change is a new snapshot and is written again. - The label key and value move to `consts` (`DiscoveryLabel`, `DiscoveryDisabled`); the predicate uses them. - The `RemoteMCPServer` controller reacts to label changes as well as generation changes (`predicate.Or(GenerationChangedPredicate, LabelChangedPredicate)`), so the opt-out and its removal apply at once instead of on the next periodic refresh. - `reconcileRemoteMCPServerStatus` is split into the existing error→condition mapping and `setRemoteMCPServerStatus`, which takes the condition explicitly; the unchanged-status short-circuit is kept. The `MCPServer` part of #2752 is not needed here: `release/v0.10.x` still filters labeled `MCPServer`s through `predicates.DiscoveryDisabledPredicate`, which `main` lost in #2565. ## Testing - `go test ./core/internal/controller/reconciler/ -run DiscoveryDisabled`: `TestReconcileKagentRemoteMCPServer_DiscoveryDisabled` reconciles a labeled server with an unroutable URL through a recording `database.Client` that panics on anything but the two catalog writes — asserts the condition (`True` / `DiscoveryDisabled`), the cleared stale inventory, the stored server with no tools, and that a second reconcile of the unchanged server writes nothing while a description change is written again; `TestReconcileKagentRemoteMCPServer_DiscoveryDisabledKeepsSecretHash` covers the published hash and the broken-reference failure; `TestRemoteMCPServerDiscoveryDisabled` covers the label matching. - `go build ./core/...`, `go vet ./core/internal/controller/...`. Context: giantswarm/agent-platform#299. --------- Signed-off-by: Timo Derstappen <[email protected]>
Summary
StoreToolServer/RefreshToolsForServerwhen nothing changed.Test plan
successfully registered remote MCP serverlogs, and thattool/toolserver.updated_atandn_tup_updstay at the first write