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}