mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-30 23:14:37 +00:00
feat(mcp): split mcp_client/registry, multi-registry, boot-spawn + setup agent (#2559)
This commit is contained in:
+5
-2
@@ -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."),
|
||||
|
||||
@@ -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",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user