Skip to main content

tokitai_mcp_server/
server.rs

1//! MCP Server implementation
2
3use axum::{
4    extract::State,
5    http::StatusCode,
6    routing::{get, post},
7    Json, Router,
8};
9use serde::{Deserialize, Serialize};
10use std::{error::Error, fmt, sync::Arc};
11use tokitai::mcp;
12use tokitai_core::serde_types;
13use tower_http::{
14    cors::{Any, CorsLayer},
15    trace::TraceLayer,
16};
17use tracing::{info, warn};
18
19/// Server error types
20#[derive(Debug)]
21pub enum ServerError {
22    /// Tool not found
23    ToolNotFound(String),
24    /// Tool execution failed
25    ToolExecutionError(String),
26    /// Invalid arguments
27    InvalidArguments(String),
28    /// Server startup failed
29    ServerStartupError(String),
30}
31
32impl fmt::Display for ServerError {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        match self {
35            ServerError::ToolNotFound(name) => write!(f, "Tool not found: {}", name),
36            ServerError::ToolExecutionError(msg) => write!(f, "Tool execution error: {}", msg),
37            ServerError::InvalidArguments(msg) => write!(f, "Invalid arguments: {}", msg),
38            ServerError::ServerStartupError(msg) => write!(f, "Server startup error: {}", msg),
39        }
40    }
41}
42
43impl Error for ServerError {}
44
45impl From<Box<dyn Error>> for ServerError {
46    fn from(err: Box<dyn Error>) -> Self {
47        ServerError::ToolExecutionError(err.to_string())
48    }
49}
50
51/// Server configuration
52#[derive(Debug, Clone)]
53pub struct McpServerConfig {
54    /// Host address to bind to
55    pub host: String,
56    /// Port to listen on
57    pub port: u16,
58    /// Enable CORS
59    pub cors_enabled: bool,
60    /// Enable request tracing
61    pub tracing_enabled: bool,
62}
63
64impl Default for McpServerConfig {
65    fn default() -> Self {
66        Self {
67            host: "127.0.0.1".to_string(),
68            port: 8080,
69            cors_enabled: true,
70            tracing_enabled: true,
71        }
72    }
73}
74
75impl McpServerConfig {
76    /// Create a new configuration with custom host and port
77    pub fn new(host: impl Into<String>, port: u16) -> Self {
78        Self {
79            host: host.into(),
80            port,
81            ..Default::default()
82        }
83    }
84
85    /// Set CORS enabled
86    pub fn with_cors(mut self, enabled: bool) -> Self {
87        self.cors_enabled = enabled;
88        self
89    }
90
91    /// Set tracing enabled
92    pub fn with_tracing(mut self, enabled: bool) -> Self {
93        self.tracing_enabled = enabled;
94        self
95    }
96
97    /// Get the full address string
98    pub fn address(&self) -> String {
99        format!("{}:{}", self.host, self.port)
100    }
101}
102
103/// Tool call request
104#[derive(Debug, Deserialize)]
105pub struct ToolCallRequest {
106    pub name: String,
107    #[serde(default)]
108    pub arguments: serde_json::Value,
109}
110
111/// Tool call response
112#[derive(Debug, Serialize)]
113pub struct ToolCallResponse {
114    pub success: bool,
115    #[serde(skip_serializing_if = "Option::is_none")]
116    pub result: Option<serde_json::Value>,
117    #[serde(skip_serializing_if = "Option::is_none")]
118    pub error: Option<String>,
119}
120
121impl ToolCallResponse {
122    pub fn success(result: serde_json::Value) -> Self {
123        Self {
124            success: true,
125            result: Some(result),
126            error: None,
127        }
128    }
129
130    pub fn error(message: impl Into<String>) -> Self {
131        Self {
132            success: false,
133            result: None,
134            error: Some(message.into()),
135        }
136    }
137}
138
139/// Internal tool registry
140struct ToolRegistry {
141    tools: Vec<mcp::McpTool>,
142}
143
144impl ToolRegistry {
145    fn new(tools: Vec<mcp::McpTool>) -> Self {
146        Self { tools }
147    }
148
149    fn find(&self, name: &str) -> Option<&mcp::McpTool> {
150        self.tools.iter().find(|t| t.name == name)
151    }
152}
153
154/// Application state
155struct AppState {
156    registry: ToolRegistry,
157}
158
159// ============================================================================
160// Generic MCP Server Builder
161// ============================================================================
162
163/// MCP Server builder with generic type support
164///
165/// # Example
166///
167/// ```rust,ignore
168/// use tokitai_mcp_server::McpServerBuilder;
169/// use tokitai::tool;
170///
171/// #[tool]
172/// struct Calculator;
173///
174/// #[tool]
175/// impl Calculator {
176///     pub fn add(&self, a: i32, b: i32) -> i32 {
177///         a + b
178///     }
179/// }
180///
181/// #[tokio::main]
182/// async fn main() {
183///     let server = McpServerBuilder::with_tool(Calculator::default())
184///         .with_port(3000)
185///         .build();
186///
187///     server.run().await.unwrap();
188/// }
189/// ```
190pub struct McpServerBuilder<T> {
191    config: McpServerConfig,
192    tool_provider: T,
193}
194
195impl<T> McpServerBuilder<T>
196where
197    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
198{
199    /// Create a new server builder with a tool provider
200    pub fn with_tool(tool: T) -> Self {
201        Self {
202            config: McpServerConfig::default(),
203            tool_provider: tool,
204        }
205    }
206
207    /// Create a new server builder with configuration
208    pub fn with_config(config: McpServerConfig, tool: T) -> Self {
209        Self {
210            config,
211            tool_provider: tool,
212        }
213    }
214
215    /// Set the server port
216    pub fn with_port(mut self, port: u16) -> Self {
217        self.config.port = port;
218        self
219    }
220
221    /// Set the server host
222    pub fn with_host(mut self, host: impl Into<String>) -> Self {
223        self.config.host = host.into();
224        self
225    }
226
227    /// Enable or disable CORS
228    pub fn with_cors(mut self, enabled: bool) -> Self {
229        self.config.cors_enabled = enabled;
230        self
231    }
232
233    /// Enable or disable tracing
234    pub fn with_tracing(mut self, enabled: bool) -> Self {
235        self.config.tracing_enabled = enabled;
236        self
237    }
238
239    /// Build the server
240    pub fn build(self) -> McpServerWithProvider<T>
241    where
242        T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
243    {
244        // Try to get tool definitions from the provider instance first (for MultiToolProvider),
245        // then fall back to static method
246        let tools = get_tools_from_provider(&self.tool_provider);
247        McpServerWithProvider {
248            config: self.config,
249            tool_provider: Arc::new(self.tool_provider),
250            tools,
251        }
252    }
253}
254
255/// Helper function to get tool definitions from a provider
256/// Works with both regular providers (via static method) and MultiToolProvider (via instance method)
257fn get_tools_from_provider<T>(provider: &T) -> Vec<mcp::McpTool>
258where
259    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
260{
261    // First try static method (works for most providers)
262    let static_tools = T::tool_definitions();
263    if !static_tools.is_empty() {
264        return mcp::to_mcp_tools(static_tools);
265    }
266
267    // If static method returns empty, try to get from instance (for MultiToolProvider)
268    // This is a special case for providers that collect tools at runtime
269    // We use the RuntimeToolProvider trait
270    get_tools_from_provider_runtime(provider)
271}
272
273/// Runtime check for providers with dynamic tool definitions
274///
275/// # Design Note: Why Type-Based Dispatch?
276///
277/// This function uses type-based dispatch to handle `MultiToolProvider` specially.
278/// This is a deliberate design choice: `MultiToolProvider` collects tools at runtime,
279/// while other providers use compile-time static methods (`ToolProvider::tool_definitions()`).
280///
281/// The type-based approach avoids introducing a trait that would only have a single implementation.
282/// If you need a custom provider with runtime tool definitions, consider:
283/// 1. Using `MultiToolProvider` to combine your tools
284/// 2. Filing an issue to discuss adding a `RuntimeToolProvider` trait
285fn get_tools_from_provider_runtime<T>(provider: &T) -> Vec<mcp::McpTool>
286where
287    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
288{
289    use std::any::Any;
290    if let Some(multi) = (provider as &dyn Any).downcast_ref::<MultiToolProvider>() {
291        return multi.tool_definitions().to_vec();
292    }
293
294    Vec::new()
295}
296
297/// MCP Server with a concrete tool provider
298pub struct McpServerWithProvider<T> {
299    config: McpServerConfig,
300    tool_provider: Arc<T>,
301    tools: Vec<mcp::McpTool>,
302}
303
304impl<T> McpServerWithProvider<T>
305where
306    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
307{
308    /// Create a new MCP server with a tool provider
309    pub fn new(config: McpServerConfig, tool_provider: T) -> Self {
310        let tools = get_tools_from_provider(&tool_provider);
311        Self {
312            config,
313            tool_provider: Arc::new(tool_provider),
314            tools,
315        }
316    }
317
318    /// Run the server with default address
319    pub async fn run(&self) -> Result<(), ServerError> {
320        self.run_with_address(&self.config.address()).await
321    }
322
323    /// Run the server with a specific address
324    ///
325    /// # Arguments
326    ///
327    /// * `addr` - Address in format "host:port"
328    ///
329    /// # Example
330    ///
331    /// ```rust,ignore
332    /// server.run_with_address("0.0.0.0:3000").await?;
333    /// ```
334    pub async fn run_with_address(&self, addr: &str) -> Result<(), ServerError> {
335        // Initialize tracing (only if not already set)
336        if self.config.tracing_enabled && !tracing::dispatcher::has_been_set() {
337            tracing_subscriber::fmt()
338                .with_env_filter(
339                    tracing_subscriber::EnvFilter::from_default_env()
340                        .add_directive("tokitai_mcp_server=info".parse().unwrap()),
341                )
342                .init();
343        }
344
345        let state = Arc::new(AppStateWithProvider {
346            registry: ToolRegistry::new(self.tools.clone()),
347            tool_provider: self.tool_provider.clone(), // 现在只是克隆 Arc,不是克隆 T
348        });
349
350        // Build router
351        let mut app = Router::new()
352            .route("/tools", get(list_tools_handler_with_provider))
353            .route("/call", post(call_tool_handler_with_provider))
354            .route("/health", get(health_handler))
355            .with_state(state);
356
357        // Add CORS if enabled
358        if self.config.cors_enabled {
359            let cors = CorsLayer::new()
360                .allow_origin(Any)
361                .allow_methods(Any)
362                .allow_headers(Any);
363            app = app.layer(cors);
364        }
365
366        // Add tracing if enabled
367        if self.config.tracing_enabled {
368            app = app.layer(TraceLayer::new_for_http());
369        }
370
371        info!("Starting MCP server on http://{}", addr);
372        info!("Endpoints:");
373        info!("  GET  /tools  - List available tools");
374        info!("  POST /call   - Call a tool");
375        info!("  GET  /health - Health check");
376
377        let listener = tokio::net::TcpListener::bind(addr)
378            .await
379            .map_err(|e| ServerError::ServerStartupError(e.to_string()))?;
380
381        axum::serve(listener, app)
382            .await
383            .map_err(|e| ServerError::ServerStartupError(e.to_string()))?;
384
385        Ok(())
386    }
387
388    /// Get the server configuration
389    pub fn config(&self) -> &McpServerConfig {
390        &self.config
391    }
392
393    /// Get the list of tools
394    pub fn tools(&self) -> &[mcp::McpTool] {
395        &self.tools
396    }
397
398    /// Get the tool provider
399    pub fn tool_provider(&self) -> &T {
400        &self.tool_provider
401    }
402}
403
404/// Application state with provider
405struct AppStateWithProvider<T> {
406    registry: ToolRegistry,
407    tool_provider: Arc<T>,
408}
409
410/// MCP Server (只读模式 - 不支持工具调用)
411///
412/// # Limitations
413///
414/// 此类型仅支持 `/tools` 端点,`/call` 端点返回 `501 Not Implemented`
415/// 如需完整功能,请使用 [`McpServerBuilder`] + [`MultiToolProvider`]
416///
417/// # Example
418///
419/// ```rust,ignore
420/// use tokitai_mcp_server::McpServer;
421///
422/// #[tokio::main]
423/// async fn main() {
424///     let server = McpServer::new();
425///     server.run().await.unwrap();
426/// }
427/// ```
428pub struct McpServer {
429    config: McpServerConfig,
430    tools: Vec<mcp::McpTool>,
431}
432
433impl Default for McpServer {
434    fn default() -> Self {
435        Self::new()
436    }
437}
438
439impl McpServer {
440    /// Create a new server with default configuration
441    pub fn new() -> Self {
442        Self {
443            config: McpServerConfig::default(),
444            tools: Vec::new(),
445        }
446    }
447
448    /// Create a new server with custom configuration
449    pub fn with_config(config: McpServerConfig) -> Self {
450        Self {
451            config,
452            tools: Vec::new(),
453        }
454    }
455
456    /// Create a server from tool providers
457    pub fn from_tools(tools: Vec<mcp::McpTool>) -> Self {
458        Self {
459            config: McpServerConfig::default(),
460            tools,
461        }
462    }
463
464    /// Run the server with default address
465    pub async fn run(&self) -> Result<(), ServerError> {
466        self.run_with_address(&self.config.address()).await
467    }
468
469    /// Run the server with a specific address
470    ///
471    /// # Arguments
472    ///
473    /// * `addr` - Address in format "host:port"
474    ///
475    /// # Example
476    ///
477    /// ```rust,ignore
478    /// server.run_with_address("0.0.0.0:3000").await?;
479    /// ```
480    pub async fn run_with_address(&self, addr: &str) -> Result<(), ServerError> {
481        // Initialize tracing (only if not already set)
482        if self.config.tracing_enabled && !tracing::dispatcher::has_been_set() {
483            tracing_subscriber::fmt()
484                .with_env_filter(
485                    tracing_subscriber::EnvFilter::from_default_env()
486                        .add_directive("tokitai_mcp_server=info".parse().unwrap()),
487                )
488                .init();
489        }
490
491        let state = Arc::new(AppState {
492            registry: ToolRegistry::new(self.tools.clone()),
493        });
494
495        // Build router
496        let mut app = Router::new()
497            .route("/tools", get(list_tools_handler))
498            .route("/call", post(call_tool_handler))
499            .route("/health", get(health_handler))
500            .with_state(state);
501
502        // Add CORS if enabled
503        if self.config.cors_enabled {
504            let cors = CorsLayer::new()
505                .allow_origin(Any)
506                .allow_methods(Any)
507                .allow_headers(Any);
508            app = app.layer(cors);
509        }
510
511        // Add tracing if enabled
512        if self.config.tracing_enabled {
513            app = app.layer(TraceLayer::new_for_http());
514        }
515
516        info!("Starting MCP server on http://{}", addr);
517        info!("Endpoints:");
518        info!("  GET  /tools  - List available tools");
519        info!("  POST /call   - Call a tool");
520        info!("  GET  /health - Health check");
521
522        let listener = tokio::net::TcpListener::bind(addr)
523            .await
524            .map_err(|e| ServerError::ServerStartupError(e.to_string()))?;
525
526        axum::serve(listener, app)
527            .await
528            .map_err(|e| ServerError::ServerStartupError(e.to_string()))?;
529
530        Ok(())
531    }
532
533    /// Get the server configuration
534    pub fn config(&self) -> &McpServerConfig {
535        &self.config
536    }
537
538    /// Get the list of tools
539    pub fn tools(&self) -> &[mcp::McpTool] {
540        &self.tools
541    }
542}
543
544// ============================================================================
545// HTTP Handlers
546// ============================================================================
547
548/// List tools handler
549async fn list_tools_handler(State(state): State<Arc<AppState>>) -> Json<Vec<mcp::McpTool>> {
550    Json(state.registry.tools.clone())
551}
552
553/// Call tool handler (without tool provider - returns 501 Not Implemented)
554///
555/// This handler always returns `501 Not Implemented` because `McpServer`
556/// doesn't have a tool provider. Use `McpServerBuilder` with `MultiToolProvider`
557/// for full tool call support.
558async fn call_tool_handler(
559    State(_state): State<Arc<AppState>>,
560    Json(request): Json<ToolCallRequest>,
561) -> Result<Json<ToolCallResponse>, StatusCode> {
562    info!("Tool call request (read-only mode): name={}", request.name);
563    // Without a tool provider, we can't execute tools
564    Err(StatusCode::NOT_IMPLEMENTED)
565}
566
567/// List tools handler with provider
568async fn list_tools_handler_with_provider<T>(
569    State(state): State<Arc<AppStateWithProvider<T>>>,
570) -> Json<Vec<mcp::McpTool>>
571where
572    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
573{
574    Json(state.registry.tools.clone())
575}
576
577/// Call tool handler with provider
578async fn call_tool_handler_with_provider<T>(
579    State(state): State<Arc<AppStateWithProvider<T>>>,
580    Json(request): Json<ToolCallRequest>,
581) -> Result<Json<ToolCallResponse>, StatusCode>
582where
583    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
584{
585    info!(
586        "Tool call request: name={}, arguments={:?}",
587        request.name, request.arguments
588    );
589
590    // Find the tool
591    let tool = state.registry.find(&request.name).ok_or_else(|| {
592        warn!("Tool not found: {}", request.name);
593        StatusCode::NOT_FOUND
594    })?;
595
596    info!("Found tool: {} - {}", tool.name, tool.description);
597
598    // Call the actual tool
599    match state
600        .tool_provider
601        .call_tool(&request.name, &request.arguments)
602    {
603        Ok(result) => {
604            info!("Tool executed successfully: {}", request.name);
605            Ok(Json(ToolCallResponse::success(result)))
606        }
607        Err(e) => {
608            warn!("Tool execution failed: {} - {}", request.name, e);
609            Ok(Json(ToolCallResponse::error(format!("{}", e))))
610        }
611    }
612}
613
614/// Health check handler
615async fn health_handler() -> &'static str {
616    "OK"
617}
618
619// ============================================================================
620// Multi-Tool Provider Support
621// ============================================================================
622
623/// A provider that combines multiple tool providers into one
624///
625/// This allows you to register multiple tool types and serve them together.
626///
627/// # Example
628///
629/// ```rust,ignore
630/// use tokitai_mcp_server::MultiToolProvider;
631///
632/// let mut provider = MultiToolProvider::new();
633/// provider.add(Calculator::default());
634/// provider.add(TextTools::default());
635///
636/// let server = McpServerBuilder::with_tool(provider)
637///     .with_port(8080)
638///     .build();
639/// ```
640pub struct MultiToolProvider {
641    providers: Vec<Box<dyn ToolCallerDyn>>,
642    tool_defs: Vec<mcp::McpTool>,
643}
644
645/// Dynamic tool caller trait object for runtime polymorphism
646///
647/// # Why This Trait Exists
648///
649/// Rust's type system requires knowing the concrete type at compile time. However,
650/// `MultiToolProvider` needs to store multiple different tool types (`Calculator`,
651/// `TextTools`, etc.) in a single collection and call them uniformly.
652///
653/// This trait object (`Box<dyn ToolCallerDyn>`) enables that by:
654/// 1. **Erasing the concrete type** - Store any tool provider in a `Vec`
655/// 2. **Dynamic dispatch** - Call tools without knowing their types at compile time
656/// 3. **Type safety** - Still enforces `Send + Sync` for thread safety
657///
658/// # How It Works
659///
660/// The `#[tool]` macro automatically implements this trait for any type that
661/// implements both `ToolProvider` and `ToolCaller`. This means you can seamlessly
662/// mix compile-time tool definitions with runtime polymorphism.
663///
664/// # Example
665///
666/// ```rust,ignore
667/// // Behind the scenes, MultiToolProvider does this:
668/// let mut providers: Vec<Box<dyn ToolCallerDyn>> = Vec::new();
669///
670/// // Each tool type is boxed as a trait object
671/// providers.push(Box::new(Calculator::default()));
672/// providers.push(Box::new(TextTools::default()));
673///
674/// // Call tools uniformly without knowing concrete types
675/// for provider in &providers {
676///     provider.call_tool("add", &args)?;
677/// }
678/// ```
679pub trait ToolCallerDyn: Send + Sync {
680    /// Call a tool by name
681    ///
682    /// # Arguments
683    ///
684    /// * `name` - The tool name to call
685    /// * `args` - JSON arguments passed to the tool
686    ///
687    /// # Returns
688    ///
689    /// The tool's result as a JSON value, or an error if the tool fails.
690    fn call_tool(
691        &self,
692        name: &str,
693        args: &serde_json::Value,
694    ) -> Result<serde_json::Value, tokitai_core::ToolError>;
695}
696
697impl<T> ToolCallerDyn for T
698where
699    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
700{
701    fn call_tool(
702        &self,
703        name: &str,
704        args: &serde_json::Value,
705    ) -> Result<serde_json::Value, tokitai_core::ToolError> {
706        self.call_tool(name, args)
707    }
708}
709
710impl MultiToolProvider {
711    /// Create a new empty multi-tool provider
712    pub fn new() -> Self {
713        Self {
714            providers: Vec::new(),
715            tool_defs: Vec::new(),
716        }
717    }
718
719    /// Add a tool provider to the collection
720    ///
721    /// # Example
722    ///
723    /// ```rust,ignore
724    /// let mut provider = MultiToolProvider::new();
725    /// provider.add(Calculator::default());
726    /// provider.add(TextTools::default());
727    /// ```
728    pub fn add<T>(&mut self, tool: T)
729    where
730        T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Send + Sync + 'static,
731    {
732        // Collect tool definitions from this provider
733        for def in T::tool_definitions() {
734            // Parse the input_schema from JSON string to Value
735            let schema: serde_json::Value =
736                serde_json::from_str(&def.input_schema).unwrap_or_else(|_| serde_json::json!({}));
737
738            let mcp_tool = mcp::McpTool {
739                name: def.name.clone(),
740                description: def.description.clone(),
741                input_schema: schema,
742            };
743            self.tool_defs.push(mcp_tool);
744        }
745
746        // Add the provider
747        self.providers.push(Box::new(tool));
748    }
749
750    /// Get all tool definitions
751    pub fn tool_definitions(&self) -> &[mcp::McpTool] {
752        &self.tool_defs
753    }
754}
755
756impl Default for MultiToolProvider {
757    fn default() -> Self {
758        Self::new()
759    }
760}
761
762impl MultiToolProvider {
763    /// Clone only the tool definitions (metadata), not the tool implementations.
764    ///
765    /// # Why This Method?
766    ///
767    /// `MultiToolProvider` cannot be fully cloned because it stores trait objects
768    /// (`Box<dyn ToolProvider + ToolCaller>`), which are not cloneable. This method
769    /// creates a new provider with the same tool **definitions** (names, descriptions,
770    /// schemas), but **without** the actual tool implementations.
771    ///
772    /// # What Gets Cloned?
773    ///
774    /// - ✅ Tool names, descriptions, and JSON schemas
775    /// - ❌ Tool implementations (the actual code that runs when called)
776    ///
777    /// # Use Cases
778    ///
779    /// This is useful when you need to:
780    /// - Share tool metadata (e.g., for documentation or UI generation)
781    /// - Create a "template" provider that others can add implementations to
782    ///
783    /// For most use cases, you should create a new `MultiToolProvider` and add
784    /// fresh instances of your tools.
785    ///
786    /// # Example
787    ///
788    /// ```rust,ignore
789    /// let mut provider = MultiToolProvider::new();
790    /// provider.add(Calculator::default());
791    ///
792    /// // Clone only the definitions
793    /// let metadata_only = provider.clone_definitions();
794    ///
795    /// // metadata_only has the same tool schemas, but no implementations
796    /// ```
797    pub fn clone_definitions(&self) -> Self {
798        if !self.tool_defs.is_empty() {
799            tracing::debug!(
800                "Cloning MultiToolProvider definitions ({} tools). \
801                 Note: The cloned instance has no tool implementations - \
802                 only metadata (names, descriptions, schemas).",
803                self.tool_defs.len()
804            );
805        }
806        Self {
807            providers: Vec::new(),
808            tool_defs: self.tool_defs.clone(),
809        }
810    }
811}
812
813impl tokitai_core::ToolProvider for MultiToolProvider {
814    fn tool_definitions() -> &'static [tokitai_core::ToolDefinition] {
815        // This won't work for MultiToolProvider since we need runtime collection
816        // We'll return an empty slice - the actual tool definitions come from
817        // the tool_defs field, not this static method
818        &[]
819    }
820}
821
822impl tokitai_core::ToolCaller for MultiToolProvider {
823    fn call_tool(
824        &self,
825        name: &str,
826        args: &serde_types::Value,
827    ) -> Result<serde_types::Value, tokitai_core::ToolError> {
828        // Try each provider until one succeeds or all fail
829        for provider in &self.providers {
830            // Check if this provider might have the tool
831            // (We could optimize this by storing a map of tool names to providers)
832            match provider.call_tool(name, args) {
833                Ok(result) => return Ok(result),
834                Err(e) => {
835                    // Check if it's a NotFound error, if so, try next provider
836                    if matches!(e.kind, tokitai_core::ToolErrorKind::NotFound) {
837                        continue;
838                    }
839                    // Other errors, return immediately
840                    return Err(e);
841                }
842            }
843        }
844
845        // No provider found
846        Err(tokitai_core::ToolError::not_found(format!(
847            "Tool '{}' not found in any provider",
848            name
849        )))
850    }
851}