use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::io::{self, BufRead, Write};
use crate::client::ConfluenceClient;
use crate::tools;
#[derive(Debug, Deserialize)]
pub struct JsonRpcRequest {
#[allow(dead_code)]
pub jsonrpc: String,
pub id: Option<Value>,
pub method: String,
#[serde(default)]
pub params: Option<Value>,
}
#[derive(Debug, Serialize)]
pub struct JsonRpcResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
pub result: Value,
}
#[derive(Debug, Serialize)]
pub struct JsonRpcError {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
pub error: RpcError,
}
#[derive(Debug, Serialize)]
pub struct RpcError {
pub code: i64,
pub message: String,
}
#[derive(Debug, Serialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: Value,
}
#[derive(Debug, Serialize)]
pub struct TextContent {
#[serde(rename = "type")]
pub content_type: String,
pub text: String,
}
#[derive(Debug, Serialize)]
pub struct CallToolResult {
pub content: Vec<TextContent>,
}
impl CallToolResult {
pub fn text(msg: impl Into<String>) -> Self {
CallToolResult {
content: vec![TextContent {
content_type: "text".to_string(),
text: msg.into(),
}],
}
}
}
fn send_response(stdout: &mut impl Write, id: Option<Value>, result: Value) -> io::Result<()> {
let resp = JsonRpcResponse {
jsonrpc: "2.0".to_string(),
id,
result,
};
let line = serde_json::to_string(&resp).unwrap();
writeln!(stdout, "{line}")?;
stdout.flush()
}
fn send_error(
stdout: &mut impl Write,
id: Option<Value>,
code: i64,
message: &str,
) -> io::Result<()> {
let resp = JsonRpcError {
jsonrpc: "2.0".to_string(),
id,
error: RpcError {
code,
message: message.to_string(),
},
};
let line = serde_json::to_string(&resp).unwrap();
writeln!(stdout, "{line}")?;
stdout.flush()
}
pub async fn run(client: ConfluenceClient) -> io::Result<()> {
let stdin = io::stdin();
let mut stdout = io::stdout().lock();
let reader = stdin.lock();
for line_result in reader.lines() {
let line = match line_result {
Ok(l) => l,
Err(_) => break,
};
let line = line.trim();
if line.is_empty() {
continue;
}
let req: JsonRpcRequest = match serde_json::from_str(line) {
Ok(r) => r,
Err(e) => {
eprintln!("[MCP] Invalid JSON-RPC: {e}");
send_error(&mut stdout, None, -32700, &format!("Parse error: {e}"))?;
continue;
}
};
let id = req.id.clone();
match req.method.as_str() {
"initialize" => {
let result = serde_json::json!({
"protocolVersion": "2024-11-05",
"capabilities": {
"tools": {}
},
"serverInfo": {
"name": "confluence-mcp-server",
"version": "1.0.0"
}
});
send_response(&mut stdout, id, result)?;
}
"notifications/initialized" => {
}
"tools/list" => {
let tool_defs = tools::list_tools();
let result = serde_json::json!({
"tools": tool_defs
});
send_response(&mut stdout, id, result)?;
}
"tools/call" => {
let params = req.params.unwrap_or(Value::Null);
let tool_name = params.get("name").and_then(|v| v.as_str()).unwrap_or("");
let arguments = params
.get("arguments")
.cloned()
.unwrap_or(Value::Object(serde_json::Map::new()));
let call_result = tools::call_tool(&client, tool_name, &arguments).await;
let result = serde_json::to_value(call_result).unwrap();
send_response(&mut stdout, id, result)?;
}
_ => {
if id.is_some() {
send_error(
&mut stdout,
id,
-32601,
&format!("Method not found: {}", req.method),
)?;
}
}
}
}
Ok(())
}