feat(gui): Integrate External MCPs into Operations Hub with status indicators
This commit is contained in:
@@ -953,6 +953,15 @@ class AppController:
|
|||||||
self.ui_agent_tools = {t: agent_tools_cfg.get(t, True) for t in models.AGENT_TOOL_NAMES}
|
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", "")
|
label = self.project.get("project", {}).get("name", "")
|
||||||
session_logger.open_session(label=label)
|
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:
|
def cb_load_prior_log(self, path: Optional[str] = None) -> None:
|
||||||
root = hide_tk_root()
|
root = hide_tk_root()
|
||||||
@@ -1266,6 +1275,9 @@ class AppController:
|
|||||||
"action": "ticket_completed",
|
"action": "ticket_completed",
|
||||||
"payload": payload
|
"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:
|
def _handle_request_event(self, event: events.UserRequestEvent) -> None:
|
||||||
"""Processes a UserRequestEvent by calling the AI client."""
|
"""Processes a UserRequestEvent by calling the AI client."""
|
||||||
|
|||||||
51
src/gui_2.py
51
src/gui_2.py
@@ -608,6 +608,9 @@ class App:
|
|||||||
if imgui.begin_tab_item("Usage Analytics")[0]:
|
if imgui.begin_tab_item("Usage Analytics")[0]:
|
||||||
self._render_usage_analytics_panel()
|
self._render_usage_analytics_panel()
|
||||||
imgui.end_tab_item()
|
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_tab_bar()
|
||||||
imgui.end()
|
imgui.end()
|
||||||
|
|
||||||
@@ -2573,6 +2576,54 @@ def hello():
|
|||||||
imgui.pop_style_color(2)
|
imgui.pop_style_color(2)
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.end_component("_render_response_panel")
|
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:
|
def _render_comms_history_panel(self) -> None:
|
||||||
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
|
if self.perf_profiling_enabled: self.perf_monitor.start_component("_render_comms_history_panel")
|
||||||
st_col = vec4(200, 220, 160)
|
st_col = vec4(200, 220, 160)
|
||||||
|
|||||||
@@ -925,12 +925,14 @@ class StdioMCPServer:
|
|||||||
self.tools = {}
|
self.tools = {}
|
||||||
self._id_counter = 0
|
self._id_counter = 0
|
||||||
self._pending_requests = {}
|
self._pending_requests = {}
|
||||||
|
self.status = 'idle'
|
||||||
|
|
||||||
def _get_id(self):
|
def _get_id(self):
|
||||||
self._id_counter += 1
|
self._id_counter += 1
|
||||||
return self._id_counter
|
return self._id_counter
|
||||||
|
|
||||||
async def start(self):
|
async def start(self):
|
||||||
|
self.status = 'starting'
|
||||||
self.proc = await asyncio.create_subprocess_exec(
|
self.proc = await asyncio.create_subprocess_exec(
|
||||||
self.config.command,
|
self.config.command,
|
||||||
*self.config.args,
|
*self.config.args,
|
||||||
@@ -940,6 +942,7 @@ class StdioMCPServer:
|
|||||||
)
|
)
|
||||||
asyncio.create_task(self._read_stderr())
|
asyncio.create_task(self._read_stderr())
|
||||||
await self.list_tools()
|
await self.list_tools()
|
||||||
|
self.status = 'running'
|
||||||
|
|
||||||
async def stop(self):
|
async def stop(self):
|
||||||
if self.proc:
|
if self.proc:
|
||||||
@@ -955,6 +958,7 @@ class StdioMCPServer:
|
|||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
self.proc = None
|
self.proc = None
|
||||||
|
self.status = 'idle'
|
||||||
|
|
||||||
async def _read_stderr(self):
|
async def _read_stderr(self):
|
||||||
while self.proc and not self.proc.stdout.at_eof():
|
while self.proc and not self.proc.stdout.at_eof():
|
||||||
@@ -1015,9 +1019,12 @@ class ExternalMCPManager:
|
|||||||
all_tools = {}
|
all_tools = {}
|
||||||
for sname, server in self.servers.items():
|
for sname, server in self.servers.items():
|
||||||
for tname, tool in server.tools.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
|
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:
|
async def async_dispatch(self, tool_name: str, tool_input: dict) -> str:
|
||||||
for server in self.servers.values():
|
for server in self.servers.values():
|
||||||
if tool_name in server.tools:
|
if tool_name in server.tools:
|
||||||
|
|||||||
Reference in New Issue
Block a user