Skip to main content

claude_code_sdk_rust/
mcp.rs

1//! MCP (Model Context Protocol) support for Claude Agent SDK.
2
3use serde::{Deserialize, Serialize};
4use serde_json::Value;
5use std::collections::HashMap;
6use std::sync::Arc;
7
8/// Configuration for an MCP server.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10#[serde(tag = "type")]
11pub enum MCPServerConfig {
12    /// Stdio-based MCP server.
13    #[serde(rename = "stdio")]
14    Stdio {
15        command: String,
16        #[serde(default)]
17        args: Vec<String>,
18        #[serde(default)]
19        env: HashMap<String, String>,
20    },
21    /// SSE-based MCP server.
22    #[serde(rename = "sse")]
23    Sse {
24        url: String,
25        #[serde(default)]
26        headers: HashMap<String, String>,
27    },
28    /// HTTP-based MCP server.
29    #[serde(rename = "http")]
30    Http {
31        url: String,
32        #[serde(default)]
33        headers: HashMap<String, String>,
34    },
35}
36
37/// Information about an MCP tool.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39pub struct MCPTool {
40    pub name: String,
41    pub description: String,
42    pub input_schema: Value,
43    pub annotations: Option<MCPToolAnnotations>,
44}
45
46/// Annotations for an MCP tool.
47#[derive(Debug, Clone, Default, Serialize, Deserialize)]
48pub struct MCPToolAnnotations {
49    #[serde(default)]
50    pub title: Option<String>,
51    #[serde(default)]
52    pub read_only_hint: bool,
53    #[serde(default)]
54    pub destructive_hint: bool,
55    #[serde(default)]
56    pub idempotent_hint: bool,
57    #[serde(default)]
58    pub open_world_hint: bool,
59    #[serde(default)]
60    pub max_result_size_chars: Option<usize>,
61}
62
63/// Content block for MCP tool results.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65#[serde(tag = "type")]
66pub enum MCPContent {
67    #[serde(rename = "text")]
68    Text { text: String },
69    #[serde(rename = "image")]
70    Image { data: String, mime_type: String },
71    #[serde(rename = "audio")]
72    Audio { data: String, mime_type: String },
73    #[serde(rename = "resource_link")]
74    ResourceLink {
75        uri: String,
76        name: Option<String>,
77        description: Option<String>,
78        mime_type: Option<String>,
79    },
80    #[serde(rename = "resource")]
81    Resource {
82        uri: String,
83        mime_type: Option<String>,
84        text: Option<String>,
85    },
86}
87
88/// Status of an MCP server.
89#[derive(Debug, Clone, Serialize, Deserialize)]
90pub struct MCPServerStatus {
91    pub name: String,
92    pub status: MCPConnectionStatus,
93    #[serde(default)]
94    pub tools: Vec<MCPTool>,
95    #[serde(default)]
96    pub error: Option<String>,
97}
98
99/// Connection status of an MCP server.
100#[derive(Debug, Clone, Serialize, Deserialize)]
101pub enum MCPConnectionStatus {
102    Connected,
103    Disconnected,
104    Error,
105}
106
107/// A simple in-process MCP server.
108type MCPToolHandler = dyn Fn(Value) -> Result<Vec<MCPContent>, String> + Send + Sync;
109
110/// Definition for an SDK MCP tool.
111#[derive(Clone)]
112pub struct SdkMcpTool {
113    pub name: String,
114    pub description: String,
115    pub input_schema: Value,
116    pub annotations: Option<MCPToolAnnotations>,
117    handler: Arc<MCPToolHandler>,
118}
119
120impl std::fmt::Debug for SdkMcpTool {
121    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122        f.debug_struct("SdkMcpTool")
123            .field("name", &self.name)
124            .field("description", &self.description)
125            .field("input_schema", &self.input_schema)
126            .field("annotations", &self.annotations)
127            .finish_non_exhaustive()
128    }
129}
130
131impl SdkMcpTool {
132    pub fn new<F>(
133        name: impl Into<String>,
134        description: impl Into<String>,
135        input_schema: Value,
136        annotations: Option<MCPToolAnnotations>,
137        handler: F,
138    ) -> Self
139    where
140        F: Fn(Value) -> Result<Vec<MCPContent>, String> + Send + Sync + 'static,
141    {
142        Self {
143            name: name.into(),
144            description: description.into(),
145            input_schema,
146            annotations,
147            handler: Arc::new(handler),
148        }
149    }
150
151    fn into_parts(self) -> (MCPTool, Arc<MCPToolHandler>) {
152        let tool = MCPTool {
153            name: self.name,
154            description: self.description,
155            input_schema: self.input_schema,
156            annotations: self.annotations,
157        };
158        (tool, self.handler)
159    }
160}
161
162/// Construct an SDK MCP tool definition.
163pub fn tool<F>(
164    name: impl Into<String>,
165    description: impl Into<String>,
166    input_schema: Value,
167    handler: F,
168) -> SdkMcpTool
169where
170    F: Fn(Value) -> Result<Vec<MCPContent>, String> + Send + Sync + 'static,
171{
172    SdkMcpTool::new(name, description, input_schema, None, handler)
173}
174
175/// Construct an SDK MCP tool definition with MCP annotations.
176pub fn tool_with_annotations<F>(
177    name: impl Into<String>,
178    description: impl Into<String>,
179    input_schema: Value,
180    annotations: MCPToolAnnotations,
181    handler: F,
182) -> SdkMcpTool
183where
184    F: Fn(Value) -> Result<Vec<MCPContent>, String> + Send + Sync + 'static,
185{
186    SdkMcpTool::new(name, description, input_schema, Some(annotations), handler)
187}
188
189#[derive(Clone)]
190pub struct SimpleMCPServer {
191    name: String,
192    version: String,
193    tools: HashMap<String, MCPTool>,
194    handlers: HashMap<String, Arc<MCPToolHandler>>,
195}
196
197impl std::fmt::Debug for SimpleMCPServer {
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        f.debug_struct("SimpleMCPServer")
200            .field("name", &self.name)
201            .field("tools", &self.tools.keys().collect::<Vec<_>>())
202            .finish()
203    }
204}
205
206impl SimpleMCPServer {
207    /// Create a new simple MCP server.
208    pub fn new(name: impl Into<String>) -> Self {
209        Self::with_version(name, "1.0.0")
210    }
211
212    /// Create a new simple MCP server with an explicit version.
213    pub fn with_version(name: impl Into<String>, version: impl Into<String>) -> Self {
214        Self {
215            name: name.into(),
216            version: version.into(),
217            tools: HashMap::new(),
218            handlers: HashMap::new(),
219        }
220    }
221
222    /// Register a tool with the server.
223    pub fn register_tool<F>(&mut self, tool: MCPTool, handler: F)
224    where
225        F: Fn(Value) -> Result<Vec<MCPContent>, String> + Send + Sync + 'static,
226    {
227        let name = tool.name.clone();
228        self.tools.insert(name.clone(), tool);
229        self.handlers.insert(name, Arc::new(handler));
230    }
231
232    /// Register a pre-built SDK MCP tool with the server.
233    pub fn register_sdk_tool(&mut self, tool: SdkMcpTool) {
234        let (tool, handler) = tool.into_parts();
235        let name = tool.name.clone();
236        self.tools.insert(name.clone(), tool);
237        self.handlers.insert(name, handler);
238    }
239
240    /// Get all registered tools.
241    pub fn list_tools(&self) -> Vec<&MCPTool> {
242        self.tools.values().collect()
243    }
244
245    /// Call a tool by name.
246    pub fn call_tool(&self, name: &str, input: Value) -> Result<Vec<MCPContent>, String> {
247        if let Some(handler) = self.handlers.get(name) {
248            handler(input)
249        } else {
250            Err(format!("Tool '{}' not found", name))
251        }
252    }
253
254    /// Get the server name.
255    pub fn name(&self) -> &str {
256        &self.name
257    }
258
259    /// Get the server version.
260    pub fn version(&self) -> &str {
261        &self.version
262    }
263}
264
265/// Initialize an MCP server.
266pub fn initialize_server(name: impl Into<String>) -> SimpleMCPServer {
267    SimpleMCPServer::new(name)
268}
269
270/// Create an in-process SDK MCP server with pre-registered tools.
271pub fn create_sdk_mcp_server(name: impl Into<String>, tools: Vec<SdkMcpTool>) -> SimpleMCPServer {
272    create_sdk_mcp_server_with_version(name, "1.0.0", tools)
273}
274
275/// Create an in-process SDK MCP server with an explicit version.
276pub fn create_sdk_mcp_server_with_version(
277    name: impl Into<String>,
278    version: impl Into<String>,
279    tools: Vec<SdkMcpTool>,
280) -> SimpleMCPServer {
281    let mut server = SimpleMCPServer::with_version(name, version);
282    for tool in tools {
283        server.register_sdk_tool(tool);
284    }
285    server
286}