1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
//! Basic Usage Example - Composio Rust SDK
//!
//! This example demonstrates the fundamental features of the Composio SDK:
//! - Client initialization with API key
//! - Session creation for a user
//! - Toolkit filtering (enable/disable)
//! - Tool execution with arguments
//! - Error handling patterns
//!
//! ## Prerequisites
//!
//! 1. Set your Composio API key as an environment variable:
//! ```bash
//! export COMPOSIO_API_KEY="your-api-key-here"
//! ```
//!
//! 2. Ensure you have at least one connected account for the GitHub toolkit
//! (or modify the example to use a different toolkit you have connected)
//!
//! ## Running the Example
//!
//! ```bash
//! cargo run --example basic_usage
//! ```
//!
//! ## What This Example Shows
//!
//! - **Client Initialization**: Creating a Composio client with your API key
//! - **Session Creation**: Creating a session scoped to a specific user
//! - **Toolkit Filtering**: Enabling specific toolkits or disabling unwanted ones
//! - **Tool Execution**: Executing tools with JSON arguments
//! - **Error Handling**: Handling different error types gracefully
//! - **Session Information**: Accessing session ID and MCP URL
use composio_sdk::{ComposioClient, ComposioError};
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Composio Rust SDK - Basic Usage Example ===\n");
// ========================================================================
// Step 1: Initialize the Composio Client
// ========================================================================
//
// The client is the main entry point for interacting with the Composio API.
// It requires an API key which you can get from the Composio dashboard.
//
// The builder pattern allows you to customize:
// - API key (required)
// - Base URL (optional, defaults to Composio's production API)
// - Timeout (optional, defaults to 30 seconds)
// - Retry configuration (optional, defaults to 3 retries with exponential backoff)
println!("Step 1: Initializing Composio client...");
let client = ComposioClient::builder()
.api_key(std::env::var("COMPOSIO_API_KEY")?)
.build()?;
println!("✓ Client initialized successfully\n");
// ========================================================================
// Step 2: Create a Session for a User
// ========================================================================
//
// Sessions are the core concept in Composio's Tool Router API.
// Each session is scoped to a specific user and defines:
// - Which toolkits are available
// - Which authentication configs to use
// - Which connected accounts to use
// - Whether to enable in-chat connection management
//
// Sessions are immutable - create a new session when configuration changes.
println!("Step 2: Creating a session for user 'demo_user_123'...");
let session = client
.create_session("demo_user_123")
.toolkits(vec!["github", "gmail"]) // Enable specific toolkits
.manage_connections(true) // Enable in-chat authentication
.send()
.await?;
println!("✓ Session created successfully");
println!(" Session ID: {}", session.session_id());
println!(" MCP URL: {}", session.mcp_url());
println!(" Available tools: {} meta tools", session.tools().len());
println!();
// ========================================================================
// Step 3: Toolkit Filtering - Enable Specific Toolkits
// ========================================================================
//
// You can control which toolkits are available in a session.
// This is useful for:
// - Limiting the agent's capabilities to specific services
// - Reducing context size by excluding unnecessary toolkits
// - Implementing role-based access control
println!("Step 3: Creating a session with specific toolkits enabled...");
let github_session = client
.create_session("demo_user_123")
.toolkits(vec!["github"]) // Only enable GitHub toolkit
.send()
.await?;
println!("✓ GitHub-only session created: {}", github_session.session_id());
println!();
// ========================================================================
// Step 4: Toolkit Filtering - Disable Specific Toolkits
// ========================================================================
//
// Alternatively, you can disable specific toolkits while keeping all others.
// This is useful when you want most toolkits but need to exclude a few.
println!("Step 4: Creating a session with specific toolkits disabled...");
let filtered_session = client
.create_session("demo_user_123")
.disable_toolkits(vec!["exa", "firecrawl"]) // Disable search toolkits
.send()
.await?;
println!("✓ Filtered session created: {}", filtered_session.session_id());
println!();
// ========================================================================
// Step 5: Execute a Tool with Arguments
// ========================================================================
//
// Tools are executed within a session context.
// Each tool requires:
// - Tool slug (e.g., "GITHUB_GET_REPOS")
// - Arguments as a JSON object
//
// The SDK automatically:
// - Injects authentication from the user's connected account
// - Retries on transient failures
// - Returns structured responses with data and error fields
println!("Step 5: Executing a tool (GITHUB_GET_REPOS)...");
match github_session
.execute_tool(
"GITHUB_GET_REPOS",
json!({
"owner": "composio",
"type": "public"
}),
)
.await
{
Ok(result) => {
println!("✓ Tool executed successfully");
println!(" Log ID: {}", result.log_id);
// Check if the tool returned an error
if let Some(error) = result.error {
println!(" Tool Error: {}", error);
} else {
// Pretty-print the result data
println!(" Result:");
println!("{}", serde_json::to_string_pretty(&result.data)?);
}
}
Err(e) => {
eprintln!("✗ Tool execution failed: {}", e);
handle_error(&e);
}
}
println!();
// ========================================================================
// Step 6: Error Handling Patterns
// ========================================================================
//
// The SDK provides detailed error types that help you handle failures:
// - ApiError: HTTP errors from the Composio API (with status, message, suggested_fix)
// - NetworkError: Connection issues, timeouts
// - SerializationError: JSON parsing failures
// - InvalidInput: Client-side validation errors
// - ConfigError: Configuration issues
//
// Always check for suggested_fix in ApiError - it provides actionable guidance.
println!("Step 6: Demonstrating error handling...");
// Example: Executing a tool that doesn't exist
match github_session
.execute_tool("INVALID_TOOL_SLUG", json!({}))
.await
{
Ok(_) => {
println!(" Unexpected success");
}
Err(e) => {
println!("✓ Error caught as expected");
handle_error(&e);
}
}
println!();
// Example: Executing a tool with missing required arguments
match github_session
.execute_tool(
"GITHUB_CREATE_ISSUE",
json!({
// Missing required fields: owner, repo, title
}),
)
.await
{
Ok(_) => {
println!(" Unexpected success");
}
Err(e) => {
println!("✓ Error caught as expected");
handle_error(&e);
}
}
println!();
// ========================================================================
// Step 7: Additional Session Features
// ========================================================================
//
// Sessions provide additional capabilities:
// - List available toolkits with connection status
// - Get meta tool schemas
// - Create authentication links for users
// - Execute meta tools (COMPOSIO_SEARCH_TOOLS, etc.)
println!("Step 7: Listing available toolkits...");
match session.list_toolkits().send().await {
Ok(toolkits) => {
println!("✓ Found {} toolkits", toolkits.items.len());
for toolkit in toolkits.items.iter().take(5) {
let status = if toolkit.connected_account.is_some() {
"✓ Connected"
} else {
"○ Not connected"
};
println!(" {} - {} ({})", toolkit.slug, toolkit.name, status);
}
if toolkits.items.len() > 5 {
println!(" ... and {} more", toolkits.items.len() - 5);
}
}
Err(e) => {
eprintln!("✗ Failed to list toolkits: {}", e);
}
}
println!();
println!("=== Example completed successfully! ===\n");
println!("Next steps:");
println!("- Check out examples/meta_tools.rs for meta tool usage");
println!("- Check out examples/auth_link_creation.rs for authentication flows");
println!("- Check out examples/toolkit_listing.rs for advanced filtering");
println!("- Read the documentation: cargo doc --open");
Ok(())
}
/// Helper function to handle and display different error types
///
/// This demonstrates best practices for error handling with the Composio SDK.
/// Always check for:
/// - Status code (to determine if retry is appropriate)
/// - Error message (for logging and debugging)
/// - Suggested fix (for actionable guidance to users)
/// - Request ID (for support inquiries)
fn handle_error(error: &ComposioError) {
match error {
ComposioError::ApiError {
status,
message,
code,
slug,
request_id,
suggested_fix,
errors,
} => {
println!(" Error Type: API Error");
println!(" Status Code: {}", status);
println!(" Message: {}", message);
if let Some(code) = code {
println!(" Error Code: {}", code);
}
if let Some(slug) = slug {
println!(" Error Slug: {}", slug);
}
if let Some(request_id) = request_id {
println!(" Request ID: {} (use this when contacting support)", request_id);
}
if let Some(fix) = suggested_fix {
println!(" 💡 Suggested Fix: {}", fix);
}
if let Some(errors) = errors {
if !errors.is_empty() {
println!(" Detailed Errors:");
for err in errors {
if let Some(field) = &err.field {
println!(" - {}: {}", field, err.message);
} else {
println!(" - {}", err.message);
}
}
}
}
// Provide guidance based on status code
match *status {
400 => println!(" ℹ️ This is a client error - check your request parameters"),
401 => println!(" ℹ️ Authentication failed - check your API key"),
403 => println!(" ℹ️ Access forbidden - check your permissions"),
404 => println!(" ℹ️ Resource not found - check the tool slug or session ID"),
429 => println!(" ℹ️ Rate limited - the SDK will automatically retry"),
500..=599 => println!(" ℹ️ Server error - the SDK will automatically retry"),
_ => {}
}
}
ComposioError::NetworkError(e) => {
println!(" Error Type: Network Error");
println!(" Message: {}", e);
println!(" ℹ️ Check your internet connection - the SDK will automatically retry");
}
ComposioError::SerializationError(e) => {
println!(" Error Type: Serialization Error");
println!(" Message: {}", e);
println!(" ℹ️ This indicates a JSON parsing issue - check your arguments");
}
ComposioError::InvalidInput(msg) => {
println!(" Error Type: Invalid Input");
println!(" Message: {}", msg);
println!(" ℹ️ Fix the input validation error and try again");
}
ComposioError::ConfigError(msg) => {
println!(" Error Type: Configuration Error");
println!(" Message: {}", msg);
println!(" ℹ️ Check your SDK configuration (API key, base URL, etc.)");
}
}
}