Skip to content

Collapse MCP startup notifications into a single aggregate readiness summary #22059

@team-wcv

Description

@team-wcv

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    app-serverIssues involving app server protocol or interfacesconfigIssues involving config.toml, config keys, config merging, or config updatesenhancementNew feature or requestmcpIssues related to the use of model context protocol (MCP) servers

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions