Skip to main content

meerkat_runtime/handles/
mcp_server_lifecycle.rs

1//! Runtime impl of [`meerkat_core::handles::McpServerLifecycleHandle`].
2//!
3//! Routes per-server MCP handshake events into the session's MeerkatMachine
4//! DSL (`mcp_server_states` substate) and exposes the `PendingConnect` set
5//! for the agent loop's `[MCP_PENDING]` system-notice toggle.
6
7use std::collections::BTreeSet;
8use std::sync::Arc;
9
10use meerkat_core::handles::{DslTransitionError, McpServerLifecycleHandle};
11
12use super::HandleDslAuthority;
13use crate::meerkat_machine::dsl as mm_dsl;
14
15/// Runtime-backed [`McpServerLifecycleHandle`] impl.
16///
17/// Routes every trait method to the corresponding DSL input on the session's
18/// shared MeerkatMachine DSL authority.
19#[derive(Debug)]
20pub struct RuntimeMcpServerLifecycleHandle {
21    dsl: Arc<HandleDslAuthority>,
22}
23
24impl RuntimeMcpServerLifecycleHandle {
25    /// Construct a handle backed by the session's shared DSL authority.
26    pub fn new(dsl: Arc<HandleDslAuthority>) -> Self {
27        Self { dsl }
28    }
29
30    /// Construct a handle backed by an ephemeral DSL authority.
31    pub fn ephemeral() -> Self {
32        Self::new(Arc::new(HandleDslAuthority::ephemeral()))
33    }
34}
35
36impl McpServerLifecycleHandle for RuntimeMcpServerLifecycleHandle {
37    fn apply_connect_pending(&self, server_id: &str) -> Result<(), DslTransitionError> {
38        // intra-machine: no route; dispatcher not applicable (handle targets the meerkat DSL directly, not a CompositionDispatcher seam)
39        self.dsl.apply_input(
40            mm_dsl::MeerkatMachineInput::McpServerConnectPending {
41                server_id: mm_dsl::McpServerId::from(server_id.to_string()),
42            },
43            "McpServerLifecycleHandle::apply_connect_pending",
44        )
45    }
46
47    fn apply_connected(&self, server_id: &str) -> Result<(), DslTransitionError> {
48        // intra-machine: no route; dispatcher not applicable (handle targets the meerkat DSL directly, not a CompositionDispatcher seam)
49        self.dsl.apply_input(
50            mm_dsl::MeerkatMachineInput::McpServerConnected {
51                server_id: mm_dsl::McpServerId::from(server_id.to_string()),
52            },
53            "McpServerLifecycleHandle::apply_connected",
54        )
55    }
56
57    fn apply_failed(&self, server_id: &str, error: &str) -> Result<(), DslTransitionError> {
58        // intra-machine: no route; dispatcher not applicable (handle targets the meerkat DSL directly, not a CompositionDispatcher seam)
59        self.dsl.apply_input(
60            mm_dsl::MeerkatMachineInput::McpServerFailed {
61                server_id: mm_dsl::McpServerId::from(server_id.to_string()),
62                error: error.to_string(),
63            },
64            "McpServerLifecycleHandle::apply_failed",
65        )
66    }
67
68    fn apply_disconnected(&self, server_id: &str) -> Result<(), DslTransitionError> {
69        // intra-machine: no route; dispatcher not applicable (handle targets the meerkat DSL directly, not a CompositionDispatcher seam)
70        self.dsl.apply_input(
71            mm_dsl::MeerkatMachineInput::McpServerDisconnected {
72                server_id: mm_dsl::McpServerId::from(server_id.to_string()),
73            },
74            "McpServerLifecycleHandle::apply_disconnected",
75        )
76    }
77
78    fn apply_reload(&self, server_id: &str) -> Result<(), DslTransitionError> {
79        // intra-machine: no route; dispatcher not applicable (handle targets the meerkat DSL directly, not a CompositionDispatcher seam)
80        self.dsl.apply_input(
81            mm_dsl::MeerkatMachineInput::McpServerReload {
82                server_id: mm_dsl::McpServerId::from(server_id.to_string()),
83            },
84            "McpServerLifecycleHandle::apply_reload",
85        )
86    }
87
88    fn pending_server_ids(&self) -> BTreeSet<String> {
89        self.dsl
90            .snapshot_state()
91            .mcp_server_states
92            .into_iter()
93            .filter_map(|(server_id, state)| {
94                if matches!(state, mm_dsl::McpServerState::PendingConnect) {
95                    Some(server_id.0)
96                } else {
97                    None
98                }
99            })
100            .collect()
101    }
102}