Skip to main content

embacle_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 crate::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 crate::state::SharedState;
17use crate::tools::ToolRegistry;
18
19/// MCP server that dispatches JSON-RPC requests to the appropriate handler
20///
21/// Owns the shared state and tool registry. Transport layers feed parsed
22/// requests into `handle_request` and send the returned responses.
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        // Validate JSON-RPC protocol version
39        if request.jsonrpc != "2.0" {
40            return Some(JsonRpcResponse::error(
41                request.id,
42                INVALID_REQUEST,
43                format!("Unsupported JSON-RPC version: {}", request.jsonrpc),
44            ));
45        }
46
47        // Notifications have no id and expect no response
48        if request.id.is_none() {
49            debug!(method = %request.method, "Received notification, no response");
50            return None;
51        }
52
53        let response = match request.method.as_str() {
54            "initialize" => Self::handle_initialize(request.id, request.params),
55            "tools/list" => self.handle_tools_list(request.id),
56            "tools/call" => self.handle_tools_call(request.id, request.params).await,
57            "ping" => JsonRpcResponse::success(request.id, Value::Object(serde_json::Map::new())),
58            method => {
59                debug!(method, "Unknown MCP method");
60                JsonRpcResponse::error(
61                    request.id,
62                    METHOD_NOT_FOUND,
63                    format!("Method not found: {method}"),
64                )
65            }
66        };
67
68        Some(response)
69    }
70
71    /// Handle `initialize` — parse client info and return server capabilities
72    fn handle_initialize(id: Option<Value>, params: Option<Value>) -> JsonRpcResponse {
73        if let Some(params) = params {
74            if let Ok(init) = serde_json::from_value::<InitializeParams>(params) {
75                debug!(
76                    client = %init.client_info.name,
77                    version = ?init.client_info.version,
78                    protocol = %init.protocol_version,
79                    capabilities = %init.capabilities,
80                    "MCP client connected"
81                );
82            }
83        }
84
85        let result = InitializeResult {
86            protocol_version: PROTOCOL_VERSION.to_owned(),
87            capabilities: ServerCapabilities {
88                tools: Some(ToolsCapability {}),
89            },
90            server_info: ServerInfo {
91                name: SERVER_NAME.to_owned(),
92                version: SERVER_VERSION.to_owned(),
93            },
94        };
95
96        match serde_json::to_value(result) {
97            Ok(val) => JsonRpcResponse::success(id, val),
98            Err(e) => {
99                JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
100            }
101        }
102    }
103
104    /// Handle `tools/list` — return all registered tool definitions
105    fn handle_tools_list(&self, id: Option<Value>) -> JsonRpcResponse {
106        let result = ToolsListResult {
107            tools: self.tools.list_definitions(),
108        };
109
110        match serde_json::to_value(result) {
111            Ok(val) => JsonRpcResponse::success(id, val),
112            Err(e) => {
113                JsonRpcResponse::error(id, INTERNAL_ERROR, format!("Serialization error: {e}"))
114            }
115        }
116    }
117
118    /// Handle `tools/call` — dispatch to the named tool handler
119    async fn handle_tools_call(&self, id: Option<Value>, params: Option<Value>) -> JsonRpcResponse {
120        let call_params: CallToolParams = match params {
121            Some(p) => match serde_json::from_value(p) {
122                Ok(cp) => cp,
123                Err(e) => {
124                    return JsonRpcResponse::error(
125                        id,
126                        INVALID_PARAMS,
127                        format!("Invalid params: {e}"),
128                    );
129                }
130            },
131            None => {
132                return JsonRpcResponse::error(
133                    id,
134                    INVALID_PARAMS,
135                    "Missing params for tools/call".to_owned(),
136                );
137            }
138        };
139
140        let arguments = call_params
141            .arguments
142            .unwrap_or_else(|| Value::Object(serde_json::Map::new()));
143
144        let result = self
145            .tools
146            .execute(&call_params.name, &self.state, arguments)
147            .await;
148
149        match serde_json::to_value(result) {
150            Ok(val) => JsonRpcResponse::success(id, val),
151            Err(e) => JsonRpcResponse::error(
152                id,
153                INTERNAL_ERROR,
154                format!("Result serialization error: {e}"),
155            ),
156        }
157    }
158}