turbomcp_macros/
lib.rs

1//! # TurboMCP Macros
2//!
3//! Zero-overhead procedural macros for ergonomic MCP server development, providing
4//! compile-time code generation for MCP protocol handlers with graceful shutdown support.
5//!
6//! ## Features
7//!
8//! ### Core Macros
9//! - **`#[server]`** - Convert structs into MCP servers with transport methods and graceful shutdown
10//! - **`#[tool]`** - Mark methods as MCP tool handlers with automatic schema generation
11//! - **`#[prompt]`** - Mark methods as MCP prompt handlers with template support
12//! - **`#[resource]`** - Mark methods as MCP resource handlers with URI templates
13//!
14//! ### Advanced Features
15//! - **Roots Configuration** - Declarative filesystem roots in `#[server]` macro: `root = "file:///path:Name"`
16//! - **Compile-Time Routing** - Zero-cost compile-time router generation (experimental)
17//! - **Enhanced Context System** - Improved async handling and error propagation
18//! - **Server Attributes** - Support for name, version, description, and roots in server macro
19//!
20//! ### Helper Macros
21//! - **`mcp_error!`** - Ergonomic error creation with formatting
22//! - **`mcp_text!`** - Text content creation helpers
23//! - **`tool_result!`** - Tool result formatting
24//! - **`elicit!`** - High-level elicitation macro for interactive user input
25//!
26//! ## Usage
27//!
28//! ### Basic Server with Tools
29//!
30//! ```ignore
31//! use turbomcp::prelude::*;
32//!
33//! #[derive(Clone)]
34//! struct Calculator {
35//!     operations: std::sync::Arc<std::sync::atomic::AtomicU64>,
36//! }
37//!
38//! #[server(
39//!     name = "calculator-server",
40//!     version = "1.0.0",
41//!     description = "A mathematical calculator service",
42//!     root = "file:///workspace:Project Workspace",
43//!     root = "file:///tmp:Temporary Files"
44//! )]
45//! impl Calculator {
46//!     #[tool("Add two numbers")]
47//!     async fn add(&self, ctx: Context, a: i32, b: i32) -> McpResult<i32> {
48//!         ctx.info(&format!("Adding {} + {}", a, b)).await?;
49//!         self.operations.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
50//!         Ok(a + b)
51//!     }
52//!     
53//!     #[tool("Divide two numbers")]
54//!     async fn divide(&self, a: f64, b: f64) -> McpResult<f64> {
55//!         if b == 0.0 {
56//!             return Err(mcp_error!("Cannot divide by zero"));
57//!         }
58//!         Ok(a / b)
59//!     }
60//!
61//!     #[resource("calc://history/{operation}")]
62//!     async fn history(&self, operation: String) -> McpResult<String> {
63//!         Ok(format!("History for {} operations", operation))
64//!     }
65//!
66//!     #[prompt("Generate report for {operation} with {count} operations")]
67//!     async fn report(&self, operation: String, count: i32) -> McpResult<String> {
68//!         Ok(format!("Generated report for {} ({} operations)", operation, count))
69//!     }
70//! }
71//! ```
72//!
73//! ### Elicitation Support
74//!
75//! ```ignore
76//! use turbomcp::prelude::*;
77//! use turbomcp::elicitation_api::{string, boolean, ElicitationResult};
78//!
79//! #[derive(Clone)]
80//! struct InteractiveServer;
81//!
82//! #[server]
83//! impl InteractiveServer {
84//!     #[tool("Configure with user input")]
85//!     async fn configure(&self, ctx: Context) -> McpResult<String> {
86//!         let result = elicit!("Configure your preferences")
87//!             .field("theme", string()
88//!                 .enum_values(vec!["light", "dark"])
89//!                 .build())
90//!             .field("auto_save", boolean()
91//!                 .description("Enable auto-save")
92//!                 .build())
93//!             .send(&ctx.request)
94//!             .await?;
95//!
96//!         match result {
97//!             ElicitationResult::Accept(data) => {
98//!                 let theme = data.get::<String>("theme")?;
99//!                 Ok(format!("Configured with {} theme", theme))
100//!             }
101//!             _ => Err(mcp_error!("Configuration cancelled"))
102//!         }
103//!     }
104//! }
105//! ```
106
107use proc_macro::TokenStream;
108
109mod attrs;
110mod bidirectional_wrapper;
111mod compile_time_router;
112mod completion;
113mod context_aware_dispatch;
114mod elicitation;
115mod helpers;
116mod ping;
117mod prompt;
118mod resource;
119mod schema;
120mod server;
121mod template;
122mod tool;
123mod uri_template;
124
125/// Marks an impl block as a TurboMCP server (idiomatic Rust)
126///
127/// # Example
128///
129/// ```text
130/// use turbomcp_macros::server;
131///
132/// struct MyServer {
133///     state: String,
134/// }
135///
136/// #[server(name = "MyServer", version = "1.0.0")]
137/// impl MyServer {
138///     fn new(state: String) -> Self {
139///         Self { state }
140///     }
141///
142///     fn get_state(&self) -> &str {
143///         &self.state
144///     }
145/// }
146/// ```
147#[proc_macro_attribute]
148pub fn server(args: TokenStream, input: TokenStream) -> TokenStream {
149    // Implementation - only supports impl blocks (the correct pattern)
150    match syn::parse::<syn::ItemImpl>(input) {
151        Ok(item_impl) => server::generate_server_impl(args, item_impl),
152        Err(_) => syn::Error::new(
153            proc_macro2::Span::call_site(),
154            "The #[server] attribute can only be applied to impl blocks. \
155                 This is the idiomatic Rust pattern that separates data from behavior.",
156        )
157        .to_compile_error()
158        .into(),
159    }
160}
161
162/// Marks a method as a tool handler
163///
164/// # Example
165///
166/// ```ignore
167/// use turbomcp_macros::tool;
168///
169/// struct MyServer;
170///
171/// impl MyServer {
172///     #[tool("Add two numbers")]
173///     async fn add(&self, a: i32, b: i32) -> turbomcp::McpResult<i32> {
174///         Ok(a + b)
175///     }
176/// }
177#[proc_macro_attribute]
178pub fn tool(args: TokenStream, input: TokenStream) -> TokenStream {
179    tool::generate_tool_impl(args, input)
180}
181
182/// Marks a method as a prompt handler
183///
184/// # Example
185///
186/// ```ignore
187/// # use turbomcp_macros::prompt;
188/// # struct MyServer;
189/// # impl MyServer {
190/// #[prompt("Generate code")]
191/// async fn code_prompt(&self, language: String) -> turbomcp::McpResult<String> {
192///     Ok(format!("Generated {} code", language))
193/// }
194/// # }
195#[proc_macro_attribute]
196pub fn prompt(args: TokenStream, input: TokenStream) -> TokenStream {
197    prompt::generate_prompt_impl(args, input)
198}
199
200/// Marks a method as a resource handler
201///
202/// # Example
203///
204/// ```ignore
205/// # use turbomcp_macros::resource;
206/// # struct MyServer;
207/// # impl MyServer {
208/// #[resource("config://settings/{section}")]
209/// async fn get_config(&self, section: String) -> turbomcp::McpResult<String> {
210///     Ok(format!("Config for section: {}", section))
211/// }
212/// # }
213#[proc_macro_attribute]
214pub fn resource(args: TokenStream, input: TokenStream) -> TokenStream {
215    resource::generate_resource_impl(args, input)
216}
217
218/// Helper macro for creating MCP ContentBlock structures (advanced usage)
219///
220/// **Note:** Most tool functions should simply return `String` using `format!()`.
221/// Only use `mcp_text!()` when manually building CallToolResult structures.
222///
223/// # Common Usage (90% of cases) ✅
224/// ```ignore
225/// use turbomcp::prelude::*;
226///
227/// #[tool("Say hello")]
228/// async fn hello(&self, name: String) -> turbomcp::McpResult<String> {
229///     Ok(format!("Hello, {}!", name))  // ✅ Use format! for #[tool] returns
230/// }
231/// ```
232///
233/// # Advanced Usage (rare) ⚠️
234/// ```ignore
235/// # use turbomcp_macros::mcp_text;
236/// let name = "world";
237/// let content_block = mcp_text!("Hello, {}!", name);
238/// // Use in manual CallToolResult construction
239/// ```
240#[proc_macro]
241pub fn mcp_text(input: TokenStream) -> TokenStream {
242    helpers::generate_text_content(input)
243}
244
245/// Helper macro for creating MCP errors
246///
247/// # Example
248///
249/// ```ignore
250/// # use turbomcp_macros::mcp_error;
251/// let error = "connection failed";
252/// let result = mcp_error!("Something went wrong: {}", error);
253/// ```
254#[proc_macro]
255pub fn mcp_error(input: TokenStream) -> TokenStream {
256    helpers::generate_error(input)
257}
258
259/// Ergonomic elicitation macro for server-initiated user input
260///
261/// This macro provides a simple way to request structured input from the client
262/// with automatic error handling and context integration.
263///
264/// # Usage Patterns
265///
266/// ## Simple Prompt (No Schema)
267/// ```ignore
268/// use turbomcp::prelude::*;
269///
270/// // Simple yes/no or text prompt
271/// let result = elicit!(ctx, "Continue with deployment?").await?;
272/// ```
273///
274/// ## With Schema Validation
275/// ```ignore
276/// use turbomcp::prelude::*;
277/// use ::turbomcp::turbomcp_protocol::types::ElicitationSchema;
278///
279/// let schema = ElicitationSchema::new()
280///     .add_string_property("theme", Some("Color theme"))
281///     .add_boolean_property("notifications", Some("Enable notifications"));
282///
283/// let result = elicit!(ctx, "Configure your preferences", schema).await?;
284/// ```
285///
286/// # Arguments
287///
288/// * `ctx` - The context object (RequestContext with server capabilities)
289/// * `message` - The message to display to the user
290/// * `schema` - (Optional) The elicitation schema defining expected input
291///
292/// # Returns
293///
294/// Returns `Result<ElicitationResult>` which can be:
295/// - `ElicitationResult::Accept(data)` - User provided input
296/// - `ElicitationResult::Decline(reason)` - User declined
297/// - `ElicitationResult::Cancel` - User cancelled
298///
299/// # When to Use
300///
301/// Use the macro for:
302/// - Simple prompts without complex schemas
303/// - Quick confirmation dialogs
304/// - Reduced boilerplate in tool handlers
305///
306/// Use the function API for:
307/// - Complex schemas with multiple fields
308/// - Reusable elicitation builders
309/// - Maximum control over schema construction
310///
311#[proc_macro]
312pub fn elicit(input: TokenStream) -> TokenStream {
313    helpers::generate_elicitation(input)
314}
315
316/// Helper macro for creating CallToolResult structures (advanced usage)
317///
318/// **Note:** The `#[tool]` attribute automatically creates CallToolResult for you.
319/// Only use `tool_result!()` when manually building responses outside of `#[tool]` functions.
320///
321/// # Common Usage (automatic) ✅  
322/// ```ignore
323/// use turbomcp::prelude::*;
324///
325/// #[tool("Process data")]
326/// async fn process(&self, data: String) -> turbomcp::McpResult<String> {
327///     Ok(format!("Processed: {}", data))  // ✅ Automatic CallToolResult creation
328/// }
329/// ```
330///
331/// # Advanced Usage (manual) ⚠️
332/// ```ignore
333/// # use turbomcp_macros::{tool_result, mcp_text};
334/// let value = 42;
335/// let text_content = mcp_text!("Result: {}", value);
336/// let result = tool_result!(text_content);  // Manual CallToolResult creation
337/// ```
338#[proc_macro]
339pub fn tool_result(input: TokenStream) -> TokenStream {
340    helpers::generate_tool_result(input)
341}
342
343/// Marks a method as an elicitation handler for gathering user input
344///
345/// Elicitation allows servers to request structured input from clients
346/// with JSON schema validation and optional default values.
347///
348/// # Example
349///
350/// ```ignore
351/// # use turbomcp_macros::elicitation;
352/// # struct MyServer;
353/// # impl MyServer {
354/// #[elicitation("Collect user preferences")]
355/// async fn get_preferences(&self, schema: serde_json::Value) -> turbomcp::McpResult<serde_json::Value> {
356///     // Implementation would send elicitation request to client
357///     // and return the structured user input
358///     Ok(serde_json::json!({"theme": "dark", "language": "en"}))
359/// }
360/// # }
361#[proc_macro_attribute]
362pub fn elicitation(args: TokenStream, input: TokenStream) -> TokenStream {
363    elicitation::generate_elicitation_impl(args, input)
364}
365
366/// Marks a method as a completion handler for argument autocompletion
367///
368/// Completion provides intelligent suggestions for tool parameters
369/// based on current context and partial input.
370///
371/// # Example
372///
373/// ```ignore
374/// # use turbomcp_macros::completion;
375/// # struct MyServer;
376/// # impl MyServer {
377/// #[completion("Complete file paths")]
378/// async fn complete_file_path(&self, partial: String) -> turbomcp::McpResult<Vec<String>> {
379///     // Return completion suggestions based on partial input
380///     Ok(vec!["config.json".to_string(), "data.txt".to_string()])
381/// }
382/// # }
383#[proc_macro_attribute]
384pub fn completion(args: TokenStream, input: TokenStream) -> TokenStream {
385    completion::generate_completion_impl(args, input)
386}
387
388/// Marks a method as a resource template handler
389///
390/// Resource templates use RFC 6570 URI templates for parameterized
391/// resource access, enabling dynamic resource URIs.
392///
393/// # Example
394///
395/// ```ignore
396/// # use turbomcp_macros::template;
397/// # struct MyServer;
398/// # impl MyServer {
399/// #[template("users/{user_id}/profile")]
400/// async fn get_user_profile(&self, user_id: String) -> turbomcp::McpResult<String> {
401///     // Return resource content for the templated URI
402///     Ok(format!("Profile for user: {}", user_id))
403/// }
404/// # }
405#[proc_macro_attribute]
406pub fn template(args: TokenStream, input: TokenStream) -> TokenStream {
407    template::generate_template_impl(args, input)
408}
409
410/// Marks a method as a ping handler for connection health monitoring
411///
412/// Ping handlers enable bidirectional health checks between
413/// clients and servers for connection monitoring.
414///
415/// # Example
416///
417/// ```ignore
418/// # use turbomcp_macros::ping;
419/// # struct MyServer;
420/// # impl MyServer {
421/// #[ping("Health check")]
422/// async fn health_check(&self) -> turbomcp::McpResult<String> {
423///     // Return health status information
424///     Ok("Server is healthy".to_string())
425/// }
426/// # }
427#[proc_macro_attribute]
428pub fn ping(args: TokenStream, input: TokenStream) -> TokenStream {
429    ping::generate_ping_impl(args, input)
430}