AI Agents

  • Fixed

    • A restart request on an MCPServer whose endpoint is down is processed by one attempt (#1166). spec.restartRequestedAt is mirrored into status.lastRestartedAt after the reconciler’s first start or restart attempt, whether it succeeded or not; the failure is reported once in the reconcile result, and the following attempts are the service’s own, on the reconnect backoff the failed start has already scheduled (status.nextRetryAfter, capped at 2 minutes since #1163). Before, the value was recorded only when the restart succeeded: the failed start moved the service starting -> failed, the state-change bridge queued a reconcile for each transition, the request was still pending, and the reconciler restarted the service again – 131 Restarting MCPServer service <name> (restartRequestedAt=...) / Connection failure #N pairs in 53 s on a management cluster whose tunnel was scaled to 0, one MCPServerFailed event each, throttled only by the 500 ms debounce, while status.consecutiveFailures climbed to 131 and status.nextRetryAfter said “in 2 minutes” the whole time; the loop stopped only when the field was removed from the CR. core_service_start and core_service_restart write exactly this field in Kubernetes mode, so any restart of an unreachable server from the CLI or the UI triggered it. The same rule applies to a spec.suspended: false resume whose start fails (its marker used to be kept, so every state change resumed again) and to a pending request whose create or update in the same pass already attempted a start: one attempt, then the service’s schedule. A request on a server with no registered service (autoStart=false, never started) whose start fails before anything is registered stays pending and is retried on the reconcile queue’s own backoff, since no state change can re-trigger it. The reconciler logs Restart of MCPServer <name> requested at <time> failed: <error>; the request is processed, further attempts follow the service's reconnect backoff (next attempt at <time>) once per processed request. Scenario: mcpserver-restart-request-endpoint-down.
  • Changed

    Full Changelog: https://github.com/giantswarm/klaus/compare/v0.0.329...v0.0.330

  • Fixed

    • The reconnect backoff of a remote MCPServer is capped at 2 minutes (#1163). The wait between connection attempts still doubles from 30 s on every consecutive failure, but it stops at MUSTER_MCPSERVER_MAX_BACKOFF (default 2m; a Go duration) instead of 30 minutes, so a server whose upstream has healed is retried within the cap plus the 30 s retry tick, however many attempts the outage cost. Before, four 504s from a tunnel in front of a healthy server scheduled the fifth attempt 4.5 minutes out and the sixth 8.5, and users saw the server down for that long after the outage had ended. MCPServerFailed events now name the HTTP status the endpoint answered with and the scheduled retry – server unreachable after 4 consecutive failures (endpoint answered HTTP 504, next retry in 2m0s at 2026-09-05T15:57:34Z): ..., or no HTTP response for a refused connection, a DNS failure or a timeout – and every failure below the threshold emits connection failure N of 3 before unreachable (...). The aggregator no longer emits a second, bare MCPServerFailed on the same transition, so there is one event per failed attempt and it is the one with the schedule. The MCPServer CR status now carries the schedule too: status.consecutiveFailures, status.lastAttempt and status.nextRetryAfter existed in the CRD but were never written; they are now mirrored from the service on every status sync, together with the new status.lastFailureHTTPStatus, and cleared once the server connects or answers a 401 (which also clears the in-memory schedule, so Auth Required no longer sits next to a stale retry forecast). core_service_status exposes the same fields under metadata, core_mcpserver_get returns lastFailureHTTPStatus, and a Failed server whose error is an HTTP 5xx gets the status message Upstream error (HTTP 5xx) - the server or a gateway in front of it is failing; muster retries with backoff. Scenario: mcpserver-remote-backoff-cap; muster test gained test_set_mock_server_outage (a mock server answers its next N requests with a fixed HTTP status) and runs instances with MUSTER_MCPSERVER_MAX_BACKOFF=3s.
  • Fixed

    • The family tool surface follows what members currently offer (#1162). A spec.family member that kept its registration but re-listed fewer tools – mcp-capi 0.3.x hides its mutating tools when it runs read-only – kept advertising the tools it had dropped: tools/list returned 40 x_capi_* names while every member reported 24, describe_tool named members that no longer offered the tool, and calling one failed with tool not found. The family routing index only grew (providers were unioned on every listing and removed only on deregistration) and the per-session listing served the capability store regardless of the member’s state. The index is now re-synced from every listing – a contributing member’s entries are replaced by what it contributed, tools it no longer offers lose it as a provider, a tool no member offers disappears, and members that did not take part in a per-session listing keep their entries – and a member whose service is down (failed, unreachable, stopped, disconnected) contributes nothing to any session’s tools, resources or prompts, even though its pending-auth registry entry and cached capabilities survive. A failed SSO reconnection drops the session’s cached capabilities for that server, and the person’s sessions receive list_changed notifications when a capability list shrinks, as they already did when it grew. Scenario: mcpserver-family-member-relists-subset; muster test gained test_stop_mock_server / test_start_mock_server.
  • Fixed

    • Subject-scoped grants refresh themselves. For an MCPServer whose authorization server is pinned with grantScope: subject (GitHub’s hosted MCP server and in-house servers verifying GitHub tokens), muster now redeems the grant’s refresh token itself: every lookup that serves a tool call, a core_auth_login or a broker release finds the person’s grant, refreshes it when its access token has expired or will within five minutes (capped at half the token’s lifetime) using the pinned tokenEndpoint and the pre-registered client, and stores the rotated tokens under the person and under every session copy. Refreshes are single-flighted per person and issuer and re-read the store first, so a rotating refresh token (GitHub) is redeemed exactly once however many sessions hit the expiry together. A refresh the authorization server rejects (invalid_grant, GitHub’s bad_refresh_token) removes the grant so the next use asks for a sign-in; a transient failure keeps it. Before, the store hid the token 30 s before expiry and nothing redeemed the refresh token: eight hours after connecting GitHub every session lost the server with authentication required: server returned 401 Unauthorized and had to connect again. The in-memory store keeps expired tokens that carry a refresh token for 30 days instead of sweeping them. Logged as subject_grant_refreshed / subject_grant_refresh_rejected / subject_grant_refresh_failed.
    • Token broker grant targets (tokenExchangeBroker.targets.<audience>.grantIssuer). A broker target can now release a person’s subject-scoped grant instead of exchanging at a downstream Dex: an allow-listed confidential broker client exchanges the person’s ID token (audience=<audience>) and receives the access token of the person’s own grant from that issuer – looked up by the subject token’s raw sub, the identity the grant was filed under, not by a trustedIssuers subjectClaim mapping – refreshed first when it is due, with issued_token_type access_token and expires_in the token’s remaining lifetime, never the refresh token. A person without a grant gets invalid_target (no_grant in muster’s log). clientAudiences gates it and every release is audited (subject_grant_released / subject_grant_release_refused). A target is either a grant target or a Dex exchange target: grantIssuer and dexTokenEndpoint are mutually exclusive and a grant target rejects the exchange settings at startup. Lets a developer portal implement its standard GitHub auth API on the grant muster already holds, with GitHub’s own client libraries. See “Releasing a person’s grant to a trusted relying party” in docs/how-to/connecting-non-rfc9728-mcp-servers.md.
    • tokenExchangeBroker.brokerClients.<id>.clientSecretFile: seed a confidential broker client from a file holding its secret, for deployments without a Kubernetes Secret store (muster serve against a local config directory, the test harness). Takes precedence over clientCredentialsSecretRef.
    • muster test: muster_broker.clients (confidential broker clients with their audiences) and muster_broker.grant_targets (audience → mock authorization server) configure brokered exchange for a scenario; test_broker_token_exchange accepts client_id/client_secret for HTTP Basic client authentication and reports an opaque released token with expires_in, issued_token_type and has_refresh_token; the mock authorization server marks access tokens it issues on a refresh with a refreshed- prefix and OAuth-protected mock backends echo an opaque bearer through echo_token tools. Scenarios: oauth-subject-grant-refresh, oauth-subject-grant-refresh-rotation, oauth-broker-grant-release.
  • Changed

    • Never start real containers from the cmd and instance-tool tests in #340 by @teemow

    Full Changelog: https://github.com/giantswarm/klausctl/compare/v0.3.1...v0.3.2

  • Fixed

    • Return the agent’s final message from blocking klaus_prompt and surface pr_urls in klausctl result in #339 by @teemow

    Full Changelog: https://github.com/giantswarm/klausctl/compare/v0.3.0...v0.3.1

  • Fixed

    • (claude) Record each turn’s result for chat-completion runs and attribute pr_urls to PRs created or pushed to in #464 by @teemow

    Full Changelog: https://github.com/giantswarm/klaus/compare/v0.0.328...v0.0.329

  • Added

    • (messages) Tail, limit and maxChars for klaus_messages / klausctl messages in #338 by @teemow

    Full Changelog: https://github.com/giantswarm/klausctl/compare/v0.2.60...v0.3.0

  • Fixed

    Full Changelog: https://github.com/giantswarm/muster/compare/v5.9.9...v5.9.10