Skip to main content

systemprompt_api/services/proxy/engine/
fixed_arguments.rs

1//! Instance-fixed tool arguments for external MCP servers.
2//!
3//! A `tools/call` bound for an external server is rewritten so that every
4//! argument the deployment fixes for that tool (`tools.<name>.arguments` in
5//! the server's yaml) carries the configured value, whatever the client sent.
6//! It runs before governance and audit, so the call that is judged and
7//! recorded is the call that reaches the upstream.
8//!
9//! Copyright (c) systemprompt.io — Business Source License 1.1.
10//! See <https://systemprompt.io> for licensing details.
11
12use std::collections::HashMap;
13use std::hash::BuildHasher;
14
15use systemprompt_models::mcp::deployment::ToolMetadata;
16
17// Why: anything that is not a `tools/call` with fixed arguments, including
18// an unparsable body, is left untouched for the upstream to reject on its
19// own terms; the proxy never invents a JSON-RPC error of its own here.
20pub fn apply<S: BuildHasher>(tools: &HashMap<String, ToolMetadata, S>, body: &mut Vec<u8>) {
21    if tools.values().all(|tool| tool.arguments.is_empty()) || body.is_empty() {
22        return;
23    }
24    let Ok(mut value) = serde_json::from_slice::<serde_json::Value>(body) else {
25        return;
26    };
27    if value.get("method").and_then(serde_json::Value::as_str) != Some("tools/call") {
28        return;
29    }
30    let Some(params) = value
31        .get_mut("params")
32        .and_then(serde_json::Value::as_object_mut)
33    else {
34        return;
35    };
36    let Some(fixed) = params
37        .get("name")
38        .and_then(serde_json::Value::as_str)
39        .and_then(|name| tools.get(name))
40        .filter(|tool| !tool.arguments.is_empty())
41        .map(|tool| tool.arguments.clone())
42    else {
43        return;
44    };
45    let arguments = params
46        .entry("arguments")
47        .or_insert_with(|| serde_json::Value::Object(serde_json::Map::new()));
48    if !arguments.is_object() {
49        *arguments = serde_json::Value::Object(serde_json::Map::new());
50    }
51    if let Some(arguments) = arguments.as_object_mut() {
52        for (key, fixed_value) in fixed {
53            arguments.insert(key, fixed_value);
54        }
55    }
56    if let Ok(rewritten) = serde_json::to_vec(&value) {
57        *body = rewritten;
58    }
59}