turul_mcp_derive/lib.rs
1//! # MCP Derive Macros
2//!
3//! **Procedural macros for zero-configuration MCP tool and resource creation.**
4//!
5//! Transform regular Rust structs and functions into full-featured MCP tools, resources,
6//! and protocol handlers with automatic schema generation and method dispatch.
7//!
8//! [](https://crates.io/crates/turul-mcp-derive)
9//! [](https://docs.rs/turul-mcp-derive)
10//! [](https://github.com/aussierobots/turul-mcp-framework/blob/main/LICENSE)
11//!
12//! ## Features
13//!
14//! - **Tool Creation**: `#[derive(McpTool)]`, `#[mcp_tool]`, `tool!` macro
15//! - **Resource Handling**: `#[derive(McpResource)]`, `#[mcp_resource]`, `resource!` macro
16//! - **Schema Generation**: Automatic JSON schema from Rust types
17//! - **Zero Configuration**: Framework auto-determines method strings
18//! - **Type Safety**: Compile-time validation of MCP protocols
19//! - **Full Protocol**: Tools, resources, prompts, notifications, sampling
20//!
21//! ## Installation
22//!
23//! ```toml
24//! [dependencies]
25//! turul-mcp-derive = "0.4"
26//! turul-mcp-server = "0.4" # For server-side usage
27//! ```
28//!
29//! ## Quick Start
30//!
31//! ### Function Tool (Level 1 - Simplest)
32//!
33//! ```rust,no_run
34//! use turul_mcp_derive::mcp_tool;
35//! use turul_mcp_server::McpResult;
36//!
37//! #[mcp_tool(name = "add", description = "Add two numbers")]
38//! async fn add(
39//! #[param(description = "First number")] a: f64,
40//! #[param(description = "Second number")] b: f64,
41//! ) -> McpResult<f64> {
42//! Ok(a + b)
43//! }
44//! ```
45//!
46//! ### Derive Tool (Level 2 - Most Common)
47//!
48//! ```rust,no_run
49//! use turul_mcp_derive::McpTool;
50//! use turul_mcp_server::{McpResult, SessionContext};
51//!
52//! #[derive(McpTool, Clone)]
53//! #[tool(name = "calculator", description = "Multi-operation calculator")]
54//! struct Calculator {
55//! #[param(description = "First operand")]
56//! a: f64,
57//! #[param(description = "Second operand")]
58//! b: f64,
59//! #[param(description = "Operation to perform")]
60//! operation: String,
61//! }
62//!
63//! impl Calculator {
64//! async fn execute(&self, _session: Option<SessionContext>) -> McpResult<f64> {
65//! match self.operation.as_str() {
66//! "add" => Ok(self.a + self.b),
67//! "subtract" => Ok(self.a - self.b),
68//! "multiply" => Ok(self.a * self.b),
69//! "divide" => {
70//! if self.b != 0.0 {
71//! Ok(self.a / self.b)
72//! } else {
73//! Err("Division by zero".into())
74//! }
75//! }
76//! _ => Err("Unknown operation".into()),
77//! }
78//! }
79//! }
80//! ```
81//!
82//! ### Resource Handler
83//!
84//! ```rust,no_run
85//! use turul_mcp_derive::mcp_resource;
86//! use turul_mcp_protocol::resources::ResourceContent;
87//! use turul_mcp_server::McpResult;
88//!
89//! #[mcp_resource(
90//! uri = "file:///data/{filename}.json",
91//! description = "Dynamic JSON data files"
92//! )]
93//! async fn data_file(filename: String) -> McpResult<Vec<ResourceContent>> {
94//! let content = format!(r#"{{"filename": "{}", "data": "example"}}"#, filename);
95//! Ok(vec![ResourceContent::text(
96//! &format!("file:///data/{}.json", filename),
97//! &content
98//! )])
99//! }
100//! ```
101//!
102//! ## Available Macros
103//!
104//! | Macro | Purpose | Usage |
105//! |-------|---------|-------|
106//! | `#[derive(McpTool)]` | Struct-based tools | Most flexible |
107//! | `#[mcp_tool]` | Function-based tools | Quick & simple |
108//! | `#[derive(McpResource)]` | Resource handlers | Static resources |
109//! | `#[mcp_resource]` | Function resources | Dynamic resources |
110//! | `tool!` | Declarative tools | Runtime creation |
111//! | `resource!` | Declarative resources | Runtime creation |
112//!
113//! ## Examples
114//!
115//! **Complete examples available at:**
116//! [github.com/aussierobots/turul-mcp-framework/tree/main/examples](https://github.com/aussierobots/turul-mcp-framework/tree/main/examples)
117//!
118//! - **Calculator Tools** - Math operations with derive macros
119//! - **File Resources** - Static and dynamic resource handlers
120//! - **Function Tools** - Simple function-based tools
121//! - **Builder Pattern** - Runtime tool creation
122//! - **Schema Generation** - JSON schema from Rust types
123//!
124//! ## Related Crates
125//!
126//! - [`turul-mcp-server`](https://crates.io/crates/turul-mcp-server) - Server framework
127//! - [`turul-mcp-protocol`](https://crates.io/crates/turul-mcp-protocol) - Protocol types
128//! - [`turul-mcp-builders`](https://crates.io/crates/turul-mcp-builders) - Runtime builders
129
130use proc_macro::TokenStream;
131use syn::{DeriveInput, ItemFn, Meta, Token, parse_macro_input, punctuated::Punctuated};
132
133mod completion_derive;
134mod elicitation_derive;
135mod logging_derive;
136mod macros;
137mod notification_derive;
138mod prompt_derive;
139mod resource_attr;
140mod resource_derive;
141mod roots_derive;
142mod sampling_derive;
143mod tool_attr;
144mod tool_derive;
145mod utils;
146
147#[cfg(test)]
148mod tests;
149
150/// Derive macro for automatically implementing McpTool
151///
152/// This macro generates a complete McpTool implementation from a struct definition.
153///
154/// # Attributes
155///
156/// - `#[tool(name = "...", description = "...", output = Type)]` - Tool metadata and output type
157/// - `#[param(description = "...", ...)]` - Parameter descriptions and validation
158///
159/// # Output Schema Generation
160///
161/// The framework automatically generates detailed output schemas:
162/// - **Primitives** (f64, String, bool): Simple wrapped schemas without requiring JsonSchema
163/// - **Self-returning tools**: Automatic struct introspection for detailed schemas
164/// - **External structs**: **MUST** have `#[derive(schemars::JsonSchema)]` for detailed schemas
165///
166/// ## JsonSchema Requirement for Custom Output Types
167///
168/// When using custom output types (structs/enums), you **MUST** derive `JsonSchema`:
169///
170/// ```rust,no_run
171/// use schemars::JsonSchema;
172/// use serde::{Serialize, Deserialize};
173/// use turul_mcp_derive::McpTool;
174/// use turul_mcp_protocol::McpResult;
175///
176/// #[derive(Serialize, Deserialize, JsonSchema)] // ← JsonSchema required!
177/// struct MyOutput {
178/// field1: String,
179/// field2: i32,
180///
181/// // Optional fields: use skip_serializing_if to omit when None
182/// #[serde(skip_serializing_if = "Option::is_none")]
183/// field3: Option<String>,
184/// }
185///
186/// #[derive(McpTool, Clone)]
187/// #[tool(name = "my_tool", description = "Example", output = MyOutput)]
188/// struct MyTool {}
189///
190/// impl MyTool {
191/// async fn execute(&self, _session: Option<turul_mcp_server::SessionContext>) -> McpResult<MyOutput> {
192/// Ok(MyOutput { field1: "test".to_string(), field2: 42, field3: None })
193/// }
194/// }
195/// ```
196///
197/// **Without JsonSchema**, you'll get a compile error:
198/// ```text
199/// error[E0277]: the trait `schemars::JsonSchema` is not implemented for `MyOutput`
200/// ```
201///
202/// **Solution**: Add `#[derive(schemars::JsonSchema)]` to your output type.
203///
204/// ## Optional Fields Best Practice
205///
206/// For `Option<T>` fields, use `#[serde(skip_serializing_if = "Option::is_none")]` to omit
207/// the field when None instead of serializing as `null`. This prevents MCP validation errors:
208///
209/// ```rust,no_run
210/// use schemars::JsonSchema;
211/// use serde::{Serialize, Deserialize};
212///
213/// #[derive(Serialize, Deserialize, JsonSchema)]
214/// struct Output {
215/// required_field: String,
216///
217/// // ✅ CORRECT: Omit when None
218/// #[serde(skip_serializing_if = "Option::is_none")]
219/// optional_field: Option<String>,
220///
221/// // ❌ WRONG: Serializes as null, fails validation
222/// // optional_field: Option<String>,
223/// }
224/// ```
225///
226/// **Why?** The schema converter extracts type as `"string"` (not nullable) from schemars'
227/// `"type": ["string", "null"]`. When the value is `None`, omitting the field passes validation,
228/// but serializing as `null` fails because the schema expects a string.
229///
230/// **Note**: Primitive types (`String`, `i32`, `f64`, `bool`, `Vec<T>`) don't need JsonSchema
231///
232/// # Example
233///
234/// ```rust,no_run
235/// use turul_mcp_derive::McpTool;
236/// use turul_mcp_protocol::McpResult;
237/// use turul_mcp_server::SessionContext;
238///
239/// #[derive(McpTool, Clone)]
240/// #[tool(name = "add", description = "Add two numbers")]
241/// struct AddTool {
242/// #[param(description = "First number")]
243/// a: f64,
244/// #[param(description = "Second number")]
245/// b: f64,
246/// }
247///
248/// impl AddTool {
249/// async fn execute(&self, _session: Option<SessionContext>) -> McpResult<f64> {
250/// Ok(self.a + self.b)
251/// }
252/// }
253/// ```
254#[proc_macro_derive(McpTool, attributes(tool, param))]
255pub fn derive_mcp_tool(input: TokenStream) -> TokenStream {
256 let input = parse_macro_input!(input as DeriveInput);
257 tool_derive::derive_mcp_tool_impl(input)
258 .unwrap_or_else(|err| err.to_compile_error())
259 .into()
260}
261
262/// Function attribute macro for creating MCP tools
263///
264/// This macro converts a regular async function into an MCP tool with automatic
265/// parameter extraction and schema generation.
266///
267/// # Example
268///
269/// ```rust,no_run
270/// use turul_mcp_derive::mcp_tool;
271/// use turul_mcp_protocol::McpResult;
272///
273/// #[mcp_tool(name = "multiply", description = "Multiply two numbers")]
274/// async fn multiply(
275/// #[param(description = "First number")] a: f64,
276/// #[param(description = "Second number")] b: f64,
277/// ) -> McpResult<String> {
278/// Ok(format!("{} × {} = {}", a, b, a * b))
279/// }
280/// ```
281#[proc_macro_attribute]
282pub fn mcp_tool(args: TokenStream, input: TokenStream) -> TokenStream {
283 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
284 let input = parse_macro_input!(input as ItemFn);
285 tool_attr::mcp_tool_impl(args, input)
286 .unwrap_or_else(|err| err.to_compile_error())
287 .into()
288}
289
290/// Helper attribute for parameter metadata in function macros
291///
292/// This attribute is consumed by the #[mcp_tool] macro and has no effect when used alone.
293/// It provides parameter descriptions and constraints for function parameters.
294///
295/// Must be used within functions annotated with #[mcp_tool] to have any effect.
296#[proc_macro_attribute]
297pub fn param(_args: TokenStream, input: TokenStream) -> TokenStream {
298 // This attribute is only processed by the #[mcp_tool] macro
299 // When used alone, it just passes through the input unchanged
300 input
301}
302
303/// Function attribute macro for creating MCP resources
304///
305/// This macro automatically generates an MCP resource implementation from an async function.
306/// It supports both static and template resources based on the URI pattern.
307///
308/// # Example
309///
310/// ```rust,no_run
311/// use turul_mcp_derive::mcp_resource;
312/// use turul_mcp_protocol::resources::ResourceContent;
313/// use turul_mcp_server::McpResult;
314///
315/// #[mcp_resource(uri = "file:///asx/timeline/{ticker}.json", description = "Timeline for ticker")]
316/// async fn ticker_timeline(ticker: String) -> McpResult<Vec<ResourceContent>> {
317/// // Implementation
318/// Ok(vec![ResourceContent::text(
319/// &format!("file:///asx/timeline/{}.json", ticker),
320/// &format!("Timeline data for {}", ticker)
321/// )])
322/// }
323/// ```
324#[proc_macro_attribute]
325pub fn mcp_resource(args: TokenStream, input: TokenStream) -> TokenStream {
326 let args = parse_macro_input!(args with Punctuated::<Meta, Token![,]>::parse_terminated);
327 let input = parse_macro_input!(input as ItemFn);
328 resource_attr::mcp_resource_impl(args, input)
329 .unwrap_or_else(|err| err.to_compile_error())
330 .into()
331}
332
333/// Derive macro for automatically implementing MCP resource handlers
334///
335/// This macro generates both the metadata traits and the session-aware McpResource trait implementation.
336/// Fields marked with `#[content]` are automatically used to generate the resource content.
337///
338/// # Example
339///
340/// ```rust,no_run
341/// use turul_mcp_derive::McpResource;
342/// use async_trait::async_trait;
343/// use serde::{Deserialize, Serialize};
344/// use turul_mcp_server::{McpResource, McpResult, SessionContext};
345/// use turul_mcp_protocol::resources::ResourceContent;
346/// use turul_mcp_builders::prelude::HasResourceUri;
347/// use serde_json::Value;
348///
349/// #[derive(McpResource, Clone)]
350/// #[resource(name = "config", uri = "file://config.json", description = "Application configuration file")]
351/// struct ConfigResource {
352/// #[content]
353/// #[content_type = "application/json"]
354/// data: String,
355/// }
356///
357/// // McpResource trait is automatically implemented with session-aware signature:
358/// // async fn read(&self, params: Option<Value>, session: Option<&SessionContext>) -> McpResult<Vec<ResourceContent>>
359/// ```
360#[proc_macro_derive(
361 McpResource,
362 attributes(resource, uri, name, description, content, content_type)
363)]
364pub fn derive_mcp_resource(input: TokenStream) -> TokenStream {
365 let input = parse_macro_input!(input as DeriveInput);
366 resource_derive::derive_mcp_resource_impl(input)
367 .unwrap_or_else(|err| err.to_compile_error())
368 .into()
369}
370
371/// Declarative macro for creating simple resources
372///
373/// This provides a concise syntax for resource creation.
374///
375/// # Example
376///
377/// ```rust,no_run
378/// use turul_mcp_derive::resource;
379///
380/// let config_resource = resource! {
381/// uri: "file://config.json",
382/// name: "Configuration",
383/// description: "Application configuration file",
384/// content: |_, _| async {
385/// let config = serde_json::json!({
386/// "app_name": "Test App",
387/// "version": "1.0.0"
388/// });
389/// Ok(vec![turul_mcp_protocol::resources::ResourceContent::blob(
390/// "file://config.json".to_string(),
391/// serde_json::to_string_pretty(&config).unwrap(),
392/// "application/json".to_string()
393/// )])
394/// }
395/// };
396/// ```
397#[proc_macro]
398pub fn resource(input: TokenStream) -> TokenStream {
399 match macros::resource_declarative_impl(input) {
400 Ok(tokens) => tokens,
401 Err(err) => err.to_compile_error().into(),
402 }
403}
404
405/// Generate a JSON schema for a Rust type
406///
407/// This macro generates a JSON schema definition for any Rust type that implements
408/// Serialize. It analyzes the type structure and creates appropriate schema constraints.
409///
410/// # Example
411///
412/// ```rust,no_run
413/// use turul_mcp_derive::schema_for;
414/// use serde::{Serialize, Deserialize};
415///
416/// #[derive(Serialize, Deserialize)]
417/// struct Point {
418/// x: f64,
419/// y: f64,
420/// }
421///
422/// let schema = schema_for!(Point);
423/// ```
424#[proc_macro]
425pub fn schema_for(input: TokenStream) -> TokenStream {
426 match macros::schema_for_impl(input) {
427 Ok(tokens) => tokens,
428 Err(err) => err.to_compile_error().into(),
429 }
430}
431
432/// Derive macro for automatically implementing McpElicitation
433///
434/// Elicitation is a 2025-11-25 feature; the generated code references types the
435/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
436///
437/// # Example
438///
439#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
440#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
441/// use turul_mcp_derive::McpElicitation;
442///
443/// #[derive(McpElicitation)]
444/// #[elicitation(message = "Please enter your details")]
445/// struct UserDetailsElicitation {
446/// name: String,
447/// email: String,
448/// }
449/// ```
450#[proc_macro_derive(McpElicitation, attributes(elicitation, field))]
451pub fn derive_mcp_elicitation(input: TokenStream) -> TokenStream {
452 let input = parse_macro_input!(input as DeriveInput);
453 elicitation_derive::derive_mcp_elicitation_impl(input)
454 .unwrap_or_else(|err| err.to_compile_error())
455 .into()
456}
457
458/// Derive macro for automatically implementing McpPrompt
459///
460/// # Example
461///
462/// ```rust,no_run
463/// use turul_mcp_derive::McpPrompt;
464///
465/// #[derive(McpPrompt)]
466/// #[prompt(name = "code_review", description = "Review code")]
467/// struct CodeReviewPrompt {
468/// code: String,
469/// language: String,
470/// }
471/// ```
472#[proc_macro_derive(McpPrompt, attributes(prompt, argument))]
473pub fn derive_mcp_prompt(input: TokenStream) -> TokenStream {
474 let input = parse_macro_input!(input as DeriveInput);
475 prompt_derive::derive_mcp_prompt_impl(input)
476 .unwrap_or_else(|err| err.to_compile_error())
477 .into()
478}
479
480/// Derive macro for automatically implementing McpSampling
481///
482/// Sampling is a 2025-11-25 feature; the generated code references types the
483/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
484///
485/// # Example
486///
487#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
488#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
489/// use turul_mcp_derive::McpSampling;
490/// use turul_mcp_protocol::prelude::*;
491///
492/// #[derive(McpSampling)]
493/// #[sampling(model = "claude-3-haiku", temperature = 0.7)]
494/// struct TextGenerationSampling {
495/// prompt: String,
496/// max_tokens: u32,
497/// }
498/// ```
499#[proc_macro_derive(McpSampling, attributes(sampling, config))]
500pub fn derive_mcp_sampling(input: TokenStream) -> TokenStream {
501 let input = parse_macro_input!(input as DeriveInput);
502 sampling_derive::derive_mcp_sampling_impl(input)
503 .unwrap_or_else(|err| err.to_compile_error())
504 .into()
505}
506
507/// Derive macro for automatically implementing McpCompletion
508///
509/// # Example
510///
511/// ```rust,no_run
512/// use turul_mcp_derive::McpCompletion;
513/// use turul_mcp_builders::prelude::{CompletionDefinition, HasCompletionHandling};
514///
515/// #[derive(McpCompletion)]
516/// #[completion(reference = "prompt://code_assist")]
517/// struct CodeCompletionProvider {
518/// context: String,
519/// cursor_position: usize,
520/// }
521/// ```
522#[proc_macro_derive(McpCompletion, attributes(completion, reference))]
523pub fn derive_mcp_completion(input: TokenStream) -> TokenStream {
524 let input = parse_macro_input!(input as DeriveInput);
525 completion_derive::derive_mcp_completion_impl(input)
526 .unwrap_or_else(|err| err.to_compile_error())
527 .into()
528}
529
530/// Derive macro for automatically implementing McpLogger
531///
532/// Logging is a 2025-11-25 feature; the generated code references types the
533/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
534///
535/// # Example
536///
537#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
538#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
539/// use turul_mcp_derive::McpLogger;
540/// use turul_mcp_builders::prelude::{HasLoggingMetadata, LoggerDefinition, HasLogLevel};
541///
542/// #[derive(McpLogger)]
543/// #[logger(name = "app_logger", level = "info")]
544/// struct ApplicationLogger {
545/// format: String,
546/// output_path: Option<String>,
547/// }
548/// ```
549#[proc_macro_derive(McpLogger, attributes(logger, level))]
550pub fn derive_mcp_logger(input: TokenStream) -> TokenStream {
551 let input = parse_macro_input!(input as DeriveInput);
552 logging_derive::derive_mcp_logger_impl(input)
553 .unwrap_or_else(|err| err.to_compile_error())
554 .into()
555}
556
557/// Derive macro for automatically implementing McpRoot
558///
559/// # Example
560///
561/// ```rust,no_run
562/// use turul_mcp_derive::McpRoot;
563/// use turul_mcp_builders::prelude::{HasRootFiltering, HasRootPermissions, RootDefinition, HasRootMetadata};
564///
565/// #[derive(McpRoot)]
566/// #[root(uri = "file:///home/user/project", name = "Project Root")]
567/// struct ProjectRoot;
568/// ```
569#[proc_macro_derive(McpRoot, attributes(root, permission))]
570pub fn derive_mcp_root(input: TokenStream) -> TokenStream {
571 let input = parse_macro_input!(input as DeriveInput);
572 roots_derive::derive_mcp_root_impl(input)
573 .unwrap_or_else(|err| err.to_compile_error())
574 .into()
575}
576
577/// Derive macro for automatically implementing McpNotification
578///
579/// ZERO CONFIGURATION - Framework auto-determines method from struct name for MCP spec notifications:
580/// - `ProgressNotification` → `"notifications/progress"`
581/// - `LoggingMessageNotification` → `"notifications/logging/message"`
582/// - `ResourceUpdatedNotification` → `"notifications/resources/updated"`
583/// - `ResourceListChangedNotification` → `"notifications/resources/list_changed"`
584/// - `ToolListChangedNotification` → `"notifications/tools/list_changed"`
585///
586/// # Example
587///
588/// ```rust,no_run
589/// use turul_mcp_derive::McpNotification;
590///
591/// #[derive(McpNotification, Default)]
592/// struct ProgressNotification {
593/// progress_token: String,
594/// progress: u64,
595/// total: Option<u64>,
596/// message: Option<String>,
597/// }
598/// ```
599#[proc_macro_derive(McpNotification, attributes(notification, payload))]
600pub fn derive_mcp_notification(input: TokenStream) -> TokenStream {
601 let input = parse_macro_input!(input as DeriveInput);
602 notification_derive::derive_mcp_notification_impl(input)
603 .unwrap_or_else(|err| err.to_compile_error())
604 .into()
605}
606
607/// Declarative macro for creating simple tools
608///
609/// This provides the most concise syntax for tool creation.
610///
611/// # Example
612///
613/// ```rust,no_run
614/// use turul_mcp_derive::tool;
615/// use turul_mcp_protocol::prelude::*;
616/// use turul_mcp_builders::prelude::*;
617///
618/// let divide_tool = tool! {
619/// name: "divide",
620/// description: "Divide two numbers with validation",
621/// read_only: false,
622/// destructive: false,
623/// idempotent: true,
624/// params: {
625/// a: f64 => "Dividend (first number)",
626/// b: f64 => "Divisor (second number)",
627/// },
628/// execute: |a: f64, b: f64| async move {
629/// if b == 0.0 {
630/// Err("Division by zero")
631/// } else {
632/// Ok(format!("{} ÷ {} = {}", a, b, a / b))
633/// }
634/// }
635/// };
636/// ```
637#[proc_macro]
638pub fn tool(input: TokenStream) -> TokenStream {
639 match macros::tool_declarative_impl(input) {
640 Ok(tokens) => tokens,
641 Err(err) => err.to_compile_error().into(),
642 }
643}
644
645/// Declarative macro for creating simple prompts
646///
647/// This provides a concise syntax for prompt creation.
648///
649/// # Example
650///
651/// ```rust,no_run
652/// use turul_mcp_derive::prompt;
653/// use turul_mcp_protocol::prelude::*;
654/// use std::collections::HashMap;
655///
656/// let code_review_prompt = prompt! {
657/// name: "code_review",
658/// description: "Review code for quality and best practices",
659/// arguments: {
660/// code: String => "Code to review",
661/// language: String => "Programming language", required,
662/// },
663/// template: |args: Option<HashMap<String, Value>>| async move {
664/// let args = args.unwrap_or_default();
665/// let code = args.get("code").and_then(|v| v.as_str()).unwrap_or("");
666/// let lang = args.get("language").and_then(|v| v.as_str()).unwrap_or("text");
667///
668/// Ok::<Vec<PromptMessage>, McpError>(vec![
669/// PromptMessage::user_text(format!(
670/// "Please review this {} code for quality, security, and best practices:\n\n```{}\n{}\n```",
671/// lang, lang, code
672/// ))
673/// ])
674/// }
675/// };
676/// ```
677#[proc_macro]
678pub fn prompt(input: TokenStream) -> TokenStream {
679 match macros::prompt_declarative_impl(input) {
680 Ok(tokens) => tokens,
681 Err(err) => err.to_compile_error().into(),
682 }
683}
684
685/// Declarative macro for creating sampling configurations
686///
687/// This provides a concise syntax for sampling configuration.
688///
689/// Sampling is a 2025-11-25 feature; the generated code references types the
690/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
691///
692/// # Example
693///
694#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
695#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
696/// use turul_mcp_derive::sampling;
697/// use turul_mcp_protocol::prelude::*;
698///
699/// let text_generator = sampling! {
700/// max_tokens: 1000,
701/// temperature: 0.7,
702/// system_prompt: "You are a helpful AI assistant",
703/// handler: |request| async move {
704/// // Implementation would call actual model API
705/// let response_text = "Generated response based on the input";
706/// Ok(CreateMessageResult::new(
707/// Role::Assistant,
708/// ContentBlock::Text {
709/// text: response_text.to_string(),
710/// annotations: None,
711/// meta: None,
712/// },
713/// "claude-3-haiku"
714/// ))
715/// }
716/// };
717/// ```
718#[proc_macro]
719pub fn sampling(input: TokenStream) -> TokenStream {
720 match macros::sampling_declarative_impl(input) {
721 Ok(tokens) => tokens,
722 Err(err) => err.to_compile_error().into(),
723 }
724}
725
726/// Declarative macro for creating MCP notifications with concise syntax.
727///
728/// Supports zero-configuration method generation based on struct name.
729///
730/// # Example
731///
732/// ```rust,no_run
733/// use turul_mcp_derive::notification;
734///
735/// notification! {
736/// progress {
737/// message: String = "Progress message",
738/// percent: u32 = "Completion percentage"
739/// }
740/// };
741/// ```
742#[proc_macro]
743pub fn notification(input: TokenStream) -> TokenStream {
744 match macros::notification_declarative_impl(input) {
745 Ok(tokens) => tokens,
746 Err(err) => err.to_compile_error().into(),
747 }
748}
749
750/// Declarative macro for creating MCP completion handlers with concise syntax.
751///
752/// # Example
753///
754/// ```rust,no_run
755/// use turul_mcp_derive::completion;
756/// use turul_mcp_builders::prelude::HasCompletionHandling;
757///
758/// completion! {
759/// text_editor {
760/// context: String = "Editor context",
761/// cursor_position: u32 = "Cursor position"
762/// }
763/// };
764/// ```
765#[proc_macro]
766pub fn completion(input: TokenStream) -> TokenStream {
767 match macros::completion_declarative_impl(input) {
768 Ok(tokens) => tokens,
769 Err(err) => err.to_compile_error().into(),
770 }
771}
772
773/// Declarative macro for creating MCP elicitation handlers with concise syntax.
774///
775/// Elicitation is a 2025-11-25 feature; the generated code references types the
776/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
777///
778/// # Example
779///
780#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
781#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
782/// use turul_mcp_derive::elicitation;
783///
784/// elicitation! {
785/// user_details, "Please provide your information" {
786/// name: String = "Full name",
787/// email: String = "Email address"
788/// }
789/// };
790/// ```
791#[proc_macro]
792pub fn elicitation(input: TokenStream) -> TokenStream {
793 match macros::elicitation_declarative_impl(input) {
794 Ok(tokens) => tokens,
795 Err(err) => err.to_compile_error().into(),
796 }
797}
798
799/// Declarative macro for creating MCP root handlers with concise syntax.
800///
801/// # Example
802///
803/// ```rust,no_run
804/// use turul_mcp_derive::roots;
805/// use turul_mcp_builders::prelude::{RootDefinition, HasRootMetadata, HasRootFiltering, HasRootPermissions};
806///
807/// roots! {
808/// project, "/path/to/project", name = "Project Files", read_only = false
809/// };
810/// ```
811#[proc_macro]
812pub fn roots(input: TokenStream) -> TokenStream {
813 match macros::roots_declarative_impl(input) {
814 Ok(tokens) => tokens,
815 Err(err) => err.to_compile_error().into(),
816 }
817}
818
819/// Declarative macro for creating MCP logging handlers with concise syntax.
820///
821/// Logging is a 2025-11-25 feature; the generated code references types the
822/// stateless 2026-07-28 core removes, so the example only compiles under 2025-11-25.
823///
824/// # Example
825///
826#[cfg_attr(feature = "protocol-2025-11-25", doc = "```rust")]
827#[cfg_attr(not(feature = "protocol-2025-11-25"), doc = "```rust,ignore")]
828/// use turul_mcp_derive::logging;
829/// use turul_mcp_protocol::prelude::*;
830/// use turul_mcp_builders::prelude::*;
831///
832/// logging! {
833/// file_logger {
834/// log_level: String = "Logging level",
835/// file_path: String = "Log file path"
836/// }
837/// };
838/// ```
839#[proc_macro]
840pub fn logging(input: TokenStream) -> TokenStream {
841 match macros::logging_declarative_impl(input) {
842 Ok(tokens) => tokens,
843 Err(err) => err.to_compile_error().into(),
844 }
845}