swarm repositories / source
aboutsummaryrefslogtreecommitdiff
path: root/crates/phone-opus/src/mcp/catalog.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/phone-opus/src/mcp/catalog.rs')
-rw-r--r--crates/phone-opus/src/mcp/catalog.rs107
1 files changed, 107 insertions, 0 deletions
diff --git a/crates/phone-opus/src/mcp/catalog.rs b/crates/phone-opus/src/mcp/catalog.rs
new file mode 100644
index 0000000..a7e7cf6
--- /dev/null
+++ b/crates/phone-opus/src/mcp/catalog.rs
@@ -0,0 +1,107 @@
+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 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."
+ }
+ },
+ "required": ["prompt"]
+ })),
+ "health_snapshot" | "telemetry_snapshot" => with_common_presentation(json!({
+ "type": "object",
+ "properties": {}
+ })),
+ _ => Value::Null,
+ }
+}