systemprompt_api/services/proxy/audit/
jsonrpc.rs1use serde::Deserialize;
13use serde_json::Value;
14
15const TOOLS_CALL_METHOD: &str = "tools/call";
16
17#[derive(Deserialize)]
18struct RequestFrame {
19 #[serde(default)]
20 id: Option<Value>,
21 method: String,
22 #[serde(default)]
23 params: Option<ToolCallParams>,
24}
25
26#[derive(Deserialize)]
27struct ToolCallParams {
28 name: String,
29 #[serde(default)]
30 arguments: Option<Value>,
31}
32
33#[derive(Debug)]
34pub struct ToolCallInvocation {
35 pub id: Value,
36 pub tool_name: String,
37 pub arguments: Value,
38}
39
40pub fn parse_tool_call(body: &[u8]) -> Option<ToolCallInvocation> {
41 let frame: RequestFrame = serde_json::from_slice(body).ok()?;
42 if frame.method != TOOLS_CALL_METHOD {
43 return None;
44 }
45 let params = frame.params?;
46 Some(ToolCallInvocation {
47 id: frame.id.unwrap_or(Value::Null),
48 tool_name: params.name,
49 arguments: params.arguments.unwrap_or(Value::Null),
50 })
51}
52
53#[derive(Deserialize)]
54struct ResponseFrame {
55 #[serde(default)]
56 id: Option<Value>,
57 #[serde(default)]
58 result: Option<ToolCallResult>,
59 #[serde(default)]
60 error: Option<Value>,
61}
62
63#[derive(Deserialize)]
64struct ToolCallResult {
65 #[serde(default, rename = "isError")]
66 is_error: bool,
67 #[serde(default, rename = "structuredContent")]
68 structured_content: Option<Value>,
69 #[serde(default)]
70 content: Option<Value>,
71}
72
73#[derive(Debug)]
74pub struct ToolCallOutcome {
75 pub output: Option<Value>,
76 pub error_message: Option<String>,
77}
78
79pub fn parse_response_frame(data: &str, request_id: &Value) -> Option<ToolCallOutcome> {
80 let frame: ResponseFrame = serde_json::from_str(data).ok()?;
81 if frame.id.as_ref() != Some(request_id) {
82 return None;
83 }
84 if let Some(error) = frame.error {
85 return Some(ToolCallOutcome {
86 error_message: Some(error.to_string()),
87 output: Some(error),
88 });
89 }
90 let result = frame.result?;
91 let output = result.structured_content.or(result.content);
92 let error_message = result
93 .is_error
94 .then(|| "MCP tool call returned isError".to_owned());
95 Some(ToolCallOutcome {
96 output,
97 error_message,
98 })
99}
100
101pub fn extract_sse_data(frame: &str) -> Option<String> {
102 let mut data = String::new();
103 for line in frame.lines() {
104 if let Some(rest) = line.strip_prefix("data:") {
105 if !data.is_empty() {
106 data.push('\n');
107 }
108 data.push_str(rest.trim_start());
109 }
110 }
111 (!data.is_empty()).then_some(data)
112}