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
|
use libmcp::ReplayContract;
use serde_json::{Value, json};
use crate::mcp::output::with_common_presentation;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) enum DispatchTarget {
Host,
Worker,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(crate) struct ToolSpec {
pub(crate) name: &'static str,
pub(crate) description: &'static str,
pub(crate) dispatch: DispatchTarget,
pub(crate) replay: ReplayContract,
}
impl ToolSpec {
fn annotation_json(self) -> Value {
json!({
"title": self.name,
"readOnlyHint": true,
"destructiveHint": false,
"phoneOpus": {
"dispatch": match self.dispatch {
DispatchTarget::Host => "host",
DispatchTarget::Worker => "worker",
},
"replayContract": match self.replay {
ReplayContract::Convergent => "convergent",
ReplayContract::ProbeRequired => "probe_required",
ReplayContract::NeverReplay => "never_replay",
},
}
})
}
}
const TOOL_SPECS: &[ToolSpec] = &[
ToolSpec {
name: "consult",
description: "Run a blocking consult against the system Claude Code install using a read-only built-in toolset, optionally resume a prior Claude session by session_id, and return the response.",
dispatch: DispatchTarget::Worker,
replay: ReplayContract::NeverReplay,
},
ToolSpec {
name: "health_snapshot",
description: "Read host lifecycle, worker generation, rollout state, and latest fault. Defaults to render=porcelain; use render=json for structured output.",
dispatch: DispatchTarget::Host,
replay: ReplayContract::Convergent,
},
ToolSpec {
name: "telemetry_snapshot",
description: "Read aggregate request and recovery telemetry for this session. Defaults to render=porcelain; use render=json for structured output.",
dispatch: DispatchTarget::Host,
replay: ReplayContract::Convergent,
},
];
pub(crate) fn tool_spec(name: &str) -> Option<ToolSpec> {
TOOL_SPECS.iter().copied().find(|spec| spec.name == name)
}
pub(crate) fn tool_definitions() -> Vec<Value> {
TOOL_SPECS
.iter()
.map(|spec| {
json!({
"name": spec.name,
"description": spec.description,
"inputSchema": tool_schema(spec.name),
"annotations": spec.annotation_json(),
})
})
.collect()
}
fn tool_schema(name: &str) -> Value {
match name {
"consult" => with_common_presentation(json!({
"type": "object",
"properties": {
"prompt": {
"type": "string",
"description": "Prompt to send to Claude Code."
},
"cwd": {
"type": "string",
"description": "Optional working directory for the Claude Code session. Relative paths resolve against the MCP host working directory."
},
"max_turns": {
"type": "integer",
"minimum": 1,
"description": "Optional maximum number of Claude agent turns before stopping."
},
"session_id": {
"type": "string",
"description": "Optional Claude session handle returned by a previous consult call. When set, phone_opus resumes that conversation instead of starting a fresh one."
}
},
"required": ["prompt"]
})),
"health_snapshot" | "telemetry_snapshot" => with_common_presentation(json!({
"type": "object",
"properties": {}
})),
_ => Value::Null,
}
}
|