MCPcopy Create free account
hub / github.com/ScriptedAlchemy/tracedecay / validate_tool_args

Function validate_tool_args

src/tool_command/args.rs:237–324  ·  view source on GitHub ↗

One schema-driven validation pass over the *final* arguments object, shared by the `--args` and per-key paths. Turns the silent divergences — unknown keys forwarded and ignored, invalid enum values accepted, wrong JSON types reaching handlers — into corrective errors that state the fix. Schemas without `properties` are treated as opaque (no validation) so dynamic or profile-scoped tools cannot be

(def: &ToolDefinition, args: &Map<String, Value>)

Source from the content-addressed store, hash-verified

235/// Schemas without `properties` are treated as opaque (no validation) so
236/// dynamic or profile-scoped tools cannot be bricked by a stale walker.
237fn validate_tool_args(def: &ToolDefinition, args: &Map<String, Value>) -> Result<()> {
238 let Some(props) = def
239 .input_schema
240 .get("properties")
241 .and_then(Value::as_object)
242 .filter(|props| !props.is_empty())
243 else {
244 return Ok(());
245 };
246 let short = short_tool_name(&def.name);
247
248 let required = schema_required_keys(def);
249
250 for (key, value) in args {
251 let Some(schema) = props.get(key) else {
252 if DISPATCH_ROUTING_KEYS.contains(&key.as_str()) {
253 continue;
254 }
255 return Err(unknown_key_error(key, short, props, &required));
256 };
257
258 if value.is_null() && !required.contains(key) {
259 continue;
260 }
261
262 if let Some(allowed) = schema.get("enum").and_then(Value::as_array) {
263 if !allowed.iter().any(|candidate| candidate == value) {
264 let allowed: Vec<String> = allowed
265 .iter()
266 .map(|v| match v {
267 Value::String(s) => s.clone(),
268 other => other.to_string(),
269 })
270 .collect();
271 let displayed = value
272 .as_str()
273 .map(str::to_string)
274 .unwrap_or_else(|| value.to_string());
275 return Err(TraceDecayError::Config {
276 message: format!(
277 "--{}: `{displayed}` is not one of: {}",
278 key.replace('_', "-"),
279 allowed.join(", ")
280 ),
281 });
282 }
283 }
284
285 if let Some(expected) = schema.get("type").and_then(Value::as_str) {
286 if !value_matches_type(value, expected) {
287 let flag = key.replace('_', "-");
288 let hint = if matches!(expected, "array" | "object") {
289 format!(
290 " Pass JSON (e.g. --{flag} '<json>'), {}",
291 heredoc_hint(short)
292 )
293 } else {
294 String::new()

Callers 1

Calls 9

short_tool_nameFunction · 0.85
schema_required_keysFunction · 0.85
unknown_key_errorFunction · 0.85
value_matches_typeFunction · 0.85
check_array_elementsFunction · 0.85
collectMethod · 0.80
is_emptyMethod · 0.45
containsMethod · 0.45
as_strMethod · 0.45

Tested by

no test coverage detected