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 (Enhanced in 1.0.3)
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 (New in 1.0.3)
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 compile_time_router;
111mod completion;
112mod elicitation;
113mod helpers;
114mod ping;
115mod prompt;
116mod resource;
117mod schema;
118mod server;
119mod template;
120mod tool;
121mod uri_template;
122
123/// Marks an impl block as a TurboMCP server (idiomatic Rust)
124///
125/// # Example
126///
127/// ```text
128/// use turbomcp_macros::server;
129///
130/// struct MyServer {
131/// state: String,
132/// }
133///
134/// #[server(name = "MyServer", version = "1.0.0")]
135/// impl MyServer {
136/// fn new(state: String) -> Self {
137/// Self { state }
138/// }
139///
140/// fn get_state(&self) -> &str {
141/// &self.state
142/// }
143/// }
144/// ```
145#[proc_macro_attribute]
146pub fn server(args: TokenStream, input: TokenStream) -> TokenStream {
147 // Implementation - only supports impl blocks (the correct pattern)
148 match syn::parse::<syn::ItemImpl>(input) {
149 Ok(item_impl) => server::generate_server_impl(args, item_impl),
150 Err(_) => syn::Error::new(
151 proc_macro2::Span::call_site(),
152 "The #[server] attribute can only be applied to impl blocks. \
153 This is the idiomatic Rust pattern that separates data from behavior.",
154 )
155 .to_compile_error()
156 .into(),
157 }
158}
159
160/// Marks a method as a tool handler
161///
162/// # Example
163///
164/// ```ignore
165/// use turbomcp_macros::tool;
166///
167/// struct MyServer;
168///
169/// impl MyServer {
170/// #[tool("Add two numbers")]
171/// async fn add(&self, a: i32, b: i32) -> turbomcp::McpResult<i32> {
172/// Ok(a + b)
173/// }
174/// }
175#[proc_macro_attribute]
176pub fn tool(args: TokenStream, input: TokenStream) -> TokenStream {
177 tool::generate_tool_impl(args, input)
178}
179
180/// Marks a method as a prompt handler
181///
182/// # Example
183///
184/// ```ignore
185/// # use turbomcp_macros::prompt;
186/// # struct MyServer;
187/// # impl MyServer {
188/// #[prompt("Generate code")]
189/// async fn code_prompt(&self, language: String) -> turbomcp::McpResult<String> {
190/// Ok(format!("Generated {} code", language))
191/// }
192/// # }
193#[proc_macro_attribute]
194pub fn prompt(args: TokenStream, input: TokenStream) -> TokenStream {
195 prompt::generate_prompt_impl(args, input)
196}
197
198/// Marks a method as a resource handler
199///
200/// # Example
201///
202/// ```ignore
203/// # use turbomcp_macros::resource;
204/// # struct MyServer;
205/// # impl MyServer {
206/// #[resource("config://settings/{section}")]
207/// async fn get_config(&self, section: String) -> turbomcp::McpResult<String> {
208/// Ok(format!("Config for section: {}", section))
209/// }
210/// # }
211#[proc_macro_attribute]
212pub fn resource(args: TokenStream, input: TokenStream) -> TokenStream {
213 resource::generate_resource_impl(args, input)
214}
215
216/// Helper macro for creating MCP ContentBlock structures (advanced usage)
217///
218/// **Note:** Most tool functions should simply return `String` using `format!()`.
219/// Only use `mcp_text!()` when manually building CallToolResult structures.
220///
221/// # Common Usage (90% of cases) ✅
222/// ```ignore
223/// use turbomcp::prelude::*;
224///
225/// #[tool("Say hello")]
226/// async fn hello(&self, name: String) -> turbomcp::McpResult<String> {
227/// Ok(format!("Hello, {}!", name)) // ✅ Use format! for #[tool] returns
228/// }
229/// ```
230///
231/// # Advanced Usage (rare) ⚠️
232/// ```ignore
233/// # use turbomcp_macros::mcp_text;
234/// let name = "world";
235/// let content_block = mcp_text!("Hello, {}!", name);
236/// // Use in manual CallToolResult construction
237/// ```
238#[proc_macro]
239pub fn mcp_text(input: TokenStream) -> TokenStream {
240 helpers::generate_text_content(input)
241}
242
243/// Helper macro for creating MCP errors
244///
245/// # Example
246///
247/// ```ignore
248/// # use turbomcp_macros::mcp_error;
249/// let error = "connection failed";
250/// let result = mcp_error!("Something went wrong: {}", error);
251/// ```
252#[proc_macro]
253pub fn mcp_error(input: TokenStream) -> TokenStream {
254 helpers::generate_error(input)
255}
256
257/// Ergonomic elicitation macro for server-initiated user input
258///
259/// This macro provides a simple way to request structured input from the client
260/// with automatic error handling and context integration.
261///
262/// # Usage Patterns
263///
264/// ## Simple Prompt (No Schema)
265/// ```ignore
266/// use turbomcp::prelude::*;
267///
268/// // Simple yes/no or text prompt
269/// let result = elicit!(ctx, "Continue with deployment?").await?;
270/// ```
271///
272/// ## With Schema Validation
273/// ```ignore
274/// use turbomcp::prelude::*;
275/// use ::turbomcp::turbomcp_protocol::elicitation::ElicitationSchema;
276///
277/// let schema = ElicitationSchema::new()
278/// .add_string_property("theme", Some("Color theme"))
279/// .add_boolean_property("notifications", Some("Enable notifications"));
280///
281/// let result = elicit!(ctx, "Configure your preferences", schema).await?;
282/// ```
283///
284/// # Arguments
285///
286/// * `ctx` - The context object (RequestContext with server capabilities)
287/// * `message` - The message to display to the user
288/// * `schema` - (Optional) The elicitation schema defining expected input
289///
290/// # Returns
291///
292/// Returns `Result<ElicitationResult>` which can be:
293/// - `ElicitationResult::Accept(data)` - User provided input
294/// - `ElicitationResult::Decline(reason)` - User declined
295/// - `ElicitationResult::Cancel` - User cancelled
296///
297/// # When to Use
298///
299/// Use the macro for:
300/// - Simple prompts without complex schemas
301/// - Quick confirmation dialogs
302/// - Reduced boilerplate in tool handlers
303///
304/// Use the function API for:
305/// - Complex schemas with multiple fields
306/// - Reusable elicitation builders
307/// - Maximum control over schema construction
308///
309#[proc_macro]
310pub fn elicit(input: TokenStream) -> TokenStream {
311 helpers::generate_elicitation(input)
312}
313
314/// Helper macro for creating CallToolResult structures (advanced usage)
315///
316/// **Note:** The `#[tool]` attribute automatically creates CallToolResult for you.
317/// Only use `tool_result!()` when manually building responses outside of `#[tool]` functions.
318///
319/// # Common Usage (automatic) ✅
320/// ```ignore
321/// use turbomcp::prelude::*;
322///
323/// #[tool("Process data")]
324/// async fn process(&self, data: String) -> turbomcp::McpResult<String> {
325/// Ok(format!("Processed: {}", data)) // ✅ Automatic CallToolResult creation
326/// }
327/// ```
328///
329/// # Advanced Usage (manual) ⚠️
330/// ```ignore
331/// # use turbomcp_macros::{tool_result, mcp_text};
332/// let value = 42;
333/// let text_content = mcp_text!("Result: {}", value);
334/// let result = tool_result!(text_content); // Manual CallToolResult creation
335/// ```
336#[proc_macro]
337pub fn tool_result(input: TokenStream) -> TokenStream {
338 helpers::generate_tool_result(input)
339}
340
341/// Marks a method as an elicitation handler for gathering user input
342///
343/// Elicitation allows servers to request structured input from clients
344/// with JSON schema validation and optional default values.
345///
346/// # Example
347///
348/// ```ignore
349/// # use turbomcp_macros::elicitation;
350/// # struct MyServer;
351/// # impl MyServer {
352/// #[elicitation("Collect user preferences")]
353/// async fn get_preferences(&self, schema: serde_json::Value) -> turbomcp::McpResult<serde_json::Value> {
354/// // Implementation would send elicitation request to client
355/// // and return the structured user input
356/// Ok(serde_json::json!({"theme": "dark", "language": "en"}))
357/// }
358/// # }
359#[proc_macro_attribute]
360pub fn elicitation(args: TokenStream, input: TokenStream) -> TokenStream {
361 elicitation::generate_elicitation_impl(args, input)
362}
363
364/// Marks a method as a completion handler for argument autocompletion
365///
366/// Completion provides intelligent suggestions for tool parameters
367/// based on current context and partial input.
368///
369/// # Example
370///
371/// ```ignore
372/// # use turbomcp_macros::completion;
373/// # struct MyServer;
374/// # impl MyServer {
375/// #[completion("Complete file paths")]
376/// async fn complete_file_path(&self, partial: String) -> turbomcp::McpResult<Vec<String>> {
377/// // Return completion suggestions based on partial input
378/// Ok(vec!["config.json".to_string(), "data.txt".to_string()])
379/// }
380/// # }
381#[proc_macro_attribute]
382pub fn completion(args: TokenStream, input: TokenStream) -> TokenStream {
383 completion::generate_completion_impl(args, input)
384}
385
386/// Marks a method as a resource template handler
387///
388/// Resource templates use RFC 6570 URI templates for parameterized
389/// resource access, enabling dynamic resource URIs.
390///
391/// # Example
392///
393/// ```ignore
394/// # use turbomcp_macros::template;
395/// # struct MyServer;
396/// # impl MyServer {
397/// #[template("users/{user_id}/profile")]
398/// async fn get_user_profile(&self, user_id: String) -> turbomcp::McpResult<String> {
399/// // Return resource content for the templated URI
400/// Ok(format!("Profile for user: {}", user_id))
401/// }
402/// # }
403#[proc_macro_attribute]
404pub fn template(args: TokenStream, input: TokenStream) -> TokenStream {
405 template::generate_template_impl(args, input)
406}
407
408/// Marks a method as a ping handler for connection health monitoring
409///
410/// Ping handlers enable bidirectional health checks between
411/// clients and servers for connection monitoring.
412///
413/// # Example
414///
415/// ```ignore
416/// # use turbomcp_macros::ping;
417/// # struct MyServer;
418/// # impl MyServer {
419/// #[ping("Health check")]
420/// async fn health_check(&self) -> turbomcp::McpResult<String> {
421/// // Return health status information
422/// Ok("Server is healthy".to_string())
423/// }
424/// # }
425#[proc_macro_attribute]
426pub fn ping(args: TokenStream, input: TokenStream) -> TokenStream {
427 ping::generate_ping_impl(args, input)
428}