use rmcp::model::{CallToolResult, ContentBlock, ErrorCode, ErrorData, JsonObject};
use serde_json::{Map, Value};
use crate::constants::{
AGENT_ID_PARAM, GET_MORE_TOOLS_NAME, GET_MORE_TOOLS_RESULT_TEXT, MCP_SESSION_KEY,
SESSION_ID_PARAM, report_missing_log_line,
};
use crate::inject::{CONTEXT_PARAM, ToolEntry};
use crate::wire;
pub(crate) mod run;
#[cfg(test)]
mod tests;
pub(crate) const PLATFORM: &str = "rust";
const UNKNOWN_ERROR: &str = "Unknown error";
const CANCELLED_MESSAGE: &str = "tool call cancelled before it completed";
const CANCELLED_TYPE: &str = "cancelled";
pub(crate) fn strip_arguments(
arguments: &JsonObject,
entry: Option<&ToolEntry>,
tool_name: &str,
) -> JsonObject {
let names: &[&str] = match entry {
Some(entry) => &entry.injected,
None if tool_name == GET_MORE_TOOLS_NAME => &[SESSION_ID_PARAM, AGENT_ID_PARAM],
None => &[SESSION_ID_PARAM, AGENT_ID_PARAM, CONTEXT_PARAM],
};
arguments
.iter()
.filter(|(key, _)| !names.contains(&key.as_str()))
.map(|(key, value)| (key.clone(), value.clone()))
.collect()
}
pub(crate) fn prepend_mint_back(result: &mut CallToolResult, text: String) {
result.content.insert(0, ContentBlock::text(text));
}
pub(crate) fn mirror_mint_back(structured: &mut Option<Value>, mint: Map<String, Value>) {
let Some(Value::Object(body)) = structured else {
return;
};
if body.contains_key(MCP_SESSION_KEY) {
return;
}
let mut spread = Map::with_capacity(body.len() + 1);
spread.insert(MCP_SESSION_KEY.to_owned(), Value::Object(mint));
spread.append(body);
let order: Vec<String> = wire::own_entries(&spread)
.map(|(key, _)| key.clone())
.collect();
for key in order {
if let Some(value) = spread.remove(&key) {
body.insert(key, value);
}
}
}
pub(crate) fn result_error(result: &CallToolResult) -> Map<String, Value> {
let texts: Vec<&str> = result
.content
.iter()
.filter_map(|block| match block {
ContentBlock::Text(text) => Some(text.text.as_str()),
_ => None,
})
.collect();
let joined = texts.join(" ");
let message = match wire::trim(&joined) {
"" => UNKNOWN_ERROR,
trimmed => trimmed,
};
let mut error = Map::new();
error.insert("message".to_owned(), Value::String(message.to_owned()));
error.insert("platform".to_owned(), Value::String(PLATFORM.to_owned()));
error
}
pub(crate) fn error_data_error(error: &ErrorData) -> Map<String, Value> {
let mut shape = Map::new();
shape.insert(
"message".to_owned(),
Value::String(error.message.to_string()),
);
shape.insert("type".to_owned(), Value::String(code_name(error.code)));
shape.insert("platform".to_owned(), Value::String(PLATFORM.to_owned()));
shape
}
pub(crate) fn cancelled_error() -> Map<String, Value> {
let mut shape = Map::new();
shape.insert(
"message".to_owned(),
Value::String(CANCELLED_MESSAGE.to_owned()),
);
shape.insert("type".to_owned(), Value::String(CANCELLED_TYPE.to_owned()));
shape.insert("platform".to_owned(), Value::String(PLATFORM.to_owned()));
shape
}
pub(crate) fn code_name(code: ErrorCode) -> String {
match code {
ErrorCode::PARSE_ERROR => "ParseError".to_owned(),
ErrorCode::INVALID_REQUEST => "InvalidRequest".to_owned(),
ErrorCode::METHOD_NOT_FOUND => "MethodNotFound".to_owned(),
ErrorCode::INVALID_PARAMS => "InvalidParams".to_owned(),
ErrorCode::INTERNAL_ERROR => "InternalError".to_owned(),
ErrorCode::RESOURCE_NOT_FOUND => "ResourceNotFound".to_owned(),
ErrorCode(other) => format!("JsonRpcError({other})"),
}
}
pub(crate) fn report_missing(context: Option<&Value>) -> (CallToolResult, String) {
let length = match context {
Some(Value::String(text)) => wire::utf16_len(text),
Some(Value::Array(items)) => items.len(),
_ => 0,
};
let mut result = CallToolResult::success(vec![ContentBlock::text(GET_MORE_TOOLS_RESULT_TEXT)]);
result.is_error = None;
(result, report_missing_log_line(length))
}