Skip to main content

composio_sdk/models/
mcp.rs

1//! MCP (Model Context Protocol) module for Composio SDK.
2//!
3//! This module provides MCP server operations for creating, managing, and generating
4//! MCP server instances for users.
5//!
6//! # Overview
7//!
8//! MCP servers provide connection points for AI assistants to access applications.
9//! This module allows you to:
10//! - Create MCP server configurations with specific toolkits
11//! - List and filter existing MCP servers
12//! - Update MCP server configurations
13//! - Delete MCP servers
14//! - Generate user-specific MCP server URLs
15//!
16//! # Example
17//!
18//! ```rust,no_run
19//! use composio_sdk::{Composio, models::mcp::MCPToolkitConfig};
20//!
21//! # async fn example() -> Result<(), Box<dyn std::error::Error>> {
22//! let composio = Composio::builder()
23//!     .api_key("your-api-key")
24//!     .build()?;
25//!
26//! // Create an MCP server
27//! let server = composio.mcp().create(
28//!     "my-mcp-server",
29//!     vec!["github".to_string(), "slack".to_string()],
30//!     None,
31//!     None,
32//! ).await?;
33//!
34//! // Generate a user-specific URL
35//! let instance = composio.mcp().generate(
36//!     "user_123",
37//!     &server.id,
38//!     None,
39//! ).await?;
40//!
41//! println!("MCP URL: {}", instance.url);
42//! # Ok(())
43//! # }
44//! ```
45
46use serde::{Deserialize, Serialize};
47use std::collections::HashMap;
48
49// ============================================================================
50// Data Types (matching TypeScript/Python specification)
51// ============================================================================
52
53/// MCP toolkit configuration
54///
55/// Specifies a toolkit and optionally an auth config to use for the MCP server.
56#[derive(Debug, Clone, Serialize, Deserialize)]
57pub struct MCPToolkitConfig {
58    /// Toolkit slug (e.g., "github", "slack")
59    pub toolkit: String,
60
61    /// Optional auth config ID to use for this toolkit
62    #[serde(skip_serializing_if = "Option::is_none")]
63    pub auth_config_id: Option<String>,
64}
65
66impl MCPToolkitConfig {
67    /// Create a new toolkit configuration
68    pub fn new(toolkit: impl Into<String>) -> Self {
69        Self {
70            toolkit: toolkit.into(),
71            auth_config_id: None,
72        }
73    }
74
75    /// Set the auth config ID
76    pub fn with_auth_config(mut self, auth_config_id: impl Into<String>) -> Self {
77        self.auth_config_id = Some(auth_config_id.into());
78        self
79    }
80}
81
82impl From<String> for MCPToolkitConfig {
83    fn from(toolkit: String) -> Self {
84        Self::new(toolkit)
85    }
86}
87
88impl From<&str> for MCPToolkitConfig {
89    fn from(toolkit: &str) -> Self {
90        Self::new(toolkit)
91    }
92}
93
94/// MCP Server Instance data structure
95///
96/// Represents a user-specific MCP server instance with connection details.
97#[derive(Debug, Clone, Serialize, Deserialize)]
98pub struct MCPServerInstance {
99    /// Server instance ID
100    pub id: String,
101
102    /// Human-readable server name
103    pub name: String,
104
105    /// Server type (typically "streamable_http")
106    #[serde(rename = "type")]
107    pub server_type: String,
108
109    /// User-specific connection URL
110    pub url: String,
111
112    /// Associated user ID
113    pub user_id: String,
114
115    /// Available tools for the user
116    pub allowed_tools: Vec<String>,
117
118    /// Associated auth configurations
119    pub auth_configs: Vec<String>,
120}
121
122/// Complete MCP server information
123#[derive(Debug, Clone, Serialize, Deserialize)]
124pub struct MCPItem {
125    /// Unique server identifier
126    pub id: String,
127
128    /// Human-readable server name
129    pub name: String,
130
131    /// Array of enabled tool identifiers
132    pub allowed_tools: Vec<String>,
133
134    /// Array of auth configuration IDs
135    pub auth_config_ids: Vec<String>,
136
137    /// Array of toolkit names
138    pub toolkits: Vec<String>,
139
140    /// Setup commands for different clients
141    pub commands: HashMap<String, String>,
142
143    /// Server connection URL
144    pub mcp_url: String,
145
146    /// Map of toolkit icons
147    pub toolkit_icons: HashMap<String, String>,
148
149    /// Number of active instances
150    pub server_instance_count: i32,
151
152    /// Creation timestamp
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub created_at: Option<String>,
155
156    /// Last update timestamp
157    #[serde(skip_serializing_if = "Option::is_none")]
158    pub updated_at: Option<String>,
159
160    /// Whether the server is soft-deleted
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub deleted: Option<bool>,
163
164    /// Whether auth is managed by Composio
165    #[serde(skip_serializing_if = "Option::is_none")]
166    pub managed_auth_via_composio: Option<bool>,
167}
168
169/// Paginated list response
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct MCPListResponse {
172    /// Array of MCP server objects
173    pub items: Vec<MCPItem>,
174
175    /// Current page number
176    pub current_page: i32,
177
178    /// Total number of pages
179    pub total_pages: i32,
180}
181
182/// Response from creating an MCP server
183#[derive(Debug, Clone, Serialize, Deserialize)]
184pub struct MCPCreateResponse {
185    /// Server ID
186    pub id: String,
187
188    /// Server name
189    pub name: String,
190
191    /// Allowed tools
192    pub allowed_tools: Vec<String>,
193
194    /// Auth config IDs
195    pub auth_config_ids: Vec<String>,
196
197    /// Toolkits
198    pub toolkits: Vec<String>,
199
200    /// MCP URL
201    pub mcp_url: String,
202
203    /// Creation timestamp
204    #[serde(skip_serializing_if = "Option::is_none")]
205    pub created_at: Option<String>,
206}
207
208/// Response from updating an MCP server
209pub type MCPUpdateResponse = MCPCreateResponse;
210
211/// Response from deleting an MCP server
212#[derive(Debug, Clone, Serialize, Deserialize)]
213pub struct MCPDeleteResponse {
214    /// Server ID that was deleted
215    pub id: String,
216
217    /// Whether the deletion was successful
218    pub deleted: bool,
219}
220
221/// Response from generating MCP URLs
222#[derive(Debug, Clone, Serialize, Deserialize)]
223pub struct MCPGenerateUrlResponse {
224    /// Base MCP URL without query parameters
225    #[serde(skip_serializing_if = "Option::is_none")]
226    pub mcp_url: Option<String>,
227
228    /// Array of connected-account-specific URLs
229    #[serde(skip_serializing_if = "Option::is_none")]
230    pub connected_account_urls: Option<Vec<String>>,
231
232    /// Array of user-specific URLs
233    pub user_ids_url: Vec<String>,
234}
235
236/// Response from retrieving an app-scoped MCP server list
237pub type MCPRetrieveAppResponse = MCPListResponse;
238
239/// Response from creating a custom MCP server
240pub type MCPCustomCreateResponse = MCPCreateResponse;
241
242// ============================================================================
243// Request Types
244// ============================================================================
245
246/// Parameters for creating an MCP server
247#[derive(Debug, Clone, Serialize)]
248pub struct MCPCreateParams {
249    /// Server name
250    pub name: String,
251
252    /// Auth config IDs
253    pub auth_config_ids: Vec<String>,
254
255    /// Allowed tool slugs
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub allowed_tools: Option<Vec<String>>,
258
259    /// No-auth app slugs
260    #[serde(skip_serializing_if = "Option::is_none")]
261    pub no_auth_apps: Option<Vec<String>>,
262
263    /// DEPRECATED: custom toolkit slugs (use no_auth_apps/auth_config_ids)
264    #[serde(skip_serializing_if = "Option::is_none")]
265    #[deprecated(
266        note = "Prefer no_auth_apps/auth_config_ids to match MCP create endpoint contract"
267    )]
268    pub toolkits: Option<Vec<String>>,
269
270    /// DEPRECATED: custom tools (use allowed_tools)
271    #[serde(skip_serializing_if = "Option::is_none")]
272    #[deprecated(note = "Prefer allowed_tools to match MCP create endpoint contract")]
273    pub custom_tools: Option<Vec<String>>,
274
275    /// Whether to use Composio managed auth
276    #[serde(skip_serializing_if = "Option::is_none")]
277    pub managed_auth_via_composio: Option<bool>,
278}
279
280/// Parameters for updating an MCP server
281#[derive(Debug, Clone, Serialize)]
282pub struct MCPUpdateParams {
283    /// Optional new name
284    #[serde(skip_serializing_if = "Option::is_none")]
285    pub name: Option<String>,
286
287    /// Optional toolkit slugs
288    #[serde(skip_serializing_if = "Option::is_none")]
289    pub toolkits: Option<Vec<String>>,
290
291    /// Optional auth config IDs
292    #[serde(skip_serializing_if = "Option::is_none")]
293    pub auth_config_ids: Option<Vec<String>>,
294
295    /// Optional allowed tools
296    #[serde(skip_serializing_if = "Option::is_none")]
297    pub allowed_tools: Option<Vec<String>>,
298
299    /// Optional custom tools (deprecated; alias of allowed tools behavior)
300    #[serde(skip_serializing_if = "Option::is_none")]
301    pub custom_tools: Option<Vec<String>>,
302
303    /// Optional managed auth flag
304    #[serde(skip_serializing_if = "Option::is_none")]
305    pub managed_auth_via_composio: Option<bool>,
306}
307
308/// Parameters for listing MCP servers
309#[derive(Debug, Clone, Default, Serialize)]
310pub struct MCPListParams {
311    /// Page number for pagination
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub page_no: Option<i32>,
314
315    /// Maximum items per page
316    #[serde(skip_serializing_if = "Option::is_none")]
317    pub limit: Option<i32>,
318
319    /// Filter by toolkit name
320    #[serde(skip_serializing_if = "Option::is_none")]
321    pub toolkits: Option<String>,
322
323    /// Filter by auth configuration ID
324    #[serde(skip_serializing_if = "Option::is_none")]
325    pub auth_config_ids: Option<String>,
326
327    /// Filter by server name
328    #[serde(skip_serializing_if = "Option::is_none")]
329    pub name: Option<String>,
330
331    /// Order by field
332    #[serde(skip_serializing_if = "Option::is_none")]
333    pub order_by: Option<String>,
334
335    /// Order direction
336    #[serde(skip_serializing_if = "Option::is_none")]
337    pub order_direction: Option<String>,
338}
339
340/// Parameters for generating MCP URLs
341#[derive(Debug, Clone, Serialize)]
342pub struct MCPGenerateUrlParams {
343    /// MCP server ID
344    pub mcp_server_id: String,
345
346    /// Connected account IDs
347    #[serde(skip_serializing_if = "Option::is_none")]
348    pub connected_account_ids: Option<Vec<String>>,
349
350    /// User IDs to generate URLs for
351    #[serde(skip_serializing_if = "Option::is_none")]
352    pub user_ids: Option<Vec<String>>,
353
354    /// Whether to use Composio managed auth
355    #[serde(skip_serializing_if = "Option::is_none")]
356    pub managed_auth_by_composio: Option<bool>,
357}
358
359/// Parameters for creating a custom MCP server
360#[derive(Debug, Clone, Serialize)]
361pub struct MCPCustomCreateParams {
362    /// Server name
363    pub name: String,
364
365    /// Allowed tool slugs
366    #[serde(skip_serializing_if = "Option::is_none")]
367    pub allowed_tools: Option<Vec<String>>,
368
369    /// Auth config IDs
370    #[serde(skip_serializing_if = "Option::is_none")]
371    pub auth_config_ids: Option<Vec<String>>,
372
373    /// Custom tools (deprecated; alias of allowed_tools)
374    #[serde(skip_serializing_if = "Option::is_none")]
375    pub custom_tools: Option<Vec<String>>,
376
377    /// Whether to use Composio managed auth
378    #[serde(skip_serializing_if = "Option::is_none")]
379    pub managed_auth_via_composio: Option<bool>,
380
381    /// Toolkit slugs
382    #[serde(skip_serializing_if = "Option::is_none")]
383    pub toolkits: Option<Vec<String>>,
384}
385
386/// Query parameters for app-scoped MCP server listing
387pub type MCPRetrieveAppParams = MCPListParams;
388
389#[cfg(test)]
390mod tests {
391    use super::*;
392
393    #[test]
394    fn test_mcp_toolkit_config_new() {
395        let config = MCPToolkitConfig::new("github");
396        assert_eq!(config.toolkit, "github");
397        assert!(config.auth_config_id.is_none());
398    }
399
400    #[test]
401    fn test_mcp_toolkit_config_with_auth() {
402        let config = MCPToolkitConfig::new("github").with_auth_config("ac_123");
403
404        assert_eq!(config.toolkit, "github");
405        assert_eq!(config.auth_config_id, Some("ac_123".to_string()));
406    }
407
408    #[test]
409    fn test_mcp_toolkit_config_from_string() {
410        let config: MCPToolkitConfig = "slack".into();
411        assert_eq!(config.toolkit, "slack");
412        assert!(config.auth_config_id.is_none());
413    }
414
415    #[test]
416    fn test_mcp_server_instance_serialization() {
417        let instance = MCPServerInstance {
418            id: "mcp_123".to_string(),
419            name: "Test Server".to_string(),
420            server_type: "streamable_http".to_string(),
421            url: "https://mcp.composio.dev/test".to_string(),
422            user_id: "user_123".to_string(),
423            allowed_tools: vec!["GITHUB_CREATE_ISSUE".to_string()],
424            auth_configs: vec!["ac_123".to_string()],
425        };
426
427        let json = serde_json::to_string(&instance).unwrap();
428        assert!(json.contains("mcp_123"));
429        assert!(json.contains("Test Server"));
430        assert!(json.contains("streamable_http"));
431    }
432
433    #[test]
434    fn test_mcp_server_instance_deserialization() {
435        let json = r#"{
436            "id": "mcp_456",
437            "name": "My Server",
438            "type": "streamable_http",
439            "url": "https://mcp.url",
440            "user_id": "user_456",
441            "allowed_tools": ["SLACK_SEND_MESSAGE"],
442            "auth_configs": ["ac_456"]
443        }"#;
444
445        let instance: MCPServerInstance = serde_json::from_str(json).unwrap();
446        assert_eq!(instance.id, "mcp_456");
447        assert_eq!(instance.name, "My Server");
448        assert_eq!(instance.server_type, "streamable_http");
449        assert_eq!(instance.url, "https://mcp.url");
450        assert_eq!(instance.user_id, "user_456");
451        assert_eq!(instance.allowed_tools.len(), 1);
452        assert_eq!(instance.auth_configs.len(), 1);
453    }
454
455    #[test]
456    fn test_mcp_list_params_default() {
457        let params = MCPListParams::default();
458        assert!(params.page_no.is_none());
459        assert!(params.limit.is_none());
460        assert!(params.toolkits.is_none());
461        assert!(params.auth_config_ids.is_none());
462        assert!(params.name.is_none());
463    }
464
465    #[test]
466    fn test_mcp_create_params_serialization() {
467        let params = MCPCreateParams {
468            name: "Test Server".to_string(),
469            auth_config_ids: vec!["ac_123".to_string()],
470            allowed_tools: Some(vec!["GITHUB_CREATE_ISSUE".to_string()]),
471            no_auth_apps: Some(vec!["notion".to_string()]),
472            toolkits: Some(vec!["github".to_string()]),
473            custom_tools: Some(vec!["GITHUB_CREATE_ISSUE".to_string()]),
474            managed_auth_via_composio: Some(true),
475        };
476
477        let json = serde_json::to_string(&params).unwrap();
478        assert!(json.contains("Test Server"));
479        assert!(json.contains("github"));
480        assert!(json.contains("ac_123"));
481        assert!(json.contains("allowed_tools"));
482        assert!(json.contains("no_auth_apps"));
483    }
484
485    #[test]
486    fn test_mcp_generate_params_serialization() {
487        let params = MCPGenerateUrlParams {
488            mcp_server_id: "mcp_123".to_string(),
489            connected_account_ids: Some(vec!["ca_1".to_string()]),
490            user_ids: Some(vec!["user_123".to_string()]),
491            managed_auth_by_composio: Some(true),
492        };
493
494        let json = serde_json::to_string(&params).unwrap();
495        assert!(json.contains("connected_account_ids"));
496        assert!(json.contains("user_ids"));
497    }
498
499    #[test]
500    fn test_mcp_generate_response_deserialization() {
501        let json = r#"{
502            "mcp_url": "https://mcp.example.com/base",
503            "connected_account_urls": ["https://mcp.example.com?connected_account_id=ca_1"],
504            "user_ids_url": ["https://mcp.example.com?user_id=user_123"]
505        }"#;
506
507        let response: MCPGenerateUrlResponse = serde_json::from_str(json).unwrap();
508        assert_eq!(
509            response.mcp_url.as_deref(),
510            Some("https://mcp.example.com/base")
511        );
512        assert_eq!(response.connected_account_urls.unwrap().len(), 1);
513        assert_eq!(response.user_ids_url.len(), 1);
514    }
515
516    #[test]
517    fn test_mcp_delete_response_deserialization() {
518        let json = r#"{
519            "id": "mcp_789",
520            "deleted": true
521        }"#;
522
523        let response: MCPDeleteResponse = serde_json::from_str(json).unwrap();
524        assert_eq!(response.id, "mcp_789");
525        assert!(response.deleted);
526    }
527}