131 lines
4.3 KiB
Rust
131 lines
4.3 KiB
Rust
use serde_json::{Value, json};
|
|
|
|
use crate::engine::{EngineError, EngineResult};
|
|
|
|
use super::McpContext;
|
|
|
|
pub const MCP_PROTOCOL_VERSION: &str = "2025-06-18";
|
|
|
|
pub fn handle_rpc(context: &McpContext, request: &Value) -> Option<Value> {
|
|
let id = request.get("id").cloned();
|
|
let Some(method) = request.get("method").and_then(Value::as_str) else {
|
|
return Some(error(id.unwrap_or(Value::Null), -32600, "Invalid Request"));
|
|
};
|
|
if request.get("jsonrpc").and_then(Value::as_str) != Some("2.0") {
|
|
return Some(error(id.unwrap_or(Value::Null), -32600, "Invalid Request"));
|
|
}
|
|
let id = id?;
|
|
let params = request.get("params").cloned().unwrap_or_else(|| json!({}));
|
|
let result = match method {
|
|
"initialize" => Ok(json!({
|
|
"protocolVersion": negotiated_version(¶ms),
|
|
"capabilities": {
|
|
"tools": {"listChanged": false},
|
|
"resources": {"subscribe": false, "listChanged": false}
|
|
},
|
|
"serverInfo": {
|
|
"name": "Blogging Desktop Server",
|
|
"version": env!("CARGO_PKG_VERSION")
|
|
}
|
|
})),
|
|
"ping" => Ok(json!({})),
|
|
"tools/list" => Ok(json!({"tools": context.list_tools()})),
|
|
"tools/call" => call_tool(context, ¶ms),
|
|
"resources/list" => Ok(json!({"resources": context.list_resources()})),
|
|
"resources/templates/list" => Ok(json!({
|
|
"resourceTemplates": context.list_resource_templates()
|
|
})),
|
|
"resources/read" => read_resource(context, ¶ms),
|
|
_ => return Some(error(id, -32601, "Method not found")),
|
|
};
|
|
Some(match result {
|
|
Ok(result) => success(id, result),
|
|
Err(EngineError::NotFound(message)) => error(id, -32004, &message),
|
|
Err(EngineError::Validation(message) | EngineError::Conflict(message)) => {
|
|
error(id, -32602, &message)
|
|
}
|
|
Err(error_value) => error(id, -32000, &error_value.to_string()),
|
|
})
|
|
}
|
|
|
|
fn negotiated_version(params: &Value) -> &str {
|
|
match params.get("protocolVersion").and_then(Value::as_str) {
|
|
Some("2025-03-26") => "2025-03-26",
|
|
Some("2025-06-18") => "2025-06-18",
|
|
_ => MCP_PROTOCOL_VERSION,
|
|
}
|
|
}
|
|
|
|
fn call_tool(context: &McpContext, params: &Value) -> EngineResult<Value> {
|
|
let name = params
|
|
.get("name")
|
|
.and_then(Value::as_str)
|
|
.filter(|name| !name.is_empty())
|
|
.ok_or_else(|| EngineError::Validation("tool name is required".into()))?;
|
|
let arguments = params
|
|
.get("arguments")
|
|
.cloned()
|
|
.unwrap_or_else(|| json!({}));
|
|
let result = context.call_tool(name, arguments)?;
|
|
Ok(json!({
|
|
"content": [{
|
|
"type": "text",
|
|
"text": serde_json::to_string(&result)?
|
|
}],
|
|
"structuredContent": result,
|
|
"isError": false
|
|
}))
|
|
}
|
|
|
|
fn read_resource(context: &McpContext, params: &Value) -> EngineResult<Value> {
|
|
let uri = params
|
|
.get("uri")
|
|
.and_then(Value::as_str)
|
|
.filter(|uri| !uri.is_empty())
|
|
.ok_or_else(|| EngineError::Validation("resource URI is required".into()))?;
|
|
let content = context.read_resource(uri)?;
|
|
let content = if let Some(blob) = content.blob {
|
|
json!({
|
|
"uri": content.uri,
|
|
"mimeType": content.mime_type,
|
|
"blob": blob
|
|
})
|
|
} else {
|
|
json!({
|
|
"uri": content.uri,
|
|
"mimeType": content.mime_type,
|
|
"text": content.text.unwrap_or_default()
|
|
})
|
|
};
|
|
Ok(json!({"contents": [content]}))
|
|
}
|
|
|
|
fn success(id: Value, result: Value) -> Value {
|
|
json!({"jsonrpc":"2.0","id":id,"result":result})
|
|
}
|
|
|
|
pub(crate) fn error(id: Value, code: i64, message: &str) -> Value {
|
|
json!({"jsonrpc":"2.0","id":id,"error":{"code":code,"message":message}})
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn notifications_have_no_response_and_invalid_requests_are_rejected() {
|
|
let context = McpContext::new("missing.sqlite".into());
|
|
assert!(
|
|
handle_rpc(
|
|
&context,
|
|
&json!({"jsonrpc":"2.0","method":"notifications/initialized"})
|
|
)
|
|
.is_none()
|
|
);
|
|
assert_eq!(
|
|
handle_rpc(&context, &json!({"jsonrpc":"2.0","id":1})).unwrap()["error"]["code"],
|
|
-32600
|
|
);
|
|
}
|
|
}
|