Use thread config for TUI MCP inventory (#24532)

## Summary
`/mcp` in the TUI should reflect the current loaded thread, including
project-local MCP servers from that thread config. Before this change,
`mcpServerStatus/list` only read the latest global MCP config, so the
active chat could miss project-local servers.

This adds optional `threadId` to `mcpServerStatus/list`. When present,
app-server resolves the loaded thread and lists MCP status from the
refreshed effective config for that thread; when omitted, existing
global config behavior stays unchanged.

The TUI now sends the active chat thread id for `/mcp` and `/mcp
verbose`, carries that origin through the async inventory result, and
ignores stale completions if the user has switched threads before the
fetch returns. The app-server schemas were regenerated.

## Follow-up
Once this app-server API change lands, the desktop app should make the
same `threadId` plumbing so its MCP inventory also uses the current
thread config.

Fixes #23874
This commit is contained in:
Eric Traut
2026-05-26 07:44:04 -07:00
committed by GitHub
parent c4e53d103c
commit 0f91e869bd
15 changed files with 228 additions and 19 deletions
@@ -1747,6 +1747,8 @@ async fn slash_memory_drop_reports_stubbed_feature() {
#[tokio::test]
async fn slash_mcp_requests_inventory_via_app_server() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let thread_id = ThreadId::new();
chat.thread_id = Some(thread_id);
chat.dispatch_command(SlashCommand::Mcp);
@@ -1754,8 +1756,9 @@ async fn slash_mcp_requests_inventory_via_app_server() {
assert_matches!(
rx.try_recv(),
Ok(AppEvent::FetchMcpInventory {
detail: McpServerStatusDetail::ToolsAndAuthOnly
})
detail: McpServerStatusDetail::ToolsAndAuthOnly,
thread_id: Some(actual_thread_id)
}) if actual_thread_id == thread_id
);
assert!(op_rx.try_recv().is_err(), "expected no core op to be sent");
}
@@ -1763,6 +1766,8 @@ async fn slash_mcp_requests_inventory_via_app_server() {
#[tokio::test]
async fn slash_mcp_verbose_requests_full_inventory_via_app_server() {
let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await;
let thread_id = ThreadId::new();
chat.thread_id = Some(thread_id);
submit_composer_text(&mut chat, "/mcp verbose");
@@ -1770,8 +1775,9 @@ async fn slash_mcp_verbose_requests_full_inventory_via_app_server() {
assert_matches!(
rx.try_recv(),
Ok(AppEvent::FetchMcpInventory {
detail: McpServerStatusDetail::Full
})
detail: McpServerStatusDetail::Full,
thread_id: Some(actual_thread_id)
}) if actual_thread_id == thread_id
);
assert!(op_rx.try_recv().is_err(), "expected no core op to be sent");
}