Skip to main content

turbomcp_wasm_macros/
lib.rs

1//! # TurboMCP WASM Macros
2//!
3//! Zero-boilerplate procedural macros for building MCP servers in WASM environments
4//! like Cloudflare Workers, Deno Deploy, and other edge platforms.
5//!
6//! ## Features
7//!
8//! - **`#[server]`** - Transform impl blocks into MCP servers
9//! - **`#[tool]`** - Mark methods as MCP tool handlers
10//! - **`#[resource]`** - Mark methods as MCP resource handlers
11//! - **`#[prompt]`** - Mark methods as MCP prompt handlers
12//!
13//! ## Example
14//!
15//! ```ignore
16//! use turbomcp_wasm::prelude::*;
17//! use serde::Deserialize;
18//!
19//! #[derive(Clone)]
20//! struct MyServer {
21//!     greeting: String,
22//! }
23//!
24//! #[derive(Deserialize, schemars::JsonSchema)]
25//! struct GreetArgs {
26//!     name: String,
27//! }
28//!
29//! #[server(name = "my-server", version = "1.0.0")]
30//! impl MyServer {
31//!     #[tool("Greet someone by name")]
32//!     async fn greet(&self, args: GreetArgs) -> String {
33//!         format!("{}, {}!", self.greeting, args.name)
34//!     }
35//!
36//!     #[tool("Get server status")]
37//!     async fn status(&self) -> String {
38//!         "Server is running".to_string()
39//!     }
40//!
41//!     #[resource("config://app")]
42//!     async fn config(&self, uri: String) -> ResourceResult {
43//!         ResourceResult::text(&uri, r#"{"theme": "dark"}"#)
44//!     }
45//!
46//!     #[prompt("Default greeting")]
47//!     async fn greeting_prompt(&self) -> PromptResult {
48//!         PromptResult::user("Hello! How can I help?")
49//!     }
50//! }
51//!
52//! // Generated method:
53//! // impl MyServer {
54//! //     pub fn into_mcp_server(self) -> McpServer { ... }
55//! // }
56//! ```
57
58use proc_macro::TokenStream;
59use syn::{ItemImpl, parse_macro_input};
60
61mod server;
62
63/// Marks an impl block as a WASM MCP server.
64///
65/// This macro transforms an impl block with `#[tool]`, `#[resource]`, and `#[prompt]`
66/// attributes into a fully-functional MCP server using the builder pattern.
67///
68/// # Attributes
69///
70/// - `name` - Server name (required)
71/// - `version` - Server version (default: "1.0.0")
72/// - `description` - Server description (optional)
73///
74/// # Example
75///
76/// ```ignore
77/// #[derive(Clone)]
78/// struct Calculator;
79///
80/// #[server(name = "calculator", version = "2.0.0")]
81/// impl Calculator {
82///     #[tool("Add two numbers")]
83///     async fn add(&self, args: AddArgs) -> i64 {
84///         args.a + args.b
85///     }
86/// }
87///
88/// // Use it:
89/// let server = Calculator.into_mcp_server();
90/// ```
91#[proc_macro_attribute]
92pub fn server(args: TokenStream, input: TokenStream) -> TokenStream {
93    let args = parse_macro_input!(args as server::ServerArgs);
94    let input = parse_macro_input!(input as ItemImpl);
95
96    server::generate_server(args, input)
97        .unwrap_or_else(|e| e.to_compile_error())
98        .into()
99}
100
101/// Marks a method as an MCP tool handler.
102///
103/// This attribute is used inside a `#[server]` impl block to register
104/// tool handlers. The macro extracts the description and generates appropriate
105/// builder registration code.
106///
107/// # Arguments
108///
109/// - A string literal description of the tool
110///
111/// # Method Signature
112///
113/// Tool methods can have various signatures:
114/// - `async fn name(&self, args: Args) -> T` - With typed arguments
115/// - `async fn name(&self) -> T` - No arguments
116/// - Return type `T` must implement `IntoToolResponse`
117///
118/// # Example
119///
120/// ```ignore
121/// #[tool("Add two numbers together")]
122/// async fn add(&self, args: AddArgs) -> i64 {
123///     args.a + args.b
124/// }
125///
126/// #[tool("Get current time")]
127/// async fn now(&self) -> String {
128///     "2024-01-01T00:00:00Z".to_string()
129/// }
130/// ```
131#[proc_macro_attribute]
132pub fn tool(_args: TokenStream, input: TokenStream) -> TokenStream {
133    // This is a marker attribute - the actual processing happens in #[server]
134    // We just pass through the input unchanged
135    input
136}
137
138/// Marks a method as an MCP resource handler.
139///
140/// This attribute is used inside a `#[server]` impl block to register
141/// resource handlers with URI templates.
142///
143/// # Arguments
144///
145/// - A string literal URI or URI template (e.g., "config://app" or "file://{path}")
146///
147/// # Method Signature
148///
149/// Resource methods must have the signature:
150/// - `async fn name(&self, uri: String) -> ResourceResult`
151/// - Or `async fn name(&self, uri: String) -> Result<ResourceResult, E>`
152///
153/// # Example
154///
155/// ```ignore
156/// #[resource("config://app")]
157/// async fn read_config(&self, uri: String) -> ResourceResult {
158///     ResourceResult::text(&uri, r#"{"theme": "dark"}"#)
159/// }
160///
161/// #[resource("file://{path}")]
162/// async fn read_file(&self, uri: String) -> Result<ResourceResult, ToolError> {
163///     // Extract path from URI and read file
164///     Ok(ResourceResult::text(&uri, "file contents"))
165/// }
166/// ```
167#[proc_macro_attribute]
168pub fn resource(_args: TokenStream, input: TokenStream) -> TokenStream {
169    // Marker attribute - processing happens in #[server]
170    input
171}
172
173/// Marks a method as an MCP prompt handler.
174///
175/// This attribute is used inside a `#[server]` impl block to register
176/// prompt handlers.
177///
178/// # Arguments
179///
180/// - A string literal description of the prompt
181///
182/// # Method Signature
183///
184/// Prompt methods can have these signatures:
185/// - `async fn name(&self) -> PromptResult` - No arguments
186/// - `async fn name(&self, args: Option<Args>) -> PromptResult` - With optional arguments
187///
188/// # Example
189///
190/// ```ignore
191/// #[prompt("Default greeting prompt")]
192/// async fn greeting(&self) -> PromptResult {
193///     PromptResult::user("Hello! How can I help you today?")
194/// }
195///
196/// #[prompt("Code review prompt")]
197/// async fn review(&self, args: Option<ReviewArgs>) -> PromptResult {
198///     let lang = args.map(|a| a.language).unwrap_or("rust".into());
199///     PromptResult::user(format!("Review this {} code:", lang))
200/// }
201/// ```
202#[proc_macro_attribute]
203pub fn prompt(_args: TokenStream, input: TokenStream) -> TokenStream {
204    // Marker attribute - processing happens in #[server]
205    input
206}