Skip to main content

component_shape_mcp/
arguments.rs

1use super::*;
2
3/// Typed MCP tool call payload passed to registered handlers.
4///
5/// The shared server normalizes protocol-level arguments into this object
6/// before dispatch. Domain integrations can then decode fields without
7/// repeatedly accepting arbitrary JSON values at every handler boundary.
8#[derive(Clone, Debug, Default, Eq, PartialEq)]
9pub struct McpToolCall {
10    arguments: McpToolArguments,
11}
12
13impl McpToolCall {
14    /// Creates a tool call from normalized MCP arguments.
15    pub fn new(arguments: McpToolArguments) -> Self {
16        Self { arguments }
17    }
18
19    /// Creates a tool call with no arguments.
20    pub fn empty() -> Self {
21        Self::default()
22    }
23
24    /// Converts protocol arguments into a normalized tool call.
25    ///
26    /// # Errors
27    ///
28    /// Returns [`McpToolError::ArgumentsMustBeObject`] when `arguments` is
29    /// present but is not a JSON object.
30    pub fn from_value(arguments: Option<Value>) -> Result<Self, McpToolError> {
31        match arguments {
32            None => Ok(Self::empty()),
33            Some(Value::Object(arguments)) => Ok(Self::new(arguments)),
34            Some(_) => Err(McpToolError::ArgumentsMustBeObject),
35        }
36    }
37
38    /// Returns the normalized argument object.
39    pub fn arguments(&self) -> &McpToolArguments {
40        &self.arguments
41    }
42
43    /// Converts this call into an owning argument decoder.
44    pub fn into_arguments(self) -> McpArguments {
45        McpArguments::new(self.arguments)
46    }
47}
48
49/// Owning decoder for MCP tool argument objects.
50///
51/// Generated form and table integrations consume arguments through this type
52/// instead of open-coding JSON map removal. Manual tool integrations can use
53/// the same helpers and call [`McpArguments::finish`] when every expected
54/// argument has been consumed.
55#[derive(Clone, Debug, Default, Eq, PartialEq)]
56pub struct McpArguments {
57    arguments: McpToolArguments,
58}
59
60impl McpArguments {
61    /// Creates an owning decoder from normalized MCP arguments.
62    pub fn new(arguments: McpToolArguments) -> Self {
63        Self { arguments }
64    }
65
66    /// Returns whether any arguments remain unconsumed.
67    pub fn is_empty(&self) -> bool {
68        self.arguments.is_empty()
69    }
70
71    /// Returns the remaining raw argument object.
72    pub fn as_inner(&self) -> &McpToolArguments {
73        &self.arguments
74    }
75
76    /// Returns the remaining raw argument object.
77    pub fn into_inner(self) -> McpToolArguments {
78        self.arguments
79    }
80
81    /// Removes and returns one raw argument by wire field name.
82    pub fn take_raw(&mut self, field: &str) -> Option<Value> {
83        self.arguments.remove(field)
84    }
85
86    /// Removes and returns one raw argument by canonical field name or alias.
87    ///
88    /// # Errors
89    ///
90    /// Returns [`McpToolError::DuplicateField`] when both the canonical field
91    /// name and an alias are present, or multiple aliases are present.
92    pub fn take_raw_one_of(
93        &mut self,
94        field: &str,
95        aliases: &[&str],
96    ) -> Result<Option<Value>, McpToolError> {
97        let mut found = self.take_raw(field);
98        for alias in aliases {
99            if let Some(alias_value) = self.take_raw(alias) {
100                if found.is_some() {
101                    return Err(McpToolError::DuplicateField {
102                        field: field.to_string(),
103                    });
104                }
105                found = Some(alias_value);
106            }
107        }
108        Ok(found)
109    }
110
111    /// Removes, requires, and decodes one typed argument by field name.
112    ///
113    /// # Errors
114    ///
115    /// Returns [`McpToolError`] when the field is missing or when `T` rejects the
116    /// raw JSON value.
117    pub fn take_required_tool_value<T>(
118        &mut self,
119        field: impl Into<String>,
120    ) -> Result<T, McpToolError>
121    where
122        T: McpToolValue,
123    {
124        let field = field.into();
125        let value = self
126            .take_raw(&field)
127            .ok_or_else(|| McpToolError::missing_field(field.clone()))?;
128        T::from_tool_value(&field, value)
129    }
130
131    /// Removes, requires, and decodes one typed argument by field name or alias.
132    ///
133    /// # Errors
134    ///
135    /// Returns [`McpToolError`] when no accepted field name is present, duplicate
136    /// field spellings are present, or `T` rejects the raw JSON value.
137    pub fn take_required_tool_value_from<T>(
138        &mut self,
139        field: &'static str,
140        aliases: &'static [&'static str],
141    ) -> Result<T, McpToolError>
142    where
143        T: McpToolValue,
144    {
145        let value = self
146            .take_raw_one_of(field, aliases)?
147            .ok_or_else(|| McpToolError::missing_field(field))?;
148        T::from_tool_value(field, value)
149    }
150
151    /// Removes and decodes an optional typed argument by field name.
152    ///
153    /// # Errors
154    ///
155    /// Returns [`McpToolError`] when `T` rejects the raw JSON value.
156    pub fn take_present_tool_value<T>(
157        &mut self,
158        field: impl Into<String>,
159    ) -> Result<Option<T>, McpToolError>
160    where
161        T: McpToolValue,
162    {
163        let field = field.into();
164        self.take_raw(&field)
165            .map(|value| T::from_tool_value(&field, value))
166            .transpose()
167    }
168
169    /// Removes and decodes an optional typed argument by field name or alias.
170    ///
171    /// # Errors
172    ///
173    /// Returns [`McpToolError`] when duplicate field spellings are present or
174    /// `T` rejects the raw JSON value.
175    pub fn take_present_tool_value_from<T>(
176        &mut self,
177        field: &'static str,
178        aliases: &'static [&'static str],
179    ) -> Result<Option<T>, McpToolError>
180    where
181        T: McpToolValue,
182    {
183        self.take_raw_one_of(field, aliases)?
184            .map(|value| T::from_tool_value(field, value))
185            .transpose()
186    }
187
188    /// Verifies that no unrecognized arguments remain.
189    ///
190    /// # Errors
191    ///
192    /// Returns [`McpToolError::UnknownField`] when any raw arguments remain.
193    pub fn finish(self) -> Result<(), McpToolError> {
194        reject_unknown_arguments(self.arguments)
195    }
196}
197
198impl From<McpToolCall> for McpArguments {
199    fn from(call: McpToolCall) -> Self {
200        call.into_arguments()
201    }
202}
203
204impl From<McpToolArguments> for McpArguments {
205    fn from(arguments: McpToolArguments) -> Self {
206        Self::new(arguments)
207    }
208}