Skip to main content

ultrafast_mcp_macros/
lib.rs

1//! # UltraFast MCP Macros
2//!
3//! Procedural macros for the UltraFast Model Context Protocol (MCP) implementation.
4//!
5//! This crate provides convenient procedural macros that simplify MCP development
6//! by automatically generating boilerplate code, schemas, and configurations.
7//! It reduces the amount of repetitive code needed to implement MCP servers and clients.
8//!
9//! ## Overview
10//!
11//! The UltraFast MCP Macros crate provides:
12//!
13//! - **Schema Generation**: Automatic JSON Schema generation from Rust types
14//! - **Tool Registration**: Simplified tool definition and registration
15//! - **Server Setup**: Streamlined server configuration and setup
16//! - **Client Configuration**: Easy client configuration and setup
17//! - **Request/Response**: Automatic request and response type generation
18//! - **Error Handling**: Simplified error type generation
19//!
20//! ## Key Features
21//!
22//! ### Automatic Schema Generation
23//! - **Type Inference**: Automatically infer JSON schemas from Rust types
24//! - **Custom Attributes**: Fine-tune schema generation with attributes
25//! - **Validation**: Generate validation rules from type constraints
26//! - **Documentation**: Preserve Rust documentation in generated schemas
27//! - **Nested Types**: Handle complex nested structures and enums
28//!
29//! ### Tool Registration
30//! - **Function Attributes**: Convert Rust functions into MCP tools
31//! - **Automatic Registration**: Generate tool registration code
32//! - **Schema Generation**: Create input/output schemas automatically
33//! - **Error Handling**: Integrate with MCP error types
34//! - **Async Support**: Full support for async functions
35//!
36//! ### Server and Client Setup
37//! - **Server Configuration**: Simplify server setup and configuration
38//! - **Client Configuration**: Easy client configuration management
39//! - **Capability Management**: Automatic capability configuration
40//! - **Info Generation**: Generate server/client information
41//! - **Type Safety**: Compile-time type checking and validation
42//!
43//! ## Macros
44//!
45//! ### `#[derive(McpSchema)]` - Schema Generation
46//! Automatically generates JSON schemas from Rust structs and enums.
47//!
48//! ```rust
49//! use ultrafast_mcp_macros::McpSchema;
50//! use serde::{Serialize, Deserialize};
51//!
52//! #[derive(McpSchema, Serialize, Deserialize)]
53//! struct UserInput {
54//!     name: String,
55//!     age: u32,
56//!     email: Option<String>,
57//!     #[mcp(description = "User preferences")]
58//!     preferences: Vec<String>,
59//! }
60//!
61//! // The macro generates:
62//! // - JSON schema for the struct
63//! // - Schema validation methods
64//! // - Type conversion utilities
65//! ```
66//!
67//! ### `#[mcp_tool]` - Tool Definition
68//! Converts Rust functions into MCP tools with automatic schema generation.
69//!
70//! ```rust
71//! use ultrafast_mcp_macros::mcp_tool;
72//! use serde_json::Value;
73//!
74//! #[mcp_tool(
75//!     name = "greet_user",
76//!     description = "Greet a user with a personalized message"
77//! )]
78//! async fn greet_user(input: Value) -> Result<String, Box<dyn std::error::Error>> {
79//!     let name = input["name"].as_str().unwrap_or("World");
80//!     let greeting = input["greeting"].as_str().unwrap_or("Hello");
81//!     Ok(format!("{}, {}!", greeting, name))
82//! }
83//!
84//! // The macro generates:
85//! // - Tool registration function
86//! // - Input/output schemas
87//! // - Error handling integration
88//! ```
89//!
90//! ### `#[mcp_server]` - Server Setup
91//! Simplifies MCP server setup and configuration.
92//!
93//! ```rust
94//! use ultrafast_mcp_macros::mcp_server;
95//!
96//! #[mcp_server(
97//!     name = "MyGreetingServer",
98//!     version = "1.0.0",
99//!     description = "A server that provides greeting tools"
100//! )]
101//! struct MyServer;
102//!
103//! // The macro generates:
104//! // - Server information
105//! // - Server capabilities
106//! // - Server setup methods
107//! ```
108//!
109//! ### `#[mcp_client]` - Client Configuration
110//! Simplifies MCP client configuration and setup.
111//!
112//! ```rust
113//! use ultrafast_mcp_macros::mcp_client;
114//!
115//! #[mcp_client(
116//!     name = "MyClient",
117//!     version = "1.0.0",
118//!     description = "A client for the greeting server"
119//! )]
120//! struct MyClient;
121//!
122//! // The macro generates:
123//! // - Client information
124//! // - Client capabilities
125//! // - Client setup methods
126//! ```
127//!
128//! ### `mcp_request!` - Request Type Generation
129//! Generates MCP request types with automatic validation.
130//!
131//! ```rust
132//! use ultrafast_mcp_macros::mcp_request;
133//! use ultrafast_mcp_core::protocol::jsonrpc::{JsonRpcRequest, RequestId};
134//! use serde_json::json;
135//!
136//! let request = mcp_request! {
137//!     method: "tools/list",
138//!     params: {},
139//!     id: 1
140//! };
141//! assert_eq!(request.method, "tools/list");
142//! assert_eq!(request.id, Some(RequestId::Number(1)));
143//! ```
144//!
145//! ### `mcp_response!` - Response Type Generation
146//! Generates MCP response types with automatic serialization.
147//!
148//! ```rust
149//! use ultrafast_mcp_macros::mcp_response;
150//! use ultrafast_mcp_core::protocol::jsonrpc::{JsonRpcResponse, RequestId};
151//! use serde_json::json;
152//!
153//! let response = mcp_response! {
154//!     result: {"status": "ok"},
155//!     id: 1
156//! };
157//! assert_eq!(response.id, Some(RequestId::Number(1)));
158//! ```
159//!
160//! ## Usage Examples
161//!
162//! ### Complete Tool Implementation
163//!
164//! ```rust
165//! use ultrafast_mcp_macros::{mcp_tool, McpSchema};
166//! use serde::{Serialize, Deserialize};
167//! use serde_json::Value;
168//!
169//! // Define input/output types with schemas
170//! #[derive(McpSchema, Serialize, Deserialize)]
171//! struct CalculatorInput {
172//!     operation: String,
173//!     a: f64,
174//!     b: f64,
175//! }
176//!
177//! #[derive(McpSchema, Serialize, Deserialize)]
178//! struct CalculatorOutput {
179//!     result: f64,
180//!     operation: String,
181//! }
182//!
183//! // Define the tool
184//! #[mcp_tool(
185//!     name = "calculate",
186//!     description = "Perform basic mathematical operations"
187//! )]
188//! async fn calculate(input: CalculatorInput) -> Result<CalculatorOutput, Box<dyn std::error::Error>> {
189//!     let result = match input.operation.as_str() {
190//!         "add" => input.a + input.b,
191//!         "subtract" => input.a - input.b,
192//!         "multiply" => input.a * input.b,
193//!         "divide" => {
194//!             if input.b == 0.0 {
195//!                 return Err("Division by zero".into());
196//!             }
197//!             input.a / input.b
198//!         }
199//!         _ => return Err("Unknown operation".into()),
200//!     };
201//!
202//!     Ok(CalculatorOutput {
203//!         result,
204//!         operation: input.operation,
205//!     })
206//! }
207//!
208//! // Example usage:
209//! // let tool = register_tool();
210//! // assert_eq!(tool.name, "calculate");
211//! ```
212//!
213//! ### Server with Multiple Tools
214//!
215//! ```rust
216//! use ultrafast_mcp_macros::{mcp_server, mcp_tool};
217//! use ultrafast_mcp_server::UltraFastServer;
218//! use ultrafast_mcp_core::types::tools::Tool;
219//!
220//! #[mcp_server(
221//!     name = "MathServer",
222//!     version = "1.0.0",
223//!     description = "A server providing mathematical tools"
224//! )]
225//! struct MathServer;
226//!
227//! #[mcp_tool(name = "add", description = "Add two numbers")]
228//! async fn add_tool(a: f64, b: f64) -> Result<f64, Box<dyn std::error::Error>> {
229//!     Ok(a + b)
230//! }
231//!
232//! #[mcp_tool(name = "multiply", description = "Multiply two numbers")]
233//! async fn multiply_tool(a: f64, b: f64) -> Result<f64, Box<dyn std::error::Error>> {
234//!     Ok(a * b)
235//! }
236//!
237//! // Example server setup (commented out to avoid async main issues in doctest):
238//! // #[tokio::main]
239//! // async fn main() -> anyhow::Result<()> {
240//! //     let server_info = MathServer::server_info();
241//! //     let server = UltraFastServer::new(server_info, Default::default());
242//! //     
243//! //     // Register tools using the generated functions
244//! //     let add_tool = register_add_tool_tool();
245//! //     let multiply_tool = register_multiply_tool_tool();
246//! //     
247//! //     server.run_stdio().await?;
248//! //     Ok(())
249//! // }
250//!
251//! // Example usage:
252//! let server_info = MathServer::server_info();
253//! assert_eq!(server_info.name, "MathServer");
254//! assert_eq!(server_info.version, "1.0.0");
255//!
256//! // Test the generated tool registration functions
257//! let add_tool = register_add_tool_tool();
258//! let multiply_tool = register_multiply_tool_tool();
259//! assert_eq!(add_tool.name, "add_tool");
260//! assert_eq!(multiply_tool.name, "multiply_tool");
261//! ```
262//!
263//! ### Client with Configuration
264//!
265//! ```rust
266//! use ultrafast_mcp_macros::{mcp_client, mcp_request, mcp_response};
267//! use serde::{Serialize, Deserialize};
268//! use ultrafast_mcp_core::types::tools::ToolCall;
269//!
270//! #[mcp_client(
271//!     name = "MathClient",
272//!     version = "1.0.0",
273//!     description = "A client for mathematical operations"
274//! )]
275//! struct MathClient;
276//!
277//! #[derive(Serialize, Deserialize)]
278//! struct AddRequest {
279//!     a: f64,
280//!     b: f64,
281//! }
282//!
283//! #[derive(Serialize, Deserialize)]
284//! struct AddResponse {
285//!     result: f64,
286//! }
287//!
288//! // Example client setup (commented out to avoid async main issues in doctest):
289//! // #[tokio::main]
290//! // async fn main() -> anyhow::Result<()> {
291//! //     let client_info = MathClient::client_info();
292//! //     let client = ultrafast_mcp_client::UltraFastClient::new(client_info, Default::default());
293//! //     
294//! //     client.connect_http("http://localhost:8080/mcp").await?;
295//! //     
296//! //     let request = AddRequest { a: 5.0, b: 3.0 };
297//! //     let tool_call = ToolCall {
298//! //         name: "add".to_string(),
299//! //         arguments: Some(serde_json::to_value(request)?),
300//! //     };
301//! //     let response = client.call_tool(tool_call).await?;
302//! //     
303//! //     println!("Result: {:?}", response);
304//! //     Ok(())
305//! // }
306//!
307//! // Example usage:
308//! // let client_info = MathClient::client_info();
309//! // assert_eq!(client_info.name, "MathClient");
310//! ```
311//!
312//! ## Schema Attributes
313//!
314//! The `McpSchema` derive macro supports various attributes for customizing schema generation:
315//!
316//! ```rust
317//! use ultrafast_mcp_macros::McpSchema;
318//! use serde::{Serialize, Deserialize};
319//!
320//! #[derive(McpSchema, Serialize, Deserialize)]
321//! struct User {
322//!     #[mcp(description = "User's full name")]
323//!     name: String,
324//!     
325//!     #[mcp(minimum = 0, maximum = 150)]
326//!     age: u32,
327//!     
328//!     #[mcp(format = "email")]
329//!     email: String,
330//!     
331//!     #[mcp(min_length = 8)]
332//!     password: String,
333//!     
334//!     #[mcp(required = false)]
335//!     bio: Option<String>,
336//! }
337//! ```
338//!
339//! ## Error Handling
340//!
341//! The macros integrate seamlessly with MCP error handling:
342//!
343//! ```rust
344//! use ultrafast_mcp_macros::mcp_tool;
345//! use ultrafast_mcp_core::MCPError;
346//!
347//! #[mcp_tool(name = "risky_operation")]
348//! async fn risky_operation(input: String) -> Result<String, MCPError> {
349//!     if input.is_empty() {
350//!         return Err(MCPError::invalid_params("Input cannot be empty".to_string()));
351//!     }
352//!     
353//!     if input.len() > 1000 {
354//!         return Err(MCPError::invalid_params("Input too long".to_string()));
355//!     }
356//!     
357//!     Ok(format!("Processed: {}", input))
358//! }
359//! ```
360//!
361//! ## Performance Considerations
362//!
363//! - **Compile-time Generation**: All code is generated at compile time
364//! - **Zero Runtime Overhead**: No runtime reflection or dynamic code generation
365//! - **Optimized Schemas**: Efficient schema generation and validation
366//! - **Minimal Allocations**: Optimized for minimal memory usage
367//! - **Fast Serialization**: Efficient serialization/deserialization
368//!
369//! ## Best Practices
370//!
371//! ### Schema Design
372//! - Use descriptive field names and types
373//! - Add meaningful descriptions with attributes
374//! - Use appropriate validation constraints
375//! - Keep schemas simple and focused
376//! - Document complex schemas thoroughly
377//!
378//! ### Tool Implementation
379//! - Use strongly-typed input/output types
380//! - Implement proper error handling
381//! - Add meaningful descriptions
382//! - Keep tools focused and single-purpose
383//! - Test tools thoroughly
384//!
385//! ### Server/Client Setup
386//! - Use descriptive names and versions
387//! - Provide meaningful descriptions
388//! - Configure appropriate capabilities
389//! - Implement proper error handling
390//! - Follow naming conventions
391//!
392//! ## Thread Safety
393//!
394//! All generated code is designed to be thread-safe:
395//! - Generated types implement `Send + Sync` where appropriate
396//! - No mutable global state is used
397//! - Concurrent access is supported
398//! - Safe for use in async contexts
399//!
400//! ## Examples
401//!
402//! See the `examples/` directory for complete working examples:
403//! - Basic tool implementation
404//! - Server with multiple tools
405//! - Client configuration
406//! - Schema customization
407//! - Error handling patterns
408
409use proc_macro::TokenStream;
410use quote::quote;
411use syn::{parse_macro_input, DeriveInput, ItemFn, ItemStruct};
412
413/// Derive macro for automatic JSON Schema generation
414///
415/// # Example
416/// ```rust
417/// use ultrafast_mcp_macros::McpSchema;
418/// use serde::{Serialize, Deserialize};
419/// use ultrafast_mcp_core::schema::McpSchema as McpSchemaTrait;
420///
421/// #[derive(McpSchema, Serialize, Deserialize)]
422/// struct MyTool {
423///     name: String,
424///     value: i32,
425/// }
426///
427/// // The macro generates an implementation of McpSchema
428/// let schema = MyTool::schema();
429/// let schema_name = MyTool::schema_name();
430/// assert_eq!(schema_name, "MyTool");
431/// ```
432#[proc_macro_derive(McpSchema, attributes(mcp))]
433pub fn derive_mcp_schema(input: TokenStream) -> TokenStream {
434    let input = parse_macro_input!(input as DeriveInput);
435    let name = &input.ident;
436    let generics = &input.generics;
437    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
438
439    let schema_impl = quote! {
440        impl #impl_generics ultrafast_mcp_core::schema::McpSchema for #name #ty_generics #where_clause {
441            fn schema() -> serde_json::Value {
442                serde_json::json!({
443                    "type": "object",
444                    "properties": {},
445                    "additionalProperties": false
446                })
447            }
448
449            fn schema_name() -> String {
450                stringify!(#name).to_string()
451            }
452        }
453    };
454
455    TokenStream::from(schema_impl)
456}
457
458/// Attribute macro for defining MCP tools
459///
460/// # Example
461/// ```rust
462/// use ultrafast_mcp_macros::mcp_tool;
463/// use serde_json;
464///
465/// #[mcp_tool(name = "echo", description = "Echo back the input")]
466/// async fn echo_tool(input: String) -> Result<String, Box<dyn std::error::Error>> {
467///     Ok(input)
468/// }
469/// // Example of constructing a Tool struct:
470/// let tool = ultrafast_mcp_core::types::tools::Tool {
471///     name: "echo".to_string(),
472///     description: "Echo back the input".to_string(),
473///     input_schema: serde_json::json!({
474///         "type": "object",
475///         "properties": {},
476///         "required": []
477///     }),
478///     output_schema: Some(serde_json::json!({})),
479/// };
480/// ```
481#[proc_macro_attribute]
482pub fn mcp_tool(_args: TokenStream, input: TokenStream) -> TokenStream {
483    let input_fn = parse_macro_input!(input as ItemFn);
484
485    // For now, just use defaults - in a real implementation you'd parse the args properly
486    let fn_name = &input_fn.sig.ident;
487    let tool_name = fn_name.to_string();
488    let description = format!("Tool: {}", tool_name);
489    let register_fn_name = quote::format_ident!("register_{}_tool", fn_name);
490
491    let expanded = quote! {
492        #input_fn
493
494        // Generate tool registration function
495        pub fn #register_fn_name() -> ultrafast_mcp_core::types::tools::Tool {
496            ultrafast_mcp_core::types::tools::Tool {
497                name: #tool_name.to_string(),
498                description: #description.to_string(),
499                input_schema: serde_json::json!({
500                    "type": "object",
501                    "properties": {},
502                    "required": []
503                }),
504                output_schema: Some(serde_json::json!({})),
505            }
506        }
507    };
508
509    TokenStream::from(expanded)
510}
511
512/// Attribute macro for MCP server setup
513///
514/// # Example
515/// ```rust
516/// use ultrafast_mcp_macros::mcp_server;
517///
518/// #[mcp_server(name = "MyServer", version = "1.0.0")]
519/// struct MyServer;
520///
521/// // Example of creating a server:
522/// let server = ultrafast_mcp_server::UltraFastServer::new(
523///     ultrafast_mcp_core::types::server::ServerInfo {
524///         name: "MyServer".to_string(),
525///         version: "1.0.0".to_string(),
526///         description: None,
527///         homepage: None,
528///         repository: None,
529///         authors: None,
530///         license: None,
531///     },
532///     ultrafast_mcp_core::types::server::ServerCapabilities::default(),
533/// );
534/// ```
535#[proc_macro_attribute]
536pub fn mcp_server(_args: TokenStream, input: TokenStream) -> TokenStream {
537    let input_struct = parse_macro_input!(input as ItemStruct);
538
539    // For now, just use defaults - in a real implementation you'd parse the args properly
540    let struct_name = &input_struct.ident;
541    let server_name = struct_name.to_string();
542    let version = "1.0.0";
543
544    let expanded = quote! {
545        #input_struct
546
547        impl #struct_name {
548            /// Get server information
549            pub fn server_info() -> ultrafast_mcp_core::types::server::ServerInfo {
550                ultrafast_mcp_core::types::server::ServerInfo {
551                    name: #server_name.to_string(),
552                    version: #version.to_string(),
553                    description: None,
554                    homepage: None,
555                    repository: None,
556                    authors: None,
557                    license: None,
558                }
559            }
560
561            /// Create a new server
562            pub fn new() -> ultrafast_mcp_server::UltraFastServer {
563                ultrafast_mcp_server::UltraFastServer::new(
564                    Self::server_info(),
565                    ultrafast_mcp_core::types::server::ServerCapabilities::default(),
566                )
567            }
568        }
569    };
570
571    TokenStream::from(expanded)
572}
573
574/// Macro for creating MCP client configurations
575///
576/// # Example
577/// ```rust
578/// use ultrafast_mcp_macros::mcp_client_config;
579///
580/// mcp_client_config! {
581///     name: "MyClient",
582///     version: "1.0.0",
583///     capabilities: {
584///         experimental: {},
585///         sampling: {}
586///     }
587/// }
588/// ```
589#[proc_macro]
590pub fn mcp_client_config(_input: TokenStream) -> TokenStream {
591    // For now, just return a placeholder - in a real implementation you'd parse the input
592    let expanded = quote! {
593        // Client configuration would be generated here
594        pub struct ClientConfig;
595    };
596
597    TokenStream::from(expanded)
598}
599
600/// Macro for creating MCP requests
601///
602/// # Example
603/// ```rust
604/// use ultrafast_mcp_macros::mcp_request;
605///
606/// let request = mcp_request! {
607///     method: "tools/list",
608///     params: {},
609///     id: 1
610/// };
611/// ```
612#[proc_macro]
613pub fn mcp_request(_input: TokenStream) -> TokenStream {
614    // For now, just return a placeholder - in a real implementation you'd parse the input
615    let expanded = quote! {
616        ultrafast_mcp_core::protocol::jsonrpc::JsonRpcRequest::new(
617            "tools/list".to_string(),
618            Some(serde_json::json!({})),
619            Some(ultrafast_mcp_core::protocol::jsonrpc::RequestId::Number(1))
620        )
621    };
622
623    TokenStream::from(expanded)
624}
625
626/// Macro for creating MCP responses
627///
628/// # Example
629/// ```rust
630/// use ultrafast_mcp_macros::mcp_response;
631///
632/// let response = mcp_response! {
633///     result: {"status": "ok"},
634///     id: 1
635/// };
636/// ```
637#[proc_macro]
638pub fn mcp_response(_input: TokenStream) -> TokenStream {
639    // For now, just return a placeholder - in a real implementation you'd parse the input
640    let expanded = quote! {
641        ultrafast_mcp_core::protocol::jsonrpc::JsonRpcResponse::success(
642            serde_json::json!({"status": "ok"}),
643            Some(ultrafast_mcp_core::protocol::jsonrpc::RequestId::Number(1))
644        )
645    };
646
647    TokenStream::from(expanded)
648}
649
650/// Macro for creating MCP errors
651///
652/// # Example
653/// ```rust
654/// use ultrafast_mcp_macros::mcp_error;
655///
656/// let error = mcp_error! {
657///     code: -32602,
658///     message: "Invalid params",
659///     id: 1
660/// };
661/// ```
662#[proc_macro]
663pub fn mcp_error(_input: TokenStream) -> TokenStream {
664    // For now, just return a placeholder - in a real implementation you'd parse the input
665    let expanded = quote! {
666        ultrafast_mcp_core::protocol::jsonrpc::JsonRpcResponse::error(
667            ultrafast_mcp_core::protocol::jsonrpc::JsonRpcError::new(-32602, "Invalid params".to_string()),
668            Some(ultrafast_mcp_core::protocol::jsonrpc::RequestId::Number(1))
669        )
670    };
671
672    TokenStream::from(expanded)
673}
674
675#[proc_macro_attribute]
676pub fn mcp_client(_args: TokenStream, input: TokenStream) -> TokenStream {
677    let input_struct = parse_macro_input!(input as ItemStruct);
678
679    // For now, just use defaults - in a real implementation you'd parse the args properly
680    let struct_name = &input_struct.ident;
681    let client_name = struct_name.to_string();
682    let version = "1.0.0";
683
684    let expanded = quote! {
685        #input_struct
686
687        impl #struct_name {
688            /// Get client information
689            pub fn client_info() -> ultrafast_mcp_core::types::client::ClientInfo {
690                ultrafast_mcp_core::types::client::ClientInfo {
691                    name: #client_name.to_string(),
692                    version: #version.to_string(),
693                    description: None,
694                    homepage: None,
695                    repository: None,
696                    authors: None,
697                    license: None,
698                }
699            }
700        }
701    };
702
703    TokenStream::from(expanded)
704}