mcp_host_macros/lib.rs
1//! Procedural macros for mcp-host
2//!
3//! Provides attribute macros for ergonomic MCP server definition.
4//!
5//! ## Usage
6//!
7//! ```rust,ignore
8//! use mcp_host::prelude::*;
9//! use schemars::JsonSchema;
10//!
11//! #[derive(Deserialize, JsonSchema)]
12//! struct CalcParams { x: f64, y: f64 }
13//!
14//! #[mcp_router]
15//! impl MyServer {
16//! #[mcp_tool(name = "calculate")]
17//! async fn calculate(&self, ctx: Ctx, params: Parameters<CalcParams>) -> ToolResult {
18//! Ok(ToolOutput::text(format!("Result: {}", params.0.x + params.0.y)))
19//! }
20//!
21//! #[mcp_prompt(name = "greeting", argument(name = "name", required = true))]
22//! async fn greeting(&self, ctx: Ctx, args: Value) -> PromptResult {
23//! let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
24//! prompt_messages(vec![user_message(format!("Hello, {name}!"))])
25//! }
26//!
27//! #[mcp_resource(uri = "config:///app", name = "config")]
28//! async fn config(&self, ctx: Ctx) -> ResourceResult {
29//! Ok(vec![text_resource("config:///app", "{\"version\": \"1.0\"}")])
30//! }
31//! }
32//!
33//! // Registration:
34//! let server = Arc::new(MyServer::new());
35//! MyServer::tool_router().register_all(&tool_registry, server.clone());
36//! MyServer::prompt_router().register_all(&prompt_manager, server.clone());
37//! MyServer::resource_router().register_all(&resource_manager, server);
38//! ```
39
40mod prompt;
41mod resource;
42mod resource_template;
43mod router;
44mod tool;
45
46use proc_macro::TokenStream;
47
48/// Mark an async function as an MCP tool handler
49///
50/// Generates:
51/// - `{fn_name}_tool_info()` - Returns `ToolInfo` with schema from `Parameters<T>`
52/// - `{fn_name}_handler()` - Handler wrapper for the router
53/// - `{fn_name}_visibility()` - Visibility predicate (if `visible` attribute specified)
54///
55/// # Attributes
56///
57/// - `name`: Tool name (default: function name)
58/// - `description`: Tool description (default: doc comments)
59/// - `visible`: Visibility predicate expression (default: always visible)
60///
61/// # Example
62///
63/// ```rust,ignore
64/// #[mcp_tool(name = "calculate", description = "Performs math")]
65/// async fn calculate(&self, ctx: Ctx, params: Parameters<CalcParams>) -> ToolResult {
66/// Ok(vec![text(&format!("Result: {}", params.0.value))])
67/// }
68/// ```
69#[proc_macro_attribute]
70pub fn mcp_tool(attr: TokenStream, item: TokenStream) -> TokenStream {
71 tool::expand_mcp_tool(attr, item)
72}
73
74/// Mark an async function as an MCP prompt handler
75///
76/// Generates:
77/// - `{fn_name}_prompt_info()` - Returns `PromptInfo` with arguments metadata
78/// - `{fn_name}_handler()` - Handler wrapper for the router
79/// - `{fn_name}_visibility()` - Visibility predicate (if `visible` attribute specified)
80///
81/// # Attributes
82///
83/// - `name`: Prompt name (default: function name)
84/// - `description`: Prompt description (default: doc comments)
85/// - `visible`: Visibility predicate expression (default: always visible)
86/// - `argument`: Prompt argument definition (can be repeated):
87/// - `name`: Argument name (required)
88/// - `description`: Argument description (optional)
89/// - `required`: Whether the argument is required (optional)
90///
91/// # Example
92///
93/// ```rust,ignore
94/// #[mcp_prompt(
95/// name = "greeting",
96/// argument(name = "name", description = "Name to greet", required = true)
97/// )]
98/// async fn greeting(&self, ctx: Ctx, args: Value) -> PromptResult {
99/// let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
100/// prompt_with_description("A greeting", vec![user_message(format!("Hello, {name}!"))])
101/// }
102/// ```
103#[proc_macro_attribute]
104pub fn mcp_prompt(attr: TokenStream, item: TokenStream) -> TokenStream {
105 prompt::expand_mcp_prompt(attr, item)
106}
107
108/// Mark an async function as an MCP resource handler
109///
110/// Generates:
111/// - `{fn_name}_resource_info()` - Returns `ResourceInfo` with metadata
112/// - `{fn_name}_handler()` - Handler wrapper for the router
113/// - `{fn_name}_visibility()` - Visibility predicate (if `visible` attribute specified)
114///
115/// # Attributes
116///
117/// - `uri`: Resource URI (required)
118/// - `name`: Resource name (required)
119/// - `description`: Resource description (default: doc comments)
120/// - `mime_type`: MIME type for the resource content
121/// - `visible`: Visibility predicate expression (default: always visible)
122///
123/// # Example
124///
125/// ```rust,ignore
126/// #[mcp_resource(uri = "config:///app", name = "app_config", mime_type = "application/json")]
127/// async fn app_config(&self, ctx: Ctx) -> ResourceResult {
128/// Ok(vec![text_resource("config:///app", "{\"version\": \"1.0\"}")])
129/// }
130/// ```
131#[proc_macro_attribute]
132pub fn mcp_resource(attr: TokenStream, item: TokenStream) -> TokenStream {
133 resource::expand_mcp_resource(attr, item)
134}
135
136/// Mark an async function as an MCP resource template handler
137///
138/// Generates:
139/// - `{fn_name}_template_info()` - Returns `ResourceTemplateInfo` with metadata
140/// - `{fn_name}_handler()` - Handler wrapper for the router
141/// - `{fn_name}_visibility()` - Visibility predicate (if `visible` attribute specified)
142///
143/// # Attributes
144///
145/// - `uri_template`: URI template pattern (required, e.g., "file:///{path}")
146/// - `name`: Template name (required)
147/// - `title`: Display title
148/// - `description`: Template description (default: doc comments)
149/// - `mime_type`: MIME type for resources matching this template
150/// - `visible`: Visibility predicate expression (default: always visible)
151///
152/// # Example
153///
154/// ```rust,ignore
155/// #[mcp_resource_template(uri_template = "file:///{path}", name = "files")]
156/// async fn read_file(&self, ctx: Ctx) -> ResourceResult {
157/// let path = ctx.get_uri_param("path").ok_or(ResourceError::InvalidUri("missing path".into()))?;
158/// let content = std::fs::read_to_string(path)?;
159/// Ok(vec![text_resource(format!("file:///{path}"), content)])
160/// }
161/// ```
162#[proc_macro_attribute]
163pub fn mcp_resource_template(attr: TokenStream, item: TokenStream) -> TokenStream {
164 resource_template::expand_mcp_resource_template(attr, item)
165}
166
167/// Router macro for MCP servers
168///
169/// Scans an impl block for all MCP handler attributes (`#[mcp_tool]`, `#[mcp_prompt]`,
170/// `#[mcp_resource]`, `#[mcp_resource_template]`) and generates the appropriate router
171/// methods for each type found.
172///
173/// # Attributes
174///
175/// - `tools`: Name of generated tool router function (default: "tool_router")
176/// - `prompts`: Name of generated prompt router function (default: "prompt_router")
177/// - `resources`: Name of generated resource router function (default: "resource_router")
178/// - `templates`: Name of generated template router function (default: "resource_template_router")
179///
180/// # Example
181///
182/// ```rust,ignore
183/// #[mcp_router]
184/// impl MyServer {
185/// #[mcp_tool(name = "echo")]
186/// async fn echo(&self, ctx: Ctx, params: Parameters<EchoParams>) -> ToolResult { ... }
187///
188/// #[mcp_prompt(name = "greeting")]
189/// async fn greeting(&self, ctx: Ctx, args: Value) -> PromptResult { ... }
190///
191/// #[mcp_resource(uri = "config:///app", name = "config")]
192/// async fn config(&self, ctx: Ctx) -> ResourceResult { ... }
193///
194/// #[mcp_resource_template(uri_template = "file:///{path}", name = "files")]
195/// async fn files(&self, ctx: Ctx) -> ResourceResult { ... }
196/// }
197///
198/// // Generated (only for types that have at least one handler):
199/// // MyServer::tool_router() -> McpToolRouter<MyServer>
200/// // MyServer::prompt_router() -> McpPromptRouter<MyServer>
201/// // MyServer::resource_router() -> McpResourceRouter<MyServer>
202/// // MyServer::resource_template_router() -> McpResourceTemplateRouter<MyServer>
203/// ```
204#[proc_macro_attribute]
205pub fn mcp_router(attr: TokenStream, item: TokenStream) -> TokenStream {
206 router::expand_mcp_router(attr, item)
207}