construct/mcp_server/runtime.rs
1//! Runtime handles the MCP server may optionally receive from the main daemon.
2//!
3//! The standalone MCP binary (now removed) had to boot blind: it only knew the
4//! workspace dir and a best-effort `Config`. Tools that depend on live runtime
5//! state — a delegate agent pool, the channel map, a workspace manager, a
6//! session store, a concrete provider — were silently skipped.
7//!
8//! Now that the MCP server runs as an in-process tokio task inside the main
9//! daemon, the gateway can hand those handles down via [`RuntimeHandles`] and
10//! the registry can register the previously-skipped tools.
11//!
12//! ## Design rules
13//!
14//! - Every field is `Option<Arc<_>>` so missing handles **degrade gracefully**
15//! — the tool is listed in `skipped` with a reason, never a panic.
16//! - We deliberately **do not** import `gateway::AppState` here. That would
17//! create a module-layer cycle (gateway → mcp_server → gateway). Callers
18//! clone the Arcs they need out of `AppState` and pass them individually.
19//! - Types come from the modules that own them, not from some central bag.
20//! If a caller wants to wire up `discord_search`, they pass the `Arc<dyn
21//! Memory>` backing discord.db directly — same shape the gateway uses.
22
23use crate::channels::session_backend::SessionBackend;
24use crate::config::DelegateAgentConfig;
25use crate::config::workspace::WorkspaceManager;
26use crate::memory::Memory;
27use crate::providers::ProviderRuntimeOptions;
28use crate::tools::Tool;
29use crate::tools::ask_user::ChannelMapHandle;
30use std::collections::HashMap;
31use std::sync::Arc;
32use tokio::sync::RwLock as TokioRwLock;
33
34/// Aggregates `Arc`-clones of everything the previously-skipped MCP tools need.
35///
36/// The gateway builds this after its `AppState` is fully constructed, then
37/// hands it to [`build_tools_with_runtime`](super::registry::build_tools_with_runtime).
38///
39/// All fields are optional; the registry gracefully skips a tool whenever its
40/// required handle is `None`.
41#[derive(Default, Clone)]
42pub struct RuntimeHandles {
43 /// Workspace manager for the `workspace` tool.
44 pub workspace_manager: Option<Arc<TokioRwLock<WorkspaceManager>>>,
45
46 /// Shared channel map (platform name → `Arc<dyn Channel>`) used by the
47 /// `poll`, `reaction`, `ask_user`, and `escalate` tools.
48 ///
49 /// The same `Arc<RwLock<_>>` is threaded into every tool that needs it;
50 /// when the channel supervisor populates it, all tools see the update.
51 pub channel_map: Option<ChannelMapHandle>,
52
53 /// Dedicated channel map handle for the `reaction` tool (it keeps its own
54 /// handle so the gateway-facing tool and the MCP-facing tool share it).
55 pub reaction_channels: Option<ChannelMapHandle>,
56
57 /// Dedicated channel map handle for the `ask_user` tool.
58 pub ask_user_channels: Option<ChannelMapHandle>,
59
60 /// Dedicated channel map handle for the `escalate` tool.
61 pub escalate_channels: Option<ChannelMapHandle>,
62
63 /// Memory backend for `discord_search` (historically a separate
64 /// SQLite store; currently unwired — pending a Kumiho-backed
65 /// reimplementation).
66 pub discord_memory: Option<Arc<dyn Memory>>,
67
68 /// Session backend used by the `sessions_list`, `sessions_history`, and
69 /// `sessions_send` tools.
70 pub session_store: Option<Arc<dyn SessionBackend>>,
71
72 /// Agent configurations for `delegate` and `swarm`. Cloned out of
73 /// `Config.agents`.
74 pub agent_config: Option<Arc<HashMap<String, DelegateAgentConfig>>>,
75
76 /// Provider runtime options — supplies the credentials/url/reasoning
77 /// preferences delegate sub-agents need when invoking LLMs.
78 pub provider_runtime_options: Option<Arc<ProviderRuntimeOptions>>,
79
80 /// Fallback API key for delegate sub-agents when the per-agent config
81 /// doesn't supply one (same value passed to `all_tools_with_runtime`).
82 pub fallback_api_key: Option<Arc<str>>,
83
84 /// Escape hatch: pre-built tools constructed by the caller (e.g. the
85 /// gateway's `all_tools_with_runtime` list) that the MCP registry should
86 /// merge in wholesale.
87 ///
88 /// When present, each entry is folded in after the baseline — if a name
89 /// collides, the pre-built tool wins. This lets the gateway forward its
90 /// full live tool registry to MCP clients without the registry having to
91 /// re-invoke every tool's constructor.
92 pub pre_built_tools: Option<Vec<Arc<dyn Tool>>>,
93}
94
95impl RuntimeHandles {
96 /// Create an empty `RuntimeHandles` where every field is `None`.
97 ///
98 /// Used by tests and by the degraded boot path if AppState construction
99 /// failed before the MCP task spawned.
100 #[must_use]
101 pub fn empty() -> Self {
102 Self::default()
103 }
104}