codex - 💡(How to fix) Fix Collapse MCP startup notifications into a single aggregate readiness summary [1 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openai/codex#22059Fetched 2026-05-11 03:19:57
View on GitHub
Comments
0
Participants
1
Timeline
6
Reactions
0
Author
Participants
Timeline (top)
labeled ×4cross-referenced ×2

codex app-server fires one mcpServer/startupStatus/updated JSON-RPC notification per server per status transition (starting -> ready / failed / cancelled). For a config with N MCP servers, that is ~2N notifications during a single thread bootstrap, and harnesses driving codex app-server over stdio end up writing ad-hoc filters to silence the firehose once they only care about the final readiness state.

Codex actually already aggregates this internally as EventMsg::McpStartupComplete(McpStartupCompleteEvent { ready, failed, cancelled })codex-mcp/src/connection_manager.rs:294-313 fires it after all server tasks join — but app-server/src/bespoke_event_handling.rs never lifts it onto the JSON-RPC surface, so external clients cannot subscribe to it. Today they have to track per-server transitions, count the configured server names, and synthesize the aggregate themselves.

A real downstream filter that this would make redundant:

// downstream wrapper TUI: only render an MCP tool entry when the per-server
// notification reports a real failure; everything else is suppressed because
// the firehose is too noisy.
if (method === 'mcpServer/startupStatus/updated') {
  const showMcpStartupFailure = Boolean(params.error) || statusIsFailed(params.status);
  if (!showMcpStartupFailure) return;
}

The codex TUI's live MCP startup panel (tui/src/chatwidget/mcp_startup.rs) does want the per-server stream, so the fix can't be "stop emitting individual updates". It needs to be per-deployment opt-out.

Error Message

const showMcpStartupFailure = Boolean(params.error) || statusIsFailed(params.status);

  1. New aggregate JSON-RPC notification mcpServer/startupStatus/completed with payload McpServerStartupCompletedNotification { ready: Vec<String>, failed: Vec<{ name, error }>, cancelled: Vec<String> }, always emitted when the existing internal EventMsg::McpStartupComplete fires. Clients can render the equivalent of MCP: 5 ready, 1 failed (foo), 0 cancelled from a single message.

Root Cause

// downstream wrapper TUI: only render an MCP tool entry when the per-server
// notification reports a real failure; everything else is suppressed because
// the firehose is too noisy.
if (method === 'mcpServer/startupStatus/updated') {
  const showMcpStartupFailure = Boolean(params.error) || statusIsFailed(params.status);
  if (!showMcpStartupFailure) return;
}

Code Example

// downstream wrapper TUI: only render an MCP tool entry when the per-server
// notification reports a real failure; everything else is suppressed because
// the firehose is too noisy.
if (method === 'mcpServer/startupStatus/updated') {
  const showMcpStartupFailure = Boolean(params.error) || statusIsFailed(params.status);
  if (!showMcpStartupFailure) return;
}

---

[notifications]
   mcp_startup_individual = false  # only emit the aggregate summary
RAW_BUFFERClick to expand / collapse

Summary

codex app-server fires one mcpServer/startupStatus/updated JSON-RPC notification per server per status transition (starting -> ready / failed / cancelled). For a config with N MCP servers, that is ~2N notifications during a single thread bootstrap, and harnesses driving codex app-server over stdio end up writing ad-hoc filters to silence the firehose once they only care about the final readiness state.

Codex actually already aggregates this internally as EventMsg::McpStartupComplete(McpStartupCompleteEvent { ready, failed, cancelled })codex-mcp/src/connection_manager.rs:294-313 fires it after all server tasks join — but app-server/src/bespoke_event_handling.rs never lifts it onto the JSON-RPC surface, so external clients cannot subscribe to it. Today they have to track per-server transitions, count the configured server names, and synthesize the aggregate themselves.

A real downstream filter that this would make redundant:

// downstream wrapper TUI: only render an MCP tool entry when the per-server
// notification reports a real failure; everything else is suppressed because
// the firehose is too noisy.
if (method === 'mcpServer/startupStatus/updated') {
  const showMcpStartupFailure = Boolean(params.error) || statusIsFailed(params.status);
  if (!showMcpStartupFailure) return;
}

The codex TUI's live MCP startup panel (tui/src/chatwidget/mcp_startup.rs) does want the per-server stream, so the fix can't be "stop emitting individual updates". It needs to be per-deployment opt-out.

Proposed solution

  1. New aggregate JSON-RPC notification mcpServer/startupStatus/completed with payload McpServerStartupCompletedNotification { ready: Vec<String>, failed: Vec<{ name, error }>, cancelled: Vec<String> }, always emitted when the existing internal EventMsg::McpStartupComplete fires. Clients can render the equivalent of MCP: 5 ready, 1 failed (foo), 0 cancelled from a single message.

  2. Per-deployment opt-out for the per-server stream via [notifications] in config.toml:

    [notifications]
    mcp_startup_individual = false  # only emit the aggregate summary

    Default is true (preserves the historical "one notification per server per transition" behavior the codex TUI relies on). Setting false suppresses mcpServer/startupStatus/updated but never the aggregate, so failure detail is still observable.

  3. Encapsulation via a small ServerNotificationsConfig helper that future [notifications] knobs can extend, rather than threading &Config through every event handler.

Reference implementation

A working implementation with tests lives on the team-wcv fork:

Touches:

  • codex-rs/app-server-protocol/src/protocol/v2/mcp.rsMcpServerStartupFailureSummary + McpServerStartupCompletedNotification payload types.
  • codex-rs/app-server-protocol/src/protocol/common.rs — register McpServerStartupCompleted => \"mcpServer/startupStatus/completed\".
  • codex-rs/config/src/config_toml.rsNotificationsToml { mcp_startup_individual } + pub notifications: Option<NotificationsToml> on ConfigToml.
  • codex-rs/core/src/config/mod.rs — resolves into a flat Config::notifications_mcp_startup_individual: bool (default true).
  • codex-rs/core/config.schema.json — regenerated via just write-config-schema.
  • codex-rs/app-server/src/server_notifications_config.rs — new module with ServerNotificationsConfig::{from_config, emit_individual_mcp_startup} plus a with_individual_mcp_startup_disabled() test helper.
  • codex-rs/app-server/src/request_processors/{thread_lifecycle,thread_processor,turn_processor}.rs — plumb notifications_config through ListenerTaskContext.
  • codex-rs/app-server/src/bespoke_event_handling.rs — gate McpServerStatusUpdated behind emit_individual_mcp_startup(), handle EventMsg::McpStartupComplete to emit the aggregate. Three new tests cover (1) per-server emission on default config, (2) per-server suppression when the flag is off, and (3) aggregate emission regardless of the per-server gate.
  • App-server protocol schema artifacts regenerated via just write-app-server-schema (new v2/McpServerStartupCompletedNotification.{json,ts} + v2/McpServerStartupFailureSummary.ts).

cargo test -p codex-app-server --lib → 221 passed, 0 failed (218 pre-existing + 3 new). cargo test --lib -p codex-app-server -p codex-config -p codex-core → 1746 passed, 0 failed. cargo clippy --no-deps clean on all touched crates. just fmt applied.

Per `docs/contributing.md`

External contributions are by invitation only, so this is filed as an enhancement request with reference implementation attached. Happy to open the PR against this repo if the approach is acceptable.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING