fix(tui): implement /mcp inventory for tui_app_server (#14931)

## Problem

The `/mcp` command did not work in the app-server TUI (remote mode). On
`main`, `add_mcp_output()` called `McpManager::effective_servers()`
in-process, which only sees locally configured servers, and then emitted
a generic stub message for the app-server to handle. In remote usage,
that left `/mcp` without a real inventory view.

## Solution

Implement `/mcp` for the app-server TUI by fetching MCP server inventory
directly from the app-server via the paginated `mcpServerStatus/list`
RPC and rendering the results into chat history.

The command now follows a three-phase lifecycle:

1. Loading: `ChatWidget::add_mcp_output()` inserts a transient
`McpInventoryLoadingCell` and emits `AppEvent::FetchMcpInventory`. This
gives immediate feedback that the command registered.
2. Fetch: `App::fetch_mcp_inventory()` spawns a background task that
calls `fetch_all_mcp_server_statuses()` over an app-server request
handle. When the RPC completes, it sends `AppEvent::McpInventoryLoaded {
result }`.
3. Resolve: `App::handle_mcp_inventory_result()` clears the loading cell
and renders either `new_mcp_tools_output_from_statuses(...)` or an error
message.

This keeps the main app event loop responsive, so the TUI can repaint
before the remote RPC finishes.

## Notes

- No `app-server` changes were required.
- The rendered inventory includes auth, tools, resources, and resource
templates, plus transport details when they are available from local
config for display enrichment.
- The app-server RPC does not expose authoritative `enabled` or
`disabled_reason` state for MCP servers, so the remote `/mcp` view no
longer renders a `Status:` row rather than guessing from local config.
- RPC failures surface in history as `Failed to load MCP inventory:
...`.

## Tests

- `slash_mcp_requests_inventory_via_app_server`
- `mcp_inventory_maps_prefix_tool_names_by_server`
- `handle_mcp_inventory_result_clears_committed_loading_cell`
- `mcp_tools_output_from_statuses_renders_status_only_servers`
- `mcp_inventory_loading_snapshot`
This commit is contained in:
Felipe Coury
2026-03-17 16:11:27 -06:00
committed by GitHub
parent 0d2ff40a58
commit 43ee72a9b9
7 changed files with 581 additions and 9 deletions
+29 -9
View File
@@ -67,7 +67,6 @@ use codex_core::find_thread_name_by_id;
use codex_core::git_info::current_branch_name;
use codex_core::git_info::get_git_repo_root;
use codex_core::git_info::local_git_branches;
use codex_core::mcp::McpManager;
use codex_core::plugins::PluginsManager;
use codex_core::project_doc::DEFAULT_PROJECT_DOC_FILENAME;
use codex_core::skills::model::SkillMetadata;
@@ -8243,18 +8242,39 @@ impl ChatWidget {
PlainHistoryCell::new(vec![line.into()])
}
/// Begin the asynchronous MCP inventory flow: show a loading spinner and
/// request the app-server fetch via `AppEvent::FetchMcpInventory`.
///
/// The spinner lives in `active_cell` and is cleared by
/// [`clear_mcp_inventory_loading`] once the result arrives.
pub(crate) fn add_mcp_output(&mut self) {
let mcp_manager = McpManager::new(Arc::new(PluginsManager::new(
self.config.codex_home.clone(),
self.flush_answer_stream_with_separator();
self.flush_active_cell();
self.active_cell = Some(Box::new(history_cell::new_mcp_inventory_loading(
self.config.animations,
)));
if mcp_manager
.effective_servers(&self.config, /*auth*/ None)
.is_empty()
self.bump_active_cell_revision();
self.request_redraw();
self.app_event_tx.send(AppEvent::FetchMcpInventory);
}
/// Remove the MCP loading spinner if it is still the active cell.
///
/// Uses `Any`-based type checking so that a late-arriving inventory result
/// does not accidentally clear an unrelated cell that was set in the meantime.
pub(crate) fn clear_mcp_inventory_loading(&mut self) {
let Some(active) = self.active_cell.as_ref() else {
return;
};
if !active
.as_any()
.is::<history_cell::McpInventoryLoadingCell>()
{
self.add_to_history(history_cell::empty_mcp_output());
} else {
self.add_app_server_stub_message("MCP tool inventory");
return;
}
self.active_cell = None;
self.bump_active_cell_revision();
self.request_redraw();
}
pub(crate) fn add_connectors_output(&mut self) {