scim_server/mcp_integration/
protocol.rs1use super::core::{ScimMcpServer, ScimToolResult};
8use super::handlers::{group_crud, group_queries, system_info, user_crud, user_queries};
9use super::tools::{group_schemas, system_schemas, user_schemas};
10use crate::ResourceProvider;
11use log::{debug, error, info, warn};
12use serde::{Deserialize, Serialize};
13use serde_json::{Value, json};
14use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
15
16#[derive(Debug, Deserialize)]
18struct McpRequest {
19 #[allow(dead_code)]
20 jsonrpc: String,
21 id: Option<Value>,
22 method: String,
23 params: Option<Value>,
24}
25
26#[derive(Debug, Serialize)]
28pub struct McpResponse {
29 pub jsonrpc: String,
30 pub id: Option<Value>,
31 pub result: Option<Value>,
32 pub error: Option<Value>,
33}
34
35#[derive(Debug, Serialize)]
37struct McpError {
38 code: i32,
39 message: String,
40 data: Option<Value>,
41}
42
43impl McpResponse {
44 fn success(id: Option<Value>, result: Value) -> Self {
46 Self {
47 jsonrpc: "2.0".to_string(),
48 id,
49 result: Some(result),
50 error: None,
51 }
52 }
53
54 fn error(id: Option<Value>, code: i32, message: String) -> Self {
56 Self {
57 jsonrpc: "2.0".to_string(),
58 id,
59 result: None,
60 error: Some(json!(McpError {
61 code,
62 message,
63 data: None
64 })),
65 }
66 }
67}
68
69impl<P: ResourceProvider + Send + Sync + 'static> ScimMcpServer<P> {
70 pub fn get_tools(&self) -> Vec<Value> {
88 vec![
89 user_schemas::create_user_tool(),
91 user_schemas::get_user_tool(),
92 user_schemas::update_user_tool(),
93 user_schemas::delete_user_tool(),
94 user_schemas::list_users_tool(),
95 user_schemas::search_users_tool(),
96 user_schemas::user_exists_tool(),
97 group_schemas::create_group_tool(),
99 group_schemas::get_group_tool(),
100 group_schemas::update_group_tool(),
101 group_schemas::delete_group_tool(),
102 group_schemas::list_groups_tool(),
103 group_schemas::search_groups_tool(),
104 group_schemas::group_exists_tool(),
105 system_schemas::get_schemas_tool(),
107 system_schemas::get_server_info_tool(),
108 ]
109 }
110
111 pub async fn execute_tool(&self, tool_name: &str, arguments: Value) -> ScimToolResult {
123 debug!("Executing MCP tool: {} with args: {}", tool_name, arguments);
124
125 match tool_name {
126 "scim_create_user" => user_crud::handle_create_user(self, arguments).await,
128 "scim_get_user" => user_crud::handle_get_user(self, arguments).await,
129 "scim_update_user" => user_crud::handle_update_user(self, arguments).await,
130 "scim_delete_user" => user_crud::handle_delete_user(self, arguments).await,
131
132 "scim_list_users" => user_queries::handle_list_users(self, arguments).await,
134 "scim_search_users" => user_queries::handle_search_users(self, arguments).await,
135 "scim_user_exists" => user_queries::handle_user_exists(self, arguments).await,
136
137 "scim_create_group" => group_crud::handle_create_group(self, arguments).await,
139 "scim_get_group" => group_crud::handle_get_group(self, arguments).await,
140 "scim_update_group" => group_crud::handle_update_group(self, arguments).await,
141 "scim_delete_group" => group_crud::handle_delete_group(self, arguments).await,
142
143 "scim_list_groups" => group_queries::handle_list_groups(self, arguments).await,
145 "scim_search_groups" => group_queries::handle_search_groups(self, arguments).await,
146 "scim_group_exists" => group_queries::handle_group_exists(self, arguments).await,
147
148 "scim_get_schemas" => system_info::handle_get_schemas(self, arguments).await,
150 "scim_server_info" => system_info::handle_server_info(self, arguments).await,
151
152 _ => ScimToolResult {
154 success: false,
155 content: json!({
156 "error": "Unknown tool",
157 "tool_name": tool_name
158 }),
159 metadata: None,
160 },
161 }
162 }
163
164 pub async fn run_stdio(self) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
183 info!("SCIM MCP server starting stdio communication");
184 info!(
185 "Available tools: {:?}",
186 self.get_tools()
187 .iter()
188 .map(|t| t.get("name"))
189 .collect::<Vec<_>>()
190 );
191
192 let stdin = tokio::io::stdin();
193 let mut stdout = tokio::io::stdout();
194 let mut reader = BufReader::new(stdin);
195 let mut line = String::new();
196
197 info!("SCIM MCP server ready - listening on stdio");
198
199 loop {
200 line.clear();
201 match reader.read_line(&mut line).await {
202 Ok(0) => {
203 debug!("EOF received, shutting down");
204 break; }
206 Ok(_) => {
207 let line_content = line.trim();
208 if line_content.is_empty() {
209 continue;
210 }
211
212 debug!("Received request: {}", line_content);
213
214 if let Some(response) = self.handle_mcp_request(line_content).await {
215 let response_json = match serde_json::to_string(&response) {
216 Ok(json) => json,
217 Err(e) => {
218 error!("Failed to serialize response: {}", e);
219 continue;
220 }
221 };
222
223 debug!("Sending response: {}", response_json);
224
225 if let Err(e) = stdout.write_all(response_json.as_bytes()).await {
226 error!("Failed to write response: {}", e);
227 break;
228 }
229 if let Err(e) = stdout.write_all(b"\n").await {
230 error!("Failed to write newline: {}", e);
231 break;
232 }
233 if let Err(e) = stdout.flush().await {
234 error!("Failed to flush stdout: {}", e);
235 break;
236 }
237 }
238 }
239 Err(e) => {
240 error!("Error reading from stdin: {}", e);
241 break;
242 }
243 }
244 }
245
246 info!("SCIM MCP server shutting down");
247 Ok(())
248 }
249
250 pub async fn handle_mcp_request(&self, line: &str) -> Option<McpResponse> {
252 let request: McpRequest = match serde_json::from_str(line) {
253 Ok(req) => req,
254 Err(e) => {
255 warn!("Failed to parse JSON request: {} - Input: {}", e, line);
256 return Some(McpResponse::error(None, -32700, "Parse error".to_string()));
257 }
258 };
259
260 debug!(
261 "Processing method: {} with id: {:?}",
262 request.method, request.id
263 );
264
265 match request.method.as_str() {
266 "initialize" => Some(self.handle_initialize(request.id)),
267 "notifications/initialized" => {
268 debug!("Received initialized notification - handshake complete");
269 None }
271 "tools/list" => Some(self.handle_tools_list(request.id)),
272 "tools/call" => Some(self.handle_tools_call(request.id, request.params).await),
273 "ping" => Some(self.handle_ping(request.id)),
274 _ => {
275 warn!("Unknown method: {}", request.method);
276 Some(McpResponse::error(
277 request.id,
278 -32601,
279 "Method not found".to_string(),
280 ))
281 }
282 }
283 }
284
285 fn handle_initialize(&self, id: Option<Value>) -> McpResponse {
287 debug!("Handling initialize request");
288
289 let result = json!({
290 "protocolVersion": "2024-11-05",
291 "capabilities": {
292 "tools": {}
293 },
294 "serverInfo": {
295 "name": self.server_info.name,
296 "version": self.server_info.version,
297 "description": self.server_info.description
298 }
299 });
300
301 McpResponse::success(id, result)
302 }
303
304 fn handle_tools_list(&self, id: Option<Value>) -> McpResponse {
306 debug!("Handling tools/list request");
307
308 let tools = self.get_tools();
309 let result = json!({
310 "tools": tools
311 });
312
313 McpResponse::success(id, result)
314 }
315
316 async fn handle_tools_call(&self, id: Option<Value>, params: Option<Value>) -> McpResponse {
318 debug!("Handling tools/call request");
319
320 let params = match params {
321 Some(p) => p,
322 None => {
323 return McpResponse::error(
324 id,
325 -32602,
326 "Invalid params: missing parameters".to_string(),
327 );
328 }
329 };
330
331 let tool_name = match params.get("name").and_then(|n| n.as_str()) {
332 Some(name) => name,
333 None => {
334 return McpResponse::error(
335 id,
336 -32602,
337 "Invalid params: missing tool name".to_string(),
338 );
339 }
340 };
341
342 let arguments = params.get("arguments").cloned().unwrap_or(json!({}));
343
344 debug!(
345 "Executing tool: {} with arguments: {}",
346 tool_name, arguments
347 );
348
349 let tool_result = self.execute_tool(tool_name, arguments).await;
350
351 if tool_result.success {
352 let result = json!({
353 "content": [
354 {
355 "type": "text",
356 "text": serde_json::to_string_pretty(&tool_result.content)
357 .unwrap_or_else(|_| "Error serializing result".to_string())
358 }
359 ],
360 "_meta": tool_result.metadata
361 });
362
363 McpResponse::success(id, result)
364 } else {
365 McpResponse::error(
366 id,
367 -32000,
368 format!(
369 "Tool execution failed: {}",
370 tool_result
371 .content
372 .get("error")
373 .and_then(|e| e.as_str())
374 .unwrap_or("Unknown error")
375 ),
376 )
377 }
378 }
379
380 fn handle_ping(&self, id: Option<Value>) -> McpResponse {
382 debug!("Handling ping request");
383 McpResponse::success(id, json!({}))
384 }
385}