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}