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 { TOOL_SPECS.iter().copied().find(|spec| spec.name == name) } pub(crate) fn tool_definitions() -> Vec { 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, } }