feat(gui): Integrate External MCPs into Operations Hub with status indicators

This commit is contained in:
2026-03-12 15:54:52 -04:00
parent 828fadf829
commit 3b2588ad61
3 changed files with 71 additions and 1 deletions

View File

@@ -953,6 +953,15 @@ class AppController:
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
label = self.project.get("project", {}).get("name", "")
session_logger.open_session(label=label)
# Trigger auto-start of MCP servers
self.event_queue.put('refresh_external_mcps', None)
async def refresh_external_mcps(self):
await mcp_client.get_external_mcp_manager().stop_all()
# Start servers with auto_start=True
for name, cfg in self.mcp_config.mcpServers.items():
if cfg.auto_start:
await mcp_client.get_external_mcp_manager().add_server(cfg)
def cb_load_prior_log(self, path: Optional[str] = None) -> None:
root = hide_tk_root()
@@ -1266,6 +1275,9 @@ class AppController:
"action": "ticket_completed",
"payload": payload
})
elif event_name == "refresh_external_mcps":
import asyncio
asyncio.run(self.refresh_external_mcps())
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
"""Processes a UserRequestEvent by calling the AI client."""

View File

@@ -608,6 +608,9 @@ class App:
if imgui.begin_tab_item("Usage Analytics")[0]:
self._render_usage_analytics_panel()
imgui.end_tab_item()
if imgui.begin_tab_item("External Tools")[0]:
self._render_external_tools_panel()
imgui.end_tab_item()
imgui.end_tab_bar()
imgui.end()
@@ -2573,6 +2576,54 @@ def hello():
imgui.pop_style_color(2)
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_response_panel")
def _render_external_tools_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_external_tools_panel")
if imgui.button("Refresh External MCPs"):
self.event_queue.put("refresh_external_mcps", None)
imgui.separator()
# Server status indicators
manager = mcp_client.get_external_mcp_manager()
statuses = manager.get_servers_status()
if statuses:
imgui.text("Servers:")
for sname, status in statuses.items():
imgui.same_line()
# Green for running, Yellow for starting, Red for error, Gray for idle
col = (0.5, 0.5, 0.5, 1.0)
if status == 'running':
col = (0.0, 1.0, 0.0, 1.0)
elif status == 'starting':
col = (1.0, 1.0, 0.0, 1.0)
elif status == 'error':
col = (1.0, 0.0, 0.0, 1.0)
imgui.color_button(f"##status_{sname}", col)
imgui.same_line()
imgui.text(sname)
imgui.separator()
tools = manager.get_all_tools()
if not tools:
imgui.text_disabled("No external tools found.")
else:
if imgui.begin_table("external_tools_table", 3, imgui.TableFlags_.borders | imgui.TableFlags_.row_bg | imgui.TableFlags_.resizable):
imgui.table_setup_column("Name")
imgui.table_setup_column("Server")
imgui.table_setup_column("Description")
imgui.table_headers_row()
for tname, tinfo in tools.items():
imgui.table_next_row()
imgui.table_next_column()
imgui.text(tname)
imgui.table_next_column()
imgui.text(tinfo.get('server', 'unknown'))
imgui.table_next_column()
imgui.text(tinfo.get('description', ''))
imgui.end_table()
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_external_tools_panel")
def _render_comms_history_panel(self) -> None:
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
st_col = vec4(200, 220, 160)

View File

@@ -925,12 +925,14 @@ class StdioMCPServer:
self.tools = {}
self._id_counter = 0
self._pending_requests = {}
self.status = 'idle'
def _get_id(self):
self._id_counter += 1
return self._id_counter
async def start(self):
self.status = 'starting'
self.proc = await asyncio.create_subprocess_exec(
self.config.command,
*self.config.args,
@@ -940,6 +942,7 @@ class StdioMCPServer:
)
asyncio.create_task(self._read_stderr())
await self.list_tools()
self.status = 'running'
async def stop(self):
if self.proc:
@@ -955,6 +958,7 @@ class StdioMCPServer:
except Exception:
pass
self.proc = None
self.status = 'idle'
async def _read_stderr(self):
while self.proc and not self.proc.stdout.at_eof():
@@ -1015,9 +1019,12 @@ class ExternalMCPManager:
all_tools = {}
for sname, server in self.servers.items():
for tname, tool in server.tools.items():
all_tools[tname] = {**tool, 'server': sname}
all_tools[tname] = {**tool, 'server': sname, 'server_status': server.status}
return all_tools
def get_servers_status(self) -> dict[str, str]:
return {name: server.status for name, server in self.servers.items()}
async def async_dispatch(self, tool_name: str, tool_input: dict) -> str:
for server in self.servers.values():
if tool_name in server.tools: