feat(mcp): split mcp_client/registry, multi-registry, boot-spawn + setup agent (#2559)

This commit is contained in:
Steven Enamakel
2026-05-23 22:15:33 -07:00
committed by GitHub
parent f66e7e433c
commit e9ca97c48c
60 changed files with 3829 additions and 1190 deletions
+5 -2
View File
@@ -115,7 +115,7 @@ fn build_registered_controllers() -> Vec<RegisteredController> {
// Scheduled job management
controllers.extend(crate::openhuman::cron::all_cron_registered_controllers());
// MCP client subsystem: Smithery registry browser, local server install/connect, tool dispatch
controllers.extend(crate::openhuman::mcp_clients::all_mcp_clients_registered_controllers());
controllers.extend(crate::openhuman::mcp_registry::all_mcp_registry_registered_controllers());
// Webview APIs bridge — proxies connector calls (Gmail, …) through
// a WebSocket to the Tauri shell so curl reaches the live webview.
controllers.extend(crate::openhuman::webview_apis::all_webview_apis_registered_controllers());
@@ -278,7 +278,7 @@ fn build_declared_controller_schemas() -> Vec<ControllerSchema> {
schemas.extend(crate::openhuman::audio_toolkit::all_audio_toolkit_controller_schemas());
schemas.extend(crate::openhuman::composio::all_composio_controller_schemas());
schemas.extend(crate::openhuman::cron::all_cron_controller_schemas());
schemas.extend(crate::openhuman::mcp_clients::all_mcp_clients_controller_schemas());
schemas.extend(crate::openhuman::mcp_registry::all_mcp_registry_controller_schemas());
schemas.extend(crate::openhuman::webview_apis::all_webview_apis_controller_schemas());
schemas.extend(crate::openhuman::agent::all_agent_controller_schemas());
schemas.extend(crate::openhuman::agent_experience::all_agent_experience_controller_schemas());
@@ -395,6 +395,9 @@ pub fn namespace_description(namespace: &str) -> Option<&'static str> {
"mcp_clients" => Some(
"Browse the Smithery.ai MCP registry, install MCP servers locally, manage their stdio connections, and expose their tools to the agent.",
),
"mcp_setup" => Some(
"MCP setup agent surface: search registries, request secrets out-of-band (opaque refs, no raw values in agent context), test, and install + connect.",
),
"decrypt" => Some("Decrypt secure values managed by secret storage."),
"doctor" => Some("Run diagnostics for workspace and runtime health."),
"encrypt" => Some("Encrypt secure values managed by secret storage."),
+12 -1
View File
@@ -516,6 +516,16 @@ pub enum DomainEvent {
success: bool,
elapsed_ms: u64,
},
/// The MCP setup agent asked the user for a secret value. The UI
/// subscribes to this and renders a native prompt; on submit it calls
/// `openhuman.mcp_setup_submit_secret`. `ref_id` is the opaque handle
/// returned to the agent; the raw secret value never traverses this
/// event.
McpSetupSecretRequested {
ref_id: String,
key_name: String,
prompt: String,
},
// ── System lifecycle ────────────────────────────────────────────────
/// A system component started up.
@@ -638,7 +648,8 @@ impl DomainEvent {
Self::McpServerInstalled { .. }
| Self::McpServerConnected { .. }
| Self::McpServerDisconnected { .. }
| Self::McpClientToolExecuted { .. } => "mcp_client",
| Self::McpClientToolExecuted { .. }
| Self::McpSetupSecretRequested { .. } => "mcp_client",
}
}
}
+12
View File
@@ -1693,6 +1693,18 @@ pub async fn bootstrap_core_runtime(embedded_core: bool) {
// --- Workspace migrations --------------------------------------------
crate::openhuman::startup::run_workspace_migrations(&workspace_dir);
// --- MCP registry boot-spawn -----------------------------------------
// Bring up every locally-installed MCP server's stdio subprocess so its
// tools are available to the agent as soon as the core is ready.
// Errors are logged per-server and never block boot. Runs as a
// background task so a slow npx install can't gate startup.
{
let cfg = cfg.clone();
tokio::spawn(async move {
crate::openhuman::mcp_registry::boot::spawn_installed_servers(&cfg).await;
});
}
// --- Socket manager bootstrap ---
let socket_mgr = Arc::new(SocketManager::new());
set_global_socket_manager(socket_mgr.clone());
+62
View File
@@ -509,6 +509,7 @@ pub fn spawn_web_channel_bridge(io: SocketIo) {
let io_transcription = io.clone();
let io_auth = io.clone();
let io_companion = io.clone();
let io_mcp_setup = io.clone();
// 2. Dictation hotkey events → broadcast to all connected clients.
tokio::spawn(async move {
@@ -659,6 +660,67 @@ pub fn spawn_web_channel_bridge(io: SocketIo) {
log::debug!("[socketio] auth session_expired bridge stopped");
});
// 6b. McpSetupSecretRequested → broadcast `mcp_setup:secret_requested`
// so the UI can render a native input dialog. Only the opaque
// ref + safe display fields are forwarded; raw secret values
// are not part of the event payload.
tokio::spawn(async move {
let bus = {
const RETRY_INTERVAL_MS: u64 = 250;
const MAX_WAIT_SECS: u64 = 30;
let max_attempts = (MAX_WAIT_SECS * 1000) / RETRY_INTERVAL_MS;
let mut attempts: u64 = 0;
loop {
if let Some(bus) = crate::core::event_bus::global() {
break bus;
}
attempts += 1;
if attempts > max_attempts {
log::warn!(
"[socketio] event_bus not initialised after {}s — mcp_setup bridge giving up",
MAX_WAIT_SECS
);
return;
}
tokio::time::sleep(std::time::Duration::from_millis(RETRY_INTERVAL_MS)).await;
}
};
let mut rx = bus.raw_receiver();
loop {
let event = match rx.recv().await {
Ok(event) => event,
Err(tokio::sync::broadcast::error::RecvError::Lagged(skipped)) => {
log::warn!(
"[socketio] dropped {} event_bus events due to lag (mcp_setup bridge)",
skipped
);
continue;
}
Err(tokio::sync::broadcast::error::RecvError::Closed) => break,
};
if let crate::core::event_bus::DomainEvent::McpSetupSecretRequested {
ref_id,
key_name,
prompt,
} = event
{
log::info!(
"[socketio] broadcast mcp_setup:secret_requested ref={} key={}",
ref_id,
key_name
);
let payload = serde_json::json!({
"ref_id": ref_id,
"key_name": key_name,
"prompt": prompt,
});
let _ = io_mcp_setup.emit("mcp_setup:secret_requested", &payload);
let _ = io_mcp_setup.emit("mcp_setup_secret_requested", &payload);
}
}
log::debug!("[socketio] mcp_setup secret_requested bridge stopped");
});
// 5. Transcription results → broadcast to all connected clients.
tokio::spawn(async move {
let mut rx = crate::openhuman::voice::dictation_listener::subscribe_transcription_results();