feat(mcp): reconcile sources and refresh session tools - #1673
Conversation
jhrozek
left a comment
There was a problem hiding this comment.
The reconciler core here is genuinely solid work - immutable candidates, atomic swap, LKG, all-or-nothing publication, and a test suite that exercises the real mcp.Manager and the real session factory rather than stubs. Verified: the shared-manager lifecycle bullet holds (nothing folds the runtime Close into per-session close), and TestPerSessionCatalogMatchesSharedCatalog + Scenario3_NameAuthorityAvailabilityMatrix both prove the catalog-equality and mid-session-tool-change guarantees you asked about.
Four things need fixing before merge (flagged inline below, all independently traced against this commit, not just taken on the reports' word):
A. An unpinned ctx on the operation-revision path breaks the shared-engine fast path after the first MCP republication, and reaches the MCP-authorization continuation specifically.
B. CreateTeam/RunTeam now have two lifecycle implementations gated on OperationPin, and production only ever exercises the less-tested one.
C. A nil-pointer panic is reachable in CancelTeammate - ts.phase flips to teamRunning before ts.sup is actually built on the pinned path, and CancelTeammate doesn't take any lock that would serialize against that window.
D. RefreshMcpSources triggers shared reconciliation one line before it acquires runEntryMu, ahead of the acceptance plan's own documented linearization point.
A few more items worth a look before/after this lands, not blocking:
internal/adapter/mcp/tool.go'snewRemoteToolcarries tool descriptions/schemas into the model's system prompt verbatim, unfenced - this file isn't touched by this diff, but automatic ToolHive publication (this PR's whole point) newly makes that a live prompt-injection surface where it wasn't before (no more operator restart/consent gate). Worth agovernance.NeutraliseFramingpass plus a length clamp in a follow-up.mcp_refresh.go's error path interpolates raw backing-store failure text intoErrInternal, serialized over both gRPC and HTTP - could leak backend paths/addresses.- Malformed remote MCP names (control chars, invalid UTF-8) can reach durable root authority without the validation explicit refresh grants enforce (
toolhive.go's validation only checks__; remote tool creation only checks non-empty). - Real data race:
teamState.phaseis read under one lock inSendTeammateMessageand written under a different lock discipline inRunTeam's deferred cleanup. list_changednotifications can be silently and permanently lost in two separate windows (thepreparingGentocurrentgap, and a deferred-publication path that discardscandidateDirty) - worth fixing since notification delivery is this feature's actual purpose.- A remote MCP server flooding
list_changedcan drive an unbounded reconnect storm (no backoff between reconcile cycles).
Happy to open follow-up issues for the non-blocking items if useful.
316f99d to
0895417
Compare
ece4671 to
1f91831
Compare
|
Pushed the rebased stack and review repairs. Current implementation head: All four inline findings have been addressed:
Related feedback: fixed phase locking and private-error leakage; validated remote names; retained dirty notifications through handoff/failure/deferred publication; added a one-second reconnect cooldown. Additional review caught and fixed partial-start resource leaks, concurrent/callback teardown races, and build-time runtime-tag mismatches. Regression coverage includes actual HTTP/gRPC error projection and automatic runtime-drain publication. Explicitly not claimed fixed: stronger remote description/schema prompt trust. Existing byte/page/entry bounds do not neutralize instructions. Blanket schema sanitization would change valid tool contracts; this remains the review's nonblocking follow-up requiring an agreed metadata-provenance/projection policy. Snapshot rollback also remains fail-safe/best-effort when lease ownership is lost or deletion fails, with retention guidance documented. Targeted race tests are green. Earlier full repair gates passed; latest full local reruns encountered the embedded-scheduler TUI race and host contention, so I am relying on fresh pushed-head CI for final verification and monitoring it to completion. |
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: OpenAI <noreply@openai.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: OpenAI Codex <noreply@openai.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-authored-by: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@stacklok.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: OpenAI Codex <codex@openai.com>
Co-Authored-By: Mecatl <mecatl@users.noreply.github.com>
Co-Authored-By: Mecatl <mecatl@users.noreply.github.com>
Co-Authored-By: Mecatl <mecatl@users.noreply.github.com>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Review follow-up: what changed and whyI've taken over this PR. These 8 commits fix the review findings I posted earlier (review 5290674823). Each inline thread has a reply with the details and is resolved. Fixes
Tests that couldn't fail beforeEach one below now fails when the production behaviour it covers is removed. I checked each by breaking that line.
Decision: shared engineAfter the first republication, default-FS sessions move to their own per-session engines, and the shared engine is never rebuilt. The only run path to the shared engine checks revisions, so this is a cost issue, not a correctness bug. For this PR, ADR 0351 decision 6 now documents it as the one exception to "rebuilt before use" (854eee3). Rebuilding the shared engine once per revision is tracked in #1802. Verification
Not in this PRThese came up in review and are left out on purpose:
🤖 Generated with Claude Code |
The implementation keeps the Service-level shared engine at its build revision and promotes default-FS sessions to per-session engines after the first republication. Record that exception in decision 6 so the approved contract matches #1673; rebuilding the shared engine per revision is tracked in #1802. Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Main allocated ADR 0351 (Mecatl Studio) and 0352 (jev delegated model router) after the plan's last baseline sync, so the reconciliation ADR, its index entry, and the two ADR-named verification functions move to the next free number. Co-Authored-By: mecatl <noreply@mecatl.dev>
Brings in the plan branch's main sync (through 33a3747) and its ADR renumber. Conflict resolution: - AGENTS.md: take main's trimmed version (#1807 removed the per-session catalog bullet this branch had edited). - docs/design/IMPLEMENTATION-NOTES.md: accept main's deletion; the reconciliation mechanism is already covered by ADR 0353 and docs/architecture/extensibility.md. - Generated protobuf code: regenerated with task generate. Follow-ups for the renumber: ADR 0353 references in ADR 0027, extensibility.md and PRODUCTION-READINESS.md, and the two TestADR_* verification functions. Also drops a requestHasTool test helper that duplicated one main added in jev_router_acceptance_test.go. Co-Authored-By: mecatl <noreply@mecatl.dev>
Co-Authored-By: mecatl <noreply@mecatl.dev>
Main allocated ADR 0353 (session-scoped agent identity) and 0354 (returned auxiliary usage results) after the previous renumber, so the reconciliation ADR, its index entry, and the two ADR-named verification functions move to the next free number. Co-Authored-By: mecatl <noreply@mecatl.dev>
Brings in the plan branch's main sync (through 36a618a) and its ADR renumber. Conflict resolution: - docs/design/PRODUCTION-READINESS.md: accept main's deletion (#1812 retired the readiness tracker); the row this branch added goes with it. - Acceptance plan header: keep this branch's landed status, take the plan's new baseline line. Follow-ups for the renumber: ADR 0355 references in ADR 0027 and extensibility.md, and the two TestADR_* verification functions. Co-Authored-By: mecatl <noreply@mecatl.dev>
#1527 was squash-merged, so the plan commits this branch carried are not ancestors of main. Resolve the two conflicts in the acceptance docs by keeping main's text and this branch's landed status (plus the plan's completion proof). Co-Authored-By: mecatl <noreply@mecatl.dev>
Main now has its own ADRs 0350-0355, so the workspace enrollment authority decision record becomes ADR 0356. The plan, the ADR index, and ADR 0335's supersession note follow. #1673 added direct MCP refresh (ADR 0355), a separate add-only authority path through Session.GrantToolAuthority. Name it in the plan's authority boundary: it refuses broker-bound sessions and broker mode clears the global MCP inputs, so the two paths never act on the same session. Add AC1.5 pinning that refusal. In the ADR, link ADR 0355 and record why its union-and-availability model is rejected for broker keys. Relates to #1740. Co-Authored-By: mecatl <noreply@mecatl.dev>
Main now has its own ADRs 0350-0355, so the workspace enrollment authority decision record becomes ADR 0356. The plan, the ADR index, and ADR 0335's supersession note follow. #1673 added direct MCP refresh (ADR 0355), a separate add-only authority path through Session.GrantToolAuthority. Name it in the plan's authority boundary: it refuses broker-bound sessions and broker mode clears the global MCP inputs, so the two paths never act on the same session. Add AC1.5 pinning that refusal. In the ADR, link ADR 0355 and record why its union-and-availability model is rejected for broker keys. Relates to #1740. Co-Authored-By: mecatl <noreply@mecatl.dev>
Main now has its own ADRs 0350-0355, so the workspace enrollment authority decision record becomes ADR 0356. The plan, the ADR index, and ADR 0335's supersession note follow. #1673 added direct MCP refresh (ADR 0355), a separate add-only authority path through Session.GrantToolAuthority. Name it in the plan's authority boundary: it refuses broker-bound sessions and broker mode clears the global MCP inputs, so the two paths never act on the same session. Add AC1.5 pinning that refusal. In the ADR, link ADR 0355 and record why its union-and-availability model is rejected for broker keys. Relates to #1740. Co-Authored-By: mecatl <noreply@mecatl.dev>
Stage
Implementation — stacked on #1527
Contract and baseline
6ad42a98127e6a5bf18246dde8f83db909fb4b1a.ca993a6ea1b648e8e18a102b6810da5dea8c494f.plan/mcp-source-reconciliation. Humans merge the plan first, then retarget/review this implementation against main.Supervisor.Close()/ErrSupervisorClosed. Proposed ADR: 0351.Summary
Session.GrantToolAuthority, direct gRPC/HTTP refresh controls, cached revision/status, SDK support, and unified mecatui/mcp-refreshrouting./tools-connectas a deprecated broker-only alias.Review repairs
Acceptance and verification
All 13 amended ACs have proof mappings. The plan's
landedstatus in this diff is a proposed transition, effective only when this implementation merges.Passed during repair verification:
task lintandtask testat earlier repair checkpoints.task api:check,task docs,task site:build, and offlinego run ./cmd/mecademoat repair checkpoints.git diff --check.Fresh CI on
4b2f27e1d4b05150e62d5456f044a39e8b6bad96is green: CI, Kubernetes e2e, performance, and advisory checks passed. This includes lint/analysis, race suites, macOS temporary-storage/CLI checks, SDK matrix, standalone modules, docs, API compatibility, build, and vulnerability checks. Conditional live-model tests were skipped. The first push's receiver-lint and macOS test-ordering failures were repaired and are superseded by these results.Independent reviews covered spec, standards, test adequacy, security, architecture, UX, DevEx and reuse. Confirmed lifecycle/runtime findings were repaired with regressions.
Explicit residuals
Tracking: #1511