use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::io::{BufRead, BufReader, Write};
use crate::error::{EngramError, Result};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpRequest {
pub jsonrpc: String,
pub id: Option<Value>,
pub method: String,
#[serde(default)]
pub params: Value,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpResponse {
pub jsonrpc: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub id: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub result: Option<Value>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<McpError>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpError {
pub code: i64,
pub message: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub data: Option<Value>,
}
impl McpResponse {
pub fn success(id: Option<Value>, result: Value) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: Some(result),
error: None,
}
}
pub fn error(id: Option<Value>, code: i64, message: String) -> Self {
Self {
jsonrpc: "2.0".to_string(),
id,
result: None,
error: Some(McpError {
code,
message,
data: None,
}),
}
}
pub fn from_error(id: Option<Value>, err: EngramError) -> Self {
Self::error(id, err.code(), err.to_string())
}
}
pub struct McpServer<H>
where
H: McpHandler,
{
handler: H,
}
pub trait McpHandler: Send + Sync {
fn handle_request(&self, request: McpRequest) -> McpResponse;
}
impl<T: McpHandler> McpHandler for std::sync::Arc<T> {
fn handle_request(&self, request: McpRequest) -> McpResponse {
(**self).handle_request(request)
}
}
impl<H: McpHandler> McpServer<H> {
pub fn new(handler: H) -> Self {
Self { handler }
}
pub fn run(&self) -> Result<()> {
let stdin = std::io::stdin();
let stdout = std::io::stdout();
let mut reader = BufReader::new(stdin.lock());
let mut writer = stdout.lock();
let mut line = String::new();
loop {
line.clear();
match reader.read_line(&mut line) {
Ok(0) => break, Ok(_) => {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
match serde_json::from_str::<McpRequest>(trimmed) {
Ok(request) => {
let is_notification = request.id.is_none();
let response = self.handler.handle_request(request);
if is_notification {
continue;
}
let response_json = serde_json::to_string(&response)?;
writeln!(writer, "{}", response_json)?;
writer.flush()?;
}
Err(e) => {
let response =
McpResponse::error(None, -32700, format!("Parse error: {}", e));
let response_json = serde_json::to_string(&response)?;
writeln!(writer, "{}", response_json)?;
writer.flush()?;
}
}
}
Err(e) => {
tracing::error!("Error reading stdin: {}", e);
break;
}
}
}
Ok(())
}
}
pub mod methods {
pub const INITIALIZE: &str = "initialize";
pub const INITIALIZED: &str = "notifications/initialized";
pub const LIST_TOOLS: &str = "tools/list";
pub const CALL_TOOL: &str = "tools/call";
pub const LIST_RESOURCES: &str = "resources/list";
pub const READ_RESOURCE: &str = "resources/read";
pub const LIST_PROMPTS: &str = "prompts/list";
pub const GET_PROMPT: &str = "prompts/get";
}
pub const MCP_PROTOCOL_VERSION: &str = "2025-11-25";
pub const MCP_PROTOCOL_VERSION_LEGACY: &str = "2024-11-05";
#[derive(Debug, Clone, Serialize, Deserialize, Default)]
pub struct ToolAnnotations {
#[serde(rename = "readOnlyHint", skip_serializing_if = "Option::is_none")]
pub read_only_hint: Option<bool>,
#[serde(rename = "destructiveHint", skip_serializing_if = "Option::is_none")]
pub destructive_hint: Option<bool>,
#[serde(rename = "idempotentHint", skip_serializing_if = "Option::is_none")]
pub idempotent_hint: Option<bool>,
#[serde(rename = "openWorldHint", skip_serializing_if = "Option::is_none")]
pub open_world_hint: Option<bool>,
}
impl ToolAnnotations {
pub const fn read_only() -> Self {
Self {
read_only_hint: Some(true),
destructive_hint: None,
idempotent_hint: None,
open_world_hint: None,
}
}
pub const fn destructive() -> Self {
Self {
read_only_hint: None,
destructive_hint: Some(true),
idempotent_hint: None,
open_world_hint: None,
}
}
pub const fn idempotent() -> Self {
Self {
read_only_hint: None,
destructive_hint: None,
idempotent_hint: Some(true),
open_world_hint: None,
}
}
pub const fn mutating() -> Self {
Self {
read_only_hint: None,
destructive_hint: None,
idempotent_hint: None,
open_world_hint: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolDefinition {
pub name: String,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: Value,
#[serde(skip_serializing_if = "Option::is_none")]
pub annotations: Option<ToolAnnotations>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: ServerCapabilities,
#[serde(rename = "serverInfo")]
pub server_info: ServerInfo,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerCapabilities {
#[serde(skip_serializing_if = "Option::is_none")]
pub tools: Option<ToolsCapability>,
#[serde(skip_serializing_if = "Option::is_none")]
pub resources: Option<ResourceCapabilities>,
#[serde(skip_serializing_if = "Option::is_none")]
pub prompts: Option<PromptCapabilities>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolsCapability {
#[serde(rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceCapabilities {
pub subscribe: bool,
#[serde(rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptCapabilities {
#[serde(rename = "listChanged")]
pub list_changed: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ServerInfo {
pub name: String,
pub version: String,
}
impl Default for InitializeResult {
fn default() -> Self {
Self {
protocol_version: MCP_PROTOCOL_VERSION.to_string(),
capabilities: ServerCapabilities {
tools: Some(ToolsCapability {
list_changed: false,
}),
resources: Some(ResourceCapabilities {
subscribe: false,
list_changed: false,
}),
prompts: Some(PromptCapabilities {
list_changed: false,
}),
},
server_info: ServerInfo {
name: "engram".to_string(),
version: env!("CARGO_PKG_VERSION").to_string(),
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolCallResult {
pub content: Vec<ToolContent>,
#[serde(rename = "isError", skip_serializing_if = "Option::is_none")]
pub is_error: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type")]
pub enum ToolContent {
#[serde(rename = "text")]
Text { text: String },
#[serde(rename = "image")]
Image { data: String, mime_type: String },
#[serde(rename = "resource")]
Resource { resource: ResourceContent },
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceContent {
pub uri: String,
pub text: Option<String>,
pub blob: Option<String>,
#[serde(rename = "mimeType")]
pub mime_type: Option<String>,
}
impl ToolCallResult {
pub fn text(text: impl Into<String>) -> Self {
Self {
content: vec![ToolContent::Text { text: text.into() }],
is_error: None,
}
}
pub fn json(value: &impl Serialize) -> Self {
let text = serde_json::to_string_pretty(value).unwrap_or_default();
Self::text(text)
}
pub fn error(message: impl Into<String>) -> Self {
Self {
content: vec![ToolContent::Text {
text: message.into(),
}],
is_error: Some(true),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceDefinition {
pub uri: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ResourceTemplate {
#[serde(rename = "uriTemplate")]
pub uri_template: String,
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(rename = "mimeType", skip_serializing_if = "Option::is_none")]
pub mime_type: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptDefinition {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub arguments: Option<Vec<PromptArgument>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptArgument {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub description: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub required: Option<bool>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptMessage {
pub role: String,
pub content: PromptContent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptContent {
#[serde(rename = "type")]
pub content_type: String,
pub text: String,
}