1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
//! [`McpHost`] — engine boundary for the MCP (Model Context Protocol)
//! tool pool.
//!
//! M4 (Engine-struct strangler step) promotes the empty
//! [`TurnLoopMcpPool`](crate::engine::turn_loop::TurnLoopMcpPool) marker
//! into a named host trait that exposes the MCP **predicate / metadata
//! surface** the live `Engine` and the core turn loop actually use:
//! `is_mcp_tool`, `tool_is_parallel_safe`, `tool_is_read_only`,
//! `tool_approval_description`. All four methods carry **default
//! implementations** that delegate to the existing stateless free
//! functions in [`crate::engine::dispatch`], so `impl McpHost for
//! McpPool {}` in tui is a one-liner that produces zero behavior
//! change.
//!
//! ## Why not include `execute_tool` / `ensure` / `shutdown` here?
//!
//! - **`execute_tool`** lives on
//! [`McpPoolPort`](crate::engine::turn_loop::McpPoolPort) and is
//! implemented on `McpPoolHandle = Arc<Mutex<McpPool>>` (the locked
//! container, not the bare pool). The two traits have different
//! `self` shapes; merging would require reworking the
//! `mcp_pool_as_port` factory and rippling through every
//! `Option<Arc<AsyncMutex<Self::McpPool>>>` parameter in the turn
//! loop signature. M4 keeps them separate.
//! - **`ensure_pool` / `shutdown_all`** mutate engine state
//! (`self.mcp_pool = Some(...)`) and depend on
//! `EngineConfig.network_policy` + `session.mcp_config_path`. They
//! stay as inherent `Engine` methods (`tool_context.rs:112-124`,
//! `op_loop.rs:86-89`) and will move into the core `Engine` struct
//! alongside the field in M7.
//!
//! ## Call-graph (R1)
//!
//! Method surface derived from direct calls to `McpPool::is_mcp_tool`
//! and the `mcp_tool_*` free functions across the Engine call tree:
//!
//! | Method | Call sites |
//! |------------------------------|------------------------------------------------------------------------------------------------------------------|
//! | `is_mcp_tool` | `tui::mcp.rs:1498` (inherent), `capacity_flow/interventions.rs:139,143`, `tool_execution/parallel.rs:15,33`, … |
//! | `tool_is_parallel_safe` | `capacity_flow/interventions.rs:144`, `tool_execution/parallel.rs:34`, `host_impl/mod.rs:427` |
//! | `tool_is_read_only` | `capacity_flow/replay.rs:35`, `host_impl/mod.rs:426,428` |
//! | `tool_approval_description` | `host_impl/mod.rs:429` |
//!
//! Existing call sites keep using the inherent
//! `McpPool::is_mcp_tool(name)` and the `mcp_tool_*` free fns
//! unchanged (M4 Q5A: zero call-site churn). The trait methods are
//! the parallel `&self` entry points future implementations (e.g.
//! MCP server capability negotiation) can override.
/// Engine-side MCP host.
///
/// Implemented by `crates/tui/src/core/engine/turn_loop/host_impl/mod.rs`
/// (`impl McpHost for McpPool {}`) using only the default method
/// bodies — the live `McpPool` type carries no extra state for
/// these predicates beyond what the free functions in
/// [`crate::engine::dispatch`] already encode.