Skip to main content

origin_mcp_core/
tool.rs

1use crate::AiPermission;
2use async_trait::async_trait;
3use origin_domain::Result;
4use serde::{Deserialize, Serialize};
5use std::fmt::Debug;
6
7/// What a tool is, as the model sees it.
8///
9/// The description is the part a model actually acts on — it is public API for a
10/// reader that cannot ask follow-up questions. It belongs in review like any other
11/// interface, not appended as an afterthought.
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ToolDescriptor {
14    /// Namespaced, dot-separated: `projects.list`, `knowledge.search`.
15    pub name: String,
16    /// Short human-readable label for permission dialogs and logs.
17    pub title: String,
18    /// What the tool does, when to use it, and what it returns.
19    pub description: String,
20    /// What invoking this costs in terms of rights.
21    pub permission: AiPermission,
22    /// JSON Schema for the arguments.
23    pub input_schema: serde_json::Value,
24}
25
26impl ToolDescriptor {
27    pub fn new(
28        name: impl Into<String>,
29        title: impl Into<String>,
30        description: impl Into<String>,
31        permission: AiPermission,
32    ) -> Self {
33        Self {
34            name: name.into(),
35            title: title.into(),
36            description: description.into(),
37            permission,
38            input_schema: serde_json::json!({
39                "type": "object",
40                "properties": {},
41                "additionalProperties": false
42            }),
43        }
44    }
45
46    pub fn with_schema(mut self, input_schema: serde_json::Value) -> Self {
47        self.input_schema = input_schema;
48        self
49    }
50}
51
52/// What a tool returns.
53#[derive(Debug, Clone, Serialize, Deserialize)]
54pub struct ToolOutput {
55    /// Rendered for the model to read.
56    pub text: String,
57    /// The same answer as data, when the caller can use it.
58    pub structured: Option<serde_json::Value>,
59}
60
61impl ToolOutput {
62    pub fn text(text: impl Into<String>) -> Self {
63        Self {
64            text: text.into(),
65            structured: None,
66        }
67    }
68
69    pub fn with_structured(mut self, structured: serde_json::Value) -> Self {
70        self.structured = Some(structured);
71        self
72    }
73}
74
75/// One operation an external AI may invoke.
76///
77/// A tool wraps an *application service*, never a Tauri command: an operation that
78/// exists only as a command is not reachable from MCP, from a CLI or from a headless
79/// run. If writing a tool means duplicating logic, the logic is in the wrong place.
80#[async_trait]
81pub trait Tool: Debug + Send + Sync + 'static {
82    fn descriptor(&self) -> ToolDescriptor;
83
84    /// Arguments arrive as JSON, validated by the caller against nothing in
85    /// particular — check them.
86    async fn call(&self, arguments: serde_json::Value) -> Result<ToolOutput>;
87}