use std::collections::HashMap;
use anyhow::{anyhow, Result};
use serde::{Deserialize, Serialize};
static REQUEST_ID: std::sync::atomic::AtomicUsize = std::sync::atomic::AtomicUsize::new(1);
pub(crate) fn next_request_id() -> usize {
REQUEST_ID.fetch_add(1, std::sync::atomic::Ordering::Relaxed)
}
pub type McpServerConfig = McpServer;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpServer {
pub name: String,
pub command: String,
pub args: Vec<String>,
pub env: HashMap<String, String>,
pub enabled: bool,
}
impl McpServer {
pub fn new(name: &str, command: &str) -> Self {
Self {
name: name.to_string(),
command: command.to_string(),
args: Vec::new(),
env: HashMap::new(),
enabled: true,
}
}
pub fn with_args(mut self, args: Vec<String>) -> Self {
self.args = args;
self
}
pub fn with_env(mut self, key: &str, value: &str) -> Self {
self.env.insert(key.to_string(), value.to_string());
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
pub jsonrpc: String,
pub id: serde_json::Value,
pub method: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub params: Option<serde_json::Value>,
}
impl McpRequest {
pub fn new(method: &str) -> Self {
Self::with_id(next_request_id(), method)
}
pub fn with_id(id: usize, method: &str) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id: serde_json::json!(id),
method: method.to_string(),
params: None,
}
}
pub fn with_params(mut self, params: serde_json::Value) -> Self {
self.params = Some(params);
self
}
pub fn to_jsonl(&self) -> Result<Vec<u8>> {
let mut buf = serde_json::to_vec(self)?;
buf.push(b'\n');
Ok(buf)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
pub jsonrpc: String,
pub id: serde_json::Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<serde_json::Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
pub code: i32,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<serde_json::Value>,
}
impl std::fmt::Display for McpError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let error_type = match self.code {
-32700 => "parse error",
-32600 => "invalid request",
-32601 => "method not found",
-32602 => "invalid params",
-32603 => "internal error",
-32099..=-32000 => "server error",
_ => "unknown error",
};
write!(f, "{} (code {}): {}", error_type, self.code, self.message)
}
}
impl McpError {
pub fn new(code: i32, message: &str) -> Self {
Self {
code,
message: message.to_string(),
data: None,
}
}
pub fn parse_error() -> Self {
Self::new(-32700, "Parse error")
}
pub fn invalid_request(msg: &str) -> Self {
Self::new(-32600, msg)
}
pub fn method_not_found() -> Self {
Self::new(-32601, "Method not found")
}
pub fn invalid_params() -> Self {
Self::new(-32602, "Invalid params")
}
pub fn internal_error(msg: &str) -> Self {
Self::new(-32603, msg)
}
pub fn server_error(msg: &str) -> Self {
Self::new(-32000, msg)
}
}
impl McpResponse {
pub fn is_error(&self) -> bool {
self.error.is_some()
}
pub fn into_result(self) -> Result<serde_json::Value> {
if let Some(err) = self.error {
return Err(anyhow!("{err}"));
}
Ok(self.result.unwrap_or(serde_json::Value::Null))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct McpCapabilities {
pub tools: bool,
pub resources: bool,
pub prompts: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeParams {
pub protocol_version: String,
pub capabilities: McpCapabilities,
pub client_info: ClientInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ClientInfo {
pub name: String,
pub version: String,
}
impl Default for InitializeParams {
fn default() -> Self {
Self {
protocol_version: "2024-11-05".to_string(),
capabilities: McpCapabilities::default(),
client_info: ClientInfo {
name: "oxios".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
pub protocol_version: String,
pub capabilities: McpCapabilities,
pub server_info: ServerInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTool {
pub name: String,
pub description: String,
pub input_schema: serde_json::Value,
}
impl McpTool {
pub fn name(&self) -> &str {
&self.name
}
pub fn description(&self) -> &str {
&self.description
}
pub fn input_schema(&self) -> &serde_json::Value {
&self.input_schema
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolsResult {
pub tools: Vec<McpTool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolCallResult {
pub content: Vec<McpContentBlock>,
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum McpContentBlock {
#[serde(rename = "text")]
Text {
text: String,
},
#[serde(rename = "image")]
Image {
data: String,
mime_type: Option<String>,
},
#[serde(rename = "resource")]
Resource {
resource: MappedResource,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct MappedResource {
pub uri: String,
pub mime_type: Option<String>,
}