Files
PaperClipAI/packages
DottaandClaude Opus 5.5 cb0b7525e0 feat(task-chat): warn at the top of a run when an MCP server failed to start (#19289)
## Thinking Path

> - Paperclip is the open source app people use to manage AI agents for
work.
> - Each agent run shows its activity in the task chat. The Paperclip
runner turns harness events into that activity.
> - Codex reports `mcpServer/startupStatus/updated` when an MCP server
fails to start. The runner dropped this event at the notification
allowlist and in the Rust normalizer.
> - When an MCP server fails, the agent runs without its tools. The
agent then says "I could not reach the server", and the user cannot see
why.
> - This pull request maps a failed MCP start to a user-facing provider
notice and shows it as one warning line at the top of the run.
> - The benefit is that the cause of a "cannot reach my tools" answer is
visible next to that answer.

## Linked Issues or Issue Description

Refs #19252 (the plan card, which set the pattern for promoting a
dropped harness signal to a dedicated task-chat surface).

**Problem description**
Codex sends `mcpServer/startupStatus/updated` (`name`, `status`,
`error`, `failureReason`) during `thread/start`. The TypeScript
allowlist (`codex-thread-normalization.ts`) and the Rust normalizer
(`runner-core/src/provider_events.rs`) both dropped it. A run with a
broken MCP server showed no sign of the failure. The agent only said
that it could not reach the tools.

**Proposed solution**
Map `status: "failed"` to `provider.notice.recorded` with `category:
"mcp_server_startup_failed"`, `severity: "warning"`, `scope: "session"`
and `userActionable: true`. Show these notices as one run-health line
under the runner header: "github MCP server failed to start: No such
file or directory (os error 2)".

**Alternatives considered**
An inline notice row in the timeline. It scrolls away and sits among
tool rows. A run-level line next to the header is easier to connect to
the agent's answer.

## What Changed

- Runner (Rust, production Codex path): `normalize_codex_notification`
maps a failed `mcpServer/startupStatus/updated` to a
`provider.notice.recorded` notice. `starting`, `ready` and `cancelled`
stay out of the transcript. The new helper removes the Codex wrapper
text ("MCP client for \`x\` failed to start: MCP startup failed:") so
that the line shows the real reason. `reauthenticationRequired` becomes
"needs re-authentication".
- Runner (TypeScript): the same mapping in
`canonicalProviderEventsFromCodex`, and the method is added to
`isSupportedCodexNotificationMethod`.
- Server: the `view=conversation` run-event query keeps
`mcp_server_startup_failed` notices. A settled run can then still show
the warning. Other notices stay out of that view.
- UI: new `TaskChatRunHealthBanner`. `partitionRunHealthItems` takes MCP
failures out of the run's items, with one line per server where the
latest report wins. `TaskChatRunnerTurn` (live runs) and `TaskChatTurn`
(settled, historical and merged turns) show it under the header.
`coalesceSettledTurns` merges the failures when it merges turns.
- Storybook: `Tasks and conversations/…/Task chat/Run health` shows one
failure, several failures, a finished run and a healthy run, all through
the production runner turn.

## Verification

- `pnpm --filter @paperclipai/paperclip-runner exec vitest run
src/provider-events.test.ts src/drivers/codex src/protocol`: 508 tests
pass.
- `cargo test --release -p paperclip-runner-core --lib --
provider_events`: 20 tests pass.
- `pnpm --filter @paperclipai/ui exec vitest run
src/components/task-chat src/components/transcript
src/components/TaskChatThread.test.tsx`: 1340 tests pass, including the
new `TaskChatRunHealthBanner.test.tsx` (persisted event to rendered
banner, deduplication, settled historical turn, merged turns).
- `pnpm --filter @paperclipai/server exec vitest run
src/__tests__/task-cold-open.test.ts`: the conversation view keeps the
MCP notice and still excludes other notices.
- `pnpm check:token-gates` is clean. `node
scripts/typecheck-low-memory.mjs` passes.
- Live test on a local instance with Codex 0.160: a Codex agent with a
preconfigured Codex home whose `config.toml` has `[mcp_servers.github]
command = "github-mcp-server-not-installed"`. Codex reported the failure
during `thread/start`. The persisted event had the summary "github MCP
server failed to start: No such file or directory (os error 2)". The
banner showed while the run was live. It also showed on the settled turn
after "More run output", above the agent's answer that it could not
reach the GitHub MCP server.

## Risks

- Low risk. The change adds one notice category. It does not change how
existing notices map or render.
- A settled run whose transcript is deferred on a cold open (a
successful run with a posted agent comment) shows the warning only after
"More run output". This is the same as all other run activity under the
current compact history.
- Claude does not report MCP startup failures over ACP.
`claude-agent-acp` reads MCP status only for OAuth. This change is
therefore Codex-only.
- Provisional task titles from provider session names are not part of
this change. Codex app-server does not generate thread names (only its
TUI does). Claude generates its title after the turn ends, and ACPX
drops the title text.

> For core feature work, check [`ROADMAP.md`](ROADMAP.md) first and
discuss it in `#dev` before opening the PR. Feature PRs that overlap
with planned core work may need to be redirected — check the roadmap
first. See `CONTRIBUTING.md`.

## Model Used

- Claude Opus 5.5 (`claude-opus-5-5`), through Claude Code with tool use
(shell, file edits, browser automation for Storybook and the live test).

## Checklist

- [x] I have included a thinking path that traces from project context
to this change
- [x] I have specified the model used (with version and capability
details)
- [x] I have checked ROADMAP.md and confirmed this PR does not duplicate
planned core work
- [x] I have searched GitHub for duplicate or related PRs and linked
them above
- [x] I have either (a) linked existing issues with `Fixes: #` / `Closes
#` / `Refs #` OR (b) described the issue in-PR following the relevant
issue template
- [x] I have not referenced internal/instance-local Paperclip issues or
links (only public GitHub `#NNN` / `github.com/paperclipai/paperclip`
URLs)
- [x] My branch name describes the change (e.g. `docs/...`, `fix/...`)
and contains no internal Paperclip ticket id or instance-derived details
- [x] I have run tests locally and they pass
- [x] I have added or updated tests where applicable
- [x] I have updated relevant documentation to reflect my changes
- [x] I have considered and documented any risks above
- [ ] All Paperclip CI gates are green
- [x] Greptile is 5/5 with no open P2s, recommendations, or follow-ups
- [x] I will address all Greptile and reviewer comments before
requesting merge

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-05 18:10:51 +00:00
..