19 KiB
spec, decision, created, status
| spec | decision | created | status |
|---|---|---|---|
| docs/specs/platform/requirements/mcp-session-observability.md | docs/decisions/2026-07-30-session-owned-mcp-observability.md | 2026-07-30 | draft |
Implementation Plan: Session MCP Attachment Observability
Overview
Replace the toolbar's profile-derived MCP list with a versioned, session-owned attachment report. Agentctl will emit honest configuration and local-server observations through its existing agent event stream; lifecycle and orchestrator will attach immutable Kandev ownership, persist a bounded history, broadcast live updates, and hydrate reloads. The frontend will disclose per-server state from that report in a neutral desktop popover or mobile drawer.
Release diagnostics remain structured and sanitized. Raw ACP frames stay
development-only, while endpoint tests and bounded agent output are explicit
user actions. acpdbg receives a sentinel MCP probe for integration work.
Confirmed root causes and protocol limits
useSessionMcpreads profile configuration and defaults to["kandev"], including after a profile-config fetch failure.McpIndicatortherefore labels intended configuration as active runtime state.- Structured ACP sessions inject Kandev's HTTP and SSE variants, then filter
by advertised or assumed transport support before sending
session/neworsession/load. The filter currently returns only the surviving servers, so its omission reasons are lost. - Passthrough materialization is a separate path. If a passthrough agent has no
MCPStrategy,applyPassthroughMCPsilently returns without exposing any servers even when discovery says the agent supports MCP. - ACP advertises MCP transport capabilities and accepts
mcpServers, but does not return a portable connected-server list. A successful ACP session request proves delivery acceptance, not connection. - Agentctl hosts Kandev's task MCP endpoint, and the existing
mcp-goversion exposes hooks for session registration, initialize,tools/list, tool calls, errors, and unregister. Those hooks can prove local endpoint behavior without raw frame logging. - Third-party profile MCP servers connect directly to the agent, so Kandev
cannot automatically observe their initialize or
tools/list. - Agent stderr is already retained in a bounded in-memory buffer and reachable
through the internal
agent.stderrstream request. It is not currently exposed as an authorized, on-demand user diagnostic. acpdbgsends an emptymcpServersarray and its runner keeps the original emptyRunConfig.Workdirafter allocating a temporary child directory. Consequentlysession/newcan receive an emptycwd, and the tool cannot test whether a real agent attaches to an injected MCP server.
Status contract
Attachment history
Add a versioned contract shared across the agentctl stream, lifecycle events, persistence, boot hydration, and frontend state:
MCPAttachmentHistory- schema version;
- current
attachment_attempt_id; - current attempt plus at most two previous attempts.
MCPAttachmentAttempt- backend-owned
task_id,session_id,execution_id, andattachment_attempt_id; agent_id,agent_profile_id, and optionalacp_session_id;- start, update, supersession, and session-acceptance times;
- bounded evidence timeline;
- report-level sanitized agent session error when it cannot be mapped to one server.
- backend-owned
MCPServerAttachment- name, source (
kandevorprofile), and transport; - sanitized target description;
- strongest display status;
- stable filter/failure reason code and bounded summary;
- latest connection ID, tool count, and evidence timestamps where observable;
- latest endpoint-test result, stored separately from attachment evidence.
- name, source (
Create a new attachment attempt before every ACP session/new,
session/load, or session/reset delivery and before every passthrough process
start. Publishing that attempt immediately supersedes the old current attempt,
so a failed or slow reset cannot retain Active status.
The display reducer follows the spec:
tools_list_observed→ Active;initialize_observed→ Connected;- delivered or accepted without observable connection → Delivered / connection unverified;
- explicit server-specific error → Failed;
- policy/capability/strategy omission → Filtered or Unavailable.
Tool use enriches Active state and does not define another toolbar color. Disconnect remains visible in detail; a later connection ID can restore the current connection evidence.
Sanitization and bounds
- Keep at most three attempts and 64 evidence events per attempt.
- Store MCP names, stable reason codes, timestamps, counts, and ownership IDs.
- Reduce network targets with URL parsing to
scheme://host[:port], dropping user info, path, query, and fragment. - Reduce stdio targets to
filepath.Base(command). - Pass error summaries through the existing routing/error sanitizer, cap them at 256 bytes, and never fall back to raw structured objects.
- Never persist headers, environment values, command arguments, prompts, content, tool arguments/results, raw ACP frames, or agent stderr.
- On-demand stderr is capped at the smaller of 50 lines or 16 KiB, remains ephemeral, and is excluded from Copy sanitized diagnostics.
Backend
Agentctl evidence production
- Add typed MCP evidence fields and constants under
apps/backend/internal/agentctl/types/streams/. - Give
process.Managera non-blocking typed update publisher so API delivery code and MCP hooks use the same ordered/agent/streamchannel as adapter events. Do not create a second event stream. - Change ACP transport filtering to return both the selected server list and a decision per input server. Record agent-advertised versus assumed transport support and stable reason codes without storing secrets.
- In
handleWSNewSession,handleWSLoadSession, and reset handling:- start and publish a new attempt before delivery;
- publish configured/filter/delivered evidence;
- publish session acceptance with the returned ACP session ID;
- publish a sanitized report-level or server-specific explicit error when available.
- Install
mcp-gohooks in the in-session Kandev MCP server. Attribute initialize,tools/list, call, error, register, and unregister events to the instance's task/session/execution/attempt ownership and the hook session's opaque connection ID. - Ensure the server factory passes
InstanceConfig.InstanceID, the status publisher, and the active attempt source into the MCP server. Tool handlers continue injecting task/session identity from server context. - Update passthrough materialization to publish resolved, filtered, materialized/delivered, start-accepted, and explicit failure evidence. A nil strategy produces an honest unavailable reason instead of a silent no-op.
Lifecycle, persistence, and live delivery
- Normalize the agentctl MCP event in lifecycle without treating it as prompt activity or chat content. Add the lifecycle payload and domain event subject.
- Stamp every accepted event with the live
AgentExecutionownership. Reject or ignore evidence whose execution or attachment attempt is no longer current. - Add
SessionMetaKeyMCPAttachmentHistoryand typed JSON rehydration helpers. - In the orchestrator handler, reduce each event into the bounded history and write only that metadata key atomically. Persistence failures are logged and do not alter session state.
- Publish
session.mcp_status_updatedon the session-scoped event bus subject and register it with the WebSocket session broadcaster. - Add task-detail boot state
mcpAttachments.bySessionId, using the persisted history so reload does not wait for a live event. - Add a frontend session-runtime slice, hydration merge, WebSocket handler, and tests. Removing a session clears its attachment report.
Release diagnostic operations
- Add session-scoped WebSocket requests:
session.mcp_test_endpointwithsession_idandserver_name;session.mcp_recent_agent_outputwithsession_id.
- Keep the existing raw-gateway rejection of
mcp.*actions. These newsession.*operations pass through ordinary dispatcher registration and the gateway's session authorization backstop, plus handler-level authorization. - Resolve the requested server name from the current persisted/effective attachment configuration. Reject unknown names; never accept a caller URL, command, headers, args, or environment.
- Execute the bounded initialize +
tools/listtest through the owning live agentctl instance so network and stdio checks run inside the session's executor. Close the temporary client/process on success, error, cancellation, and timeout. - Store only the sanitized test result in the attachment report, explicitly classified as endpoint reachability rather than agent attachment.
- Reuse
GetAgentStderrfor the output request, cap it to 50 lines/16 KiB in the backend response, do not persist it, and return a clear unavailable response when the execution is not live. - Reuse the existing reset-context recovery path in the UI. Do not add an automatic retry, process restart, or state transition based on missing evidence.
Frontend
Store-derived status
- Replace
useSessionMcp(agentProfileId)with a session-ID-based hook that readsmcpAttachments.bySessionId. It may show a neutral loading or unavailable state while no report exists, but must not synthesize Kandev as Active from profile configuration. - Keep configuration context in the status report itself. The component never fetches profile MCP JSON directly.
- Define a pure display reducer for row status, colors, checklist text, timestamp formatting, and copy-safe diagnostics. Unit-test unknown/newer reason codes with a conservative fallback.
Responsive status disclosure
- Replace
McpIndicatorwith a semantic button and sharedMcpStatusContent. - Desktop: use a controlled Popover-style disclosure. Pointer hover and
keyboard focus open it transiently; click pins it for actions; outside
interaction, Escape, or a second trigger click closes it. Keep focus order
and
aria-expandedcorrect. - Touch/coarse pointer: use
useTouchDrawerand the shared inset Drawer treatment. A.tap()on a 44px trigger opens the bottom drawer; no hover-only behavior remains. - Keep the plug icon neutral in every state. Put green/amber/red/gray only in the disclosed server rows.
- Show the current Kandev session, execution, and attempt in diagnostics, but keep opaque IDs secondary to server name and plain-language status.
- Provide the checklist and actions from the spec. Fetch recent output only after explicit activation and warn before rendering it. Copy diagnostics is built from the sanitized report and test result only.
- Link to the existing MCP settings surface. Show the existing Reset context flow only when its current eligibility rules allow it and retain its confirmation copy.
- Use the same component in structured and passthrough chat toolbars; status truth comes from the active session report.
Mobile design contract
- Desktop outcome: a neutral toolbar button opens a compact hover/focus popover; clicking pins a scroll-bounded list with per-server status and diagnostics.
- Mobile entry point: a 44px neutral plug button in the existing horizontally scrollable composer toolbar.
- Mobile outcome: a safe-area-aware inset bottom drawer containing the same server list, checklist, and actions. Rows wrap long names and summaries, the drawer owns its internal scroll, and the document has no horizontal overflow.
- Nearest shipped exemplar:
useTouchDrawerplus the Drawer/Popover split in GitHub task credential and PR status surfaces. - Shared logic: one store selector, display reducer, status content component, diagnostic request layer, and copy serializer across desktop and touch.
Developer probe
- Fix
acpdbg.NewRunnerto retain the resolved temporary working directory inr.cfg.Workdir, so ACPcwdmatches the spawned child. - Add an
mcp-probecommand that starts a temporary streamable-HTTP sentinel, injects it intosession/new, waits within the existing command timeout, and summarizes:- advertised MCP transports;
- server delivered;
- initialize observed;
tools/listobserved and count;- optional sentinel tool use when an explicit prompt is supplied.
- Give sentinel connections opaque IDs and write their lifecycle markers as JSONL metadata alongside the raw ACP frames.
- Keep
--exec, registry command resolution, inherited/stripped environment, workdir, stderr capture, and timeout behavior aligned with ordinary probes. - Update
cmd/acpdbg/README.mdand theacp-debugskill with exact syntax, output fields, interpretation, and the warning that no observation is not a portable agent failure.
Tests
- Wire contract and reducer: event kinds, status precedence, attempt supersession, bounds, target sanitization, and typed JSON rehydration.
- ACP delivery: HTTP/SSE capability selection, filtered reasons, delivery events, session acceptance, explicit errors, and reset attempt rotation.
- Kandev endpoint observation: separate connections and attempts emit
initialize,
tools/list, tool-call, error, and unregister evidence with no tool payloads or agent-provided ownership IDs. - Passthrough: strategies emit delivered evidence; nil strategy and materialization failures are unavailable/failed rather than active.
- Persistence: orchestrator rejects stale attempts, retains three attempts and 64 events each, persists sanitized data, broadcasts live state, and hydrates task detail after reload.
- Diagnostics: arbitrary targets are rejected, authorization runs before dependencies, network and stdio tests are bounded and cleaned up, successful tests do not promote attachment state, stderr is capped and never persisted.
- Frontend state: boot hydration, WS replacement, active-session separation, session removal, status reduction, diagnostic copy redaction, desktop disclosure state, keyboard operation, and touch Drawer selection.
- acpdbg: resolved temp cwd reaches
session/new; fake ACP clients that connect, ignore, list, call, or error produce distinct summaries.
E2E tests
apps/web/e2e/tests/chat/mcp-status.spec.ts- create two sessions in one task with different attachment reports;
- hover and keyboard-focus the neutral desktop trigger;
- pin it by click and verify Active, Connected, Delivered/unverified, Failed, and Filtered rows;
- switch session tabs and verify evidence never leaks between agents;
- reload and verify persisted status hydrates;
- run a mocked endpoint test, reveal bounded output, and confirm copied diagnostics omit that output and sensitive target parts.
apps/web/e2e/tests/chat/mobile-mcp-status.spec.ts- tap the 44px trigger under
mobile-chrome; - verify the inset Drawer contains the same rows and actions;
- exercise one diagnostic action;
- assert safe-area layout, internal scrolling, and no horizontal overflow.
- tap the 44px trigger under
E2E setup may seed bounded attachment metadata and diagnostic responses through E2E-only helpers, but assertions must operate through the rendered UI. Backend integration tests remain responsible for proving real MCP hook events.
Public documentation
- Update
docs/public/agents-and-profiles.mdtroubleshooting to distinguish configured, delivered, connected, and tools loaded. - Update
docs/public/automation-and-mcp.mdwith the toolbar diagnostic flow, third-party observability limit, endpoint-test interpretation, and release privacy boundary. - Update
docs/public/add-agent-cli.mdwith the sentinelacpdbgworkflow and the requirement that new passthrough agents report an unavailable strategy honestly. - Update
docs/public/feature-status.mdif its profile MCP observability row still describes only configuration. - Update
apps/backend/cmd/acpdbg/README.mdand.agents/skills/acp-debug/SKILL.mdwith the developer command contract.
Implementation waves and parallel candidates
Implementation remains in the user-started primary session unless the user later authorizes native implementation agents.
Wave 1:
Wave 2:
Tasks 02 and 06 are parallel-safe if explicitly delegated later. Task 02 owns
agentctl API/adapter/process/MCP server and lifecycle passthrough files. Task 06
owns internal/agent/acpdbg, cmd/acpdbg, and its developer documentation.
Both consume Task 01's terminology but do not edit the same implementation
files.
Wave 3:
Wave 4:
Wave 5:
Wave 6:
Wave 7:
Required final verification
After every task's targeted RED/GREEN evidence is recorded:
make -C apps/backend testcd apps && pnpm install --frozen-lockfilecd apps/web && pnpm run typecheckcd apps && pnpm --filter @kandev/web lintcd apps && pnpm --filter @kandev/web e2e:run tests/chat/mcp-status.spec.ts -- --project=chromiumcd apps && pnpm --filter @kandev/web e2e:run tests/chat/mobile-mcp-status.spec.ts -- --project=mobile-chromenode --test scripts/validate-public-docs.test.mjsnode scripts/validate-public-docs.mjs
Risks and non-goals
- Direct third-party MCP connectivity remains unobservable without a proxy or provider extension. The UI must preserve Delivered/unverified as a normal, honest state.
- Some agents attach lazily, so no timeout converts missing initialize into failure.
- MCP hooks can be concurrent. The reducer must be idempotent, preserve per-connection ordering where meaningful, and reject stale attempts.
- Status reporting must not block the agent update channel or MCP request path; a dropped live event can be logged, while later snapshots and persistence repair the current display.
- Endpoint tests can start stdio processes and make network requests, but only after explicit user action, only from stored effective configuration, and under strict timeout/cleanup.
- This plan does not proxy third-party MCP traffic, add a provider-specific ACP connected-server extension, enable release raw-frame logging, or automatically reset/restart sessions.