tokitai 0.5.0

Tokitai - AI tool integration system with compile-time tool definitions
Documentation
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
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//! MCP (Model Context Protocol) 支持
//!
//! 提供与 MCP 兼容的工具定义格式和服务器运行时。
//!
//! # 快速开始
//!
//! ```rust,ignore
//! use tokitai::{tool, mcp::McpServer};
//!
//! #[tool]
//! struct Calculator;
//!
//! #[tool]
//! impl Calculator {
//!     pub fn add(&self, a: i32, b: i32) -> i32 {
//!         a + b
//!     }
//! }
//!
//! // 创建 MCP 服务器
//! let server = Calculator::new_mcp_server();
//! ```

use serde::{Deserialize, Serialize};
use tokitai_core::ToolDefinition;

#[cfg(feature = "mcp")]
use async_trait::async_trait;

/// MCP tool definition format
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpTool {
    pub name: String,
    pub description: String,
    pub input_schema: serde_json::Value,
    /// Output schema (advertises the return type of the tool). Populated by
    /// `to_mcp_tools` when the tool's return type has a `#[tool_type]` schema
    /// registered in the global `TYPE_SCHEMA_CACHE`. `None` for tools whose
    /// return type is not registered (e.g. primitives, plain `serde_json::Value`).
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub output_schema: Option<serde_json::Value>,
}

/// Converts tokitai tool definitions to MCP format
pub fn to_mcp_tools(tools: &[ToolDefinition]) -> Vec<McpTool> {
    tools
        .iter()
        .filter_map(|t| match serde_json::from_str(&t.input_schema) {
            Ok(schema) => Some(McpTool {
                name: t.name.to_string(),
                description: t.description.to_string(),
                input_schema: schema,
                output_schema: None,
            }),
            Err(e) => {
                log::warn!("failed to parse schema for tool '{}': {}", t.name, e);
                None
            }
        })
        .collect()
}

/// MCP tool call request
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolCall {
    pub name: String,
    #[serde(default)]
    pub arguments: serde_json::Value,
}

/// MCP tool call response
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpToolResponse {
    pub success: bool,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub result: Option<serde_json::Value>,
    #[serde(skip_serializing_if = "Option::is_none")]
    pub error: Option<String>,
}

impl McpToolResponse {
    /// Build a successful response wrapping `result`.
    ///
    /// Sets `success: true`, stores `result` in the `result` field, and
    /// leaves `error` as `None`.
    pub fn success(result: serde_json::Value) -> Self {
        Self {
            success: true,
            result: Some(result),
            error: None,
        }
    }

    /// Build a failure response with the given error `message`.
    ///
    /// Sets `success: false`, stores the message in the `error` field, and
    /// leaves `result` as `None`.
    pub fn error(message: impl Into<String>) -> Self {
        Self {
            success: false,
            result: None,
            error: Some(message.into()),
        }
    }
}

// ============================================================================
// MCP Server Trait - 核心抽象
// ============================================================================

/// MCP server trait
///
/// Provides the tool list and call interface required by the MCP protocol.
/// This trait is automatically implemented for all `#[tool]` types and does
/// not need to be implemented manually.
///
/// # Examples
///
/// ```rust,ignore
/// use tokitai::{tool, mcp::McpServer};
///
/// #[tool]
/// struct MyTools;
///
/// #[tool]
/// impl MyTools {
///     pub fn greet(&self, name: String) -> String {
///         format!("Hello, {}!", name)
///     }
/// }
///
/// // McpServer is automatically implemented
/// let server = MyTools::new_mcp_server();
/// let tools = server.list_tools().await;
/// let result = server.call_tool("greet", &json!({"name": "Alice"})).await;
/// ```
#[cfg(feature = "mcp")]
#[async_trait]
pub trait McpServer: Sized + Send + Sync {
    /// Returns all available tool definitions
    async fn list_tools(&self) -> Vec<McpTool>;

    /// Invokes the specified tool
    async fn call_tool(&self, name: &str, arguments: &serde_json::Value) -> McpToolResponse;

    /// Returns the number of tools
    async fn tool_count(&self) -> usize {
        self.list_tools().await.len()
    }
}

// ============================================================================
// 为所有 #[tool] 类型自动实现 McpServer
// ============================================================================

/// MCP server wrapper
///
/// Wraps any type implementing `ToolProvider` and `ToolCaller` as an MCP
/// server. This is the foundation on which the `#[tool]` macro automatically
/// implements `McpServer`.
#[cfg(feature = "mcp")]
pub struct McpServerWrapper<T> {
    inner: T,
}

#[cfg(feature = "mcp")]
impl<T> McpServerWrapper<T>
where
    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Clone + Send + Sync + 'static,
{
    /// Creates a new MCP server wrapper
    pub fn new(inner: T) -> Self {
        Self { inner }
    }

    /// Returns the inner tool instance
    pub fn inner(&self) -> &T {
        &self.inner
    }

    /// Converts to MCP tool definitions
    pub fn to_mcp_tools(&self) -> Vec<McpTool> {
        to_mcp_tools(T::tool_definitions())
    }
}

#[cfg(feature = "mcp")]
#[async_trait]
impl<T> McpServer for McpServerWrapper<T>
where
    T: tokitai_core::ToolProvider + tokitai_core::ToolCaller + Clone + Send + Sync + 'static,
{
    async fn list_tools(&self) -> Vec<McpTool> {
        self.to_mcp_tools()
    }

    async fn call_tool(&self, name: &str, arguments: &serde_json::Value) -> McpToolResponse {
        // 使用 ToolCaller trait 的 call_tool 方法
        match self.inner.call_tool(name, arguments) {
            Ok(result) => McpToolResponse::success(result),
            Err(e) => McpToolResponse::error(format!("{}", e)),
        }
    }
}

// ============================================================================
// 宏辅助:为 #[tool] 类型生成 McpServer 实现
// ============================================================================

/// Macro that generates an `McpServer` implementation for a type
///
/// This macro is invoked automatically by the `#[tool]` macro and does not
/// need to be called manually by users.
#[macro_export]
macro_rules! impl_mcp_server {
    ($type:ty) => {
        impl $type {
            /// Creates a new MCP server instance
            #[cfg(feature = "mcp")]
            pub fn new_mcp_server() -> $crate::mcp::McpServerWrapper<Self> {
                $crate::mcp::McpServerWrapper::new(Self::default())
            }

            /// Returns the list of MCP tool definitions
            #[cfg(feature = "mcp")]
            pub fn mcp_tool_definitions() -> Vec<$crate::mcp::McpTool> {
                $crate::mcp::to_mcp_tools(<Self as $crate::ToolProvider>::tool_definitions())
            }
        }
    };
}

// ============================================================================
// MCP HTTP 服务器(可选功能,需要 http-server feature)
// ============================================================================

/// MCP HTTP server configuration
#[cfg(feature = "http-server")]
#[derive(Debug, Clone)]
pub struct McpHttpConfig {
    /// The listen address
    pub host: String,
    /// The listen port
    pub port: u16,
    /// Whether CORS is enabled
    pub cors_enabled: bool,
}

#[cfg(feature = "http-server")]
impl Default for McpHttpConfig {
    fn default() -> Self {
        Self {
            host: "127.0.0.1".to_string(),
            port: 8080,
            cors_enabled: true,
        }
    }
}

/// MCP HTTP server
///
/// Provides an HTTP-based MCP protocol implementation.
///
/// # Examples
///
/// ```rust,ignore
/// use tokitai::{tool, mcp::McpHttpServer};
///
/// #[tool]
/// struct Calculator;
///
/// #[tool]
/// impl Calculator {
///     pub fn add(&self, a: i32, b: i32) -> i32 {
///         a + b
///     }
/// }
///
/// #[tokio::main]
/// async fn main() {
///     let server = McpHttpServer::new(Calculator::default());
///     server.run("127.0.0.1:8080").await.unwrap();
/// }
/// ```
#[cfg(feature = "http-server")]
pub struct McpHttpServer<T> {
    #[allow(dead_code)]
    inner: T,
    #[allow(dead_code)]
    config: McpHttpConfig,
}

#[cfg(feature = "http-server")]
impl<T> McpHttpServer<T>
where
    T: tokitai_core::ToolProvider + Clone + Send + Sync + 'static,
{
    /// Creates a new MCP HTTP server
    pub fn new(inner: T) -> Self {
        Self {
            inner,
            config: McpHttpConfig::default(),
        }
    }

    /// Creates a server with the given configuration
    pub fn with_config(inner: T, config: McpHttpConfig) -> Self {
        Self { inner, config }
    }

    /// Runs the server
    ///
    /// # Parameters
    ///
    /// - `addr` - the listen address, in "host:port" format
    ///
    /// # Examples
    ///
    /// ```rust,ignore
    /// server.run("127.0.0.1:8080").await?;
    /// ```
    pub async fn run(&self, addr: &str) -> Result<(), Box<dyn std::error::Error>> {
        use axum::{extract::State, routing::get, Json, Router};
        use std::sync::Arc;

        let addr_parts: Vec<&str> = addr.split(':').collect();
        let host = addr_parts.first().unwrap_or(&"127.0.0.1");
        let port: u16 = addr_parts
            .get(1)
            .and_then(|s| s.parse().ok())
            .unwrap_or(8080);

        let app_state = Arc::new(AppState::new(to_mcp_tools(T::tool_definitions())));

        // 定义处理器
        async fn list_tools_handler(State(state): State<Arc<AppState>>) -> Json<Vec<McpTool>> {
            Json(state.tools.clone())
        }

        async fn call_tool_handler(Json(_request): Json<McpToolCall>) -> Json<McpToolResponse> {
            // 在实际应用中,这里会调用具体的工具实现
            Json(McpToolResponse::error("Tool execution requires concrete implementation. Use examples/mcp_http_server.rs for a complete example."))
        }

        async fn health_handler() -> &'static str {
            "OK"
        }

        let app = Router::new()
            .route("/tools", get(list_tools_handler))
            .route("/call", axum::routing::post(call_tool_handler))
            .route("/health", get(health_handler))
            .with_state(app_state);

        let listener = tokio::net::TcpListener::bind(format!("{}:{}", host, port)).await?;
        println!("MCP Server listening on http://{}:{}", host, port);
        println!("  - GET  /tools   - List available tools");
        println!("  - POST /call    - Call a tool");
        println!("  - GET  /health  - Health check");

        axum::serve(listener, app).await?;
        Ok(())
    }
}

/// Application state (used by the standalone HTTP server)
#[cfg(feature = "http-server")]
pub struct AppState {
    pub tools: Vec<McpTool>,
}

#[cfg(feature = "http-server")]
impl AppState {
    /// Creates a new application state
    pub fn new(tools: Vec<McpTool>) -> Self {
        Self { tools }
    }
}

// ============================================================================
// SSE (Server-Sent Events) 支持
// ============================================================================

/// MCP SSE message
#[cfg(all(feature = "http-server", feature = "runtime"))]
#[derive(Debug, Clone, Serialize)]
pub struct McpSseMessage {
    pub event: String,
    pub data: serde_json::Value,
}

#[cfg(all(feature = "http-server", feature = "runtime"))]
impl McpSseMessage {
    /// Build an SSE message advertising a `tools/list` event whose payload is
    /// the serialized `tools`. Used by the HTTP transport to push a fresh
    /// tool catalogue to subscribers.
    pub fn tool_list(tools: Vec<McpTool>) -> Self {
        Self {
            event: "tools/list".to_string(),
            data: serde_json::to_value(tools).unwrap_or_default(),
        }
    }

    /// Build an SSE message advertising a `tool/result` event whose payload
    /// is the serialized `result`. Used by the HTTP transport to push a
    /// tool call outcome to subscribers.
    pub fn tool_result(result: McpToolResponse) -> Self {
        Self {
            event: "tool/result".to_string(),
            data: serde_json::to_value(result).unwrap_or_default(),
        }
    }
}