Skip to main content

embacle_server/mcp/
server.rs

1// ABOUTME: MCP server core that routes JSON-RPC requests to protocol handlers and tools
2// ABOUTME: Implements initialize, tools/list, tools/call, and ping MCP methods
3//
4// SPDX-License-Identifier: Apache-2.0
5// Copyright (c) 2026 dravr.ai
6
7use serde_json::Value;
8use tracing::debug;
9
10use super::protocol::{
11    CallToolParams, InitializeParams, InitializeResult, JsonRpcRequest, JsonRpcResponse,
12    ServerCapabilities, ServerInfo, ToolsCapability, ToolsListResult, INTERNAL_ERROR,
13    INVALID_PARAMS, INVALID_REQUEST, METHOD_NOT_FOUND, PROTOCOL_VERSION, SERVER_NAME,
14    SERVER_VERSION,
15};
16use super::tools::ToolRegistry;
17use crate::state::SharedState;
18
19/// MCP server that dispatches JSON-RPC requests to the appropriate handler
20///
21/// Uses the same `SharedState` as the OpenAI-compatible endpoints.
22/// Provider routing is stateless — determined per-request via tool arguments.
23pub struct McpServer {
24    state: SharedState,
25    tools: ToolRegistry,
26}
27
28impl McpServer {
29    /// Create a server with the given shared state and tool registry
30    pub const fn new(state: SharedState, tools: ToolRegistry) -> Self {
31        Self { state, tools }
32    }
33
34    /// Route a JSON-RPC request to the appropriate MCP handler
35    ///
36    /// Returns `None` for notifications (requests without an id).
37    pub async fn handle_request(&self, request: JsonRpcRequest) -> Option<JsonRpcResponse> {
38        if request.jsonrpc != "2.0" {
39            return Some(JsonRpcResponse::error(
40                request.id,
41                INVALID_REQUEST,
42                format!("Unsupported JSON-RPC version: {}", request.jsonrpc),
43            ));
44        }
45
46        // Notifications have no id and expect no response
47        if request.id.is_none() {
48            debug!(method = %request.method, "Received notification, no response");
49            return None;
50        }
51
52        let response = match request.method.as_str() {
53            "initialize" => Self::handle_initialize(request.id, request.params),
54            "tools/list" => self.handle_tools_list(request.id),
55            "tools/call" => self.handle_tools_call(request.id, request.params).await,
56            "ping" => JsonRpcResponse::success(request.id, Value::Object(serde_json::Map::new())),
57            method => {
58                debug!(method, "Unknown MCP method");
59                JsonRpcResponse::error(
60                    request.id,
61                    METHOD_NOT_FOUND,
62                    format!("Method not found: {method}"),
63                )
64            }
65        };
66
67        Some(response)
68    }
69
70    /// Handle `initialize` — parse client info and return server capabilities
71    fn handle_initialize(id: Option<Value>, params: Option<Value>) -> JsonRpcResponse {
72        if let Some(params) = params {
73            if let Ok(init) = serde_json::from_value::<InitializeParams>(params) {
74                debug!(
75                    client = %init.client_info.name,
76                    version = ?init.client_info.version,
77                    protocol = %init.protocol_version,
78                    "MCP client connected"
79                );
80            }
81        }
82
83        let result = InitializeResult {
84            protocol_version: PROTOCOL_VERSION.to_owned(),
85            capabilities: ServerCapabilities {
86                tools: Some(ToolsCapability {}),
87            },
88            server_info: ServerInfo {
89                name: SERVER_NAME.to_owned(),
90                version: SERVER_VERSION.to_owned(),
91            },
92        };
93
94        match serde_json::to_value(result) {
95            Ok(val) => JsonRpcResponse::success(id, val),
96            Err(e) => {
97                JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
98            }
99        }
100    }
101
102    /// Handle `tools/list` — return all registered tool definitions
103    fn handle_tools_list(&self, id: Option<Value>) -> JsonRpcResponse {
104        let result = ToolsListResult {
105            tools: self.tools.list_definitions(),
106        };
107
108        match serde_json::to_value(result) {
109            Ok(val) => JsonRpcResponse::success(id, val),
110            Err(e) => {
111                JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
112            }
113        }
114    }
115
116    /// Handle `tools/call` — dispatch to the named tool handler
117    async fn handle_tools_call(&self, id: Option<Value>, params: Option<Value>) -> JsonRpcResponse {
118        let call_params: CallToolParams = match params {
119            Some(p) => match serde_json::from_value(p) {
120                Ok(cp) => cp,
121                Err(e) => {
122                    return JsonRpcResponse::error(
123                        id,
124                        INVALID_PARAMS,
125                        format!("Invalid params: {e}"),
126                    );
127                }
128            },
129            None => {
130                return JsonRpcResponse::error(
131                    id,
132                    INVALID_PARAMS,
133                    "Missing params for tools/call".to_owned(),
134                );
135            }
136        };
137
138        let arguments = call_params
139            .arguments
140            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
141
142        let result = self
143            .tools
144            .execute(&call_params.name, &self.state, arguments)
145            .await;
146
147        match serde_json::to_value(result) {
148            Ok(val) => JsonRpcResponse::success(id, val),
149            Err(e) => JsonRpcResponse::error(
150                id,
151                INTERNAL_ERROR,
152                format!("Result serialization error: {e}"),
153            ),
154        }
155    }
156}