1#![allow(unused)]
2
3use std::{
4 collections::{HashMap, HashSet},
5 str::FromStr,
6};
7
8use proc_macro2::{Span, TokenStream};
9use quote::{ToTokens, format_ident, quote};
10use structmeta::{NameArgs, NameValue, StructMeta};
11use syn::{
12 Attribute, Error, FnArg, Ident, ImplItem, ImplItemFn, ItemFn, ItemImpl, LitStr, Pat, Path,
13 Result, Token, Type,
14 parse::{Parse, ParseStream},
15 parse2,
16 punctuated::Punctuated,
17 spanned::Spanned,
18};
19use uri_template_ex::UriTemplate;
20
21use syn_utils::{get_element, into_macro_output, is_path, is_type};
22use utils::{get_trait_path, is_defined};
23
24use crate::prompts::{PromptAttr, PromptEntry};
25use crate::resources::{ResourceAttr, ResourceEntry};
26use crate::tools::{ToolAttr, ToolEntry};
27use crate::utils::{build_if, drain_attr};
28
29#[macro_use]
30mod syn_utils;
31mod utils;
32
33mod prompts;
34mod resources;
35mod tools;
36
37#[proc_macro_attribute]
38pub fn mcp_server(
39 attr: proc_macro::TokenStream,
40 item: proc_macro::TokenStream,
41) -> proc_macro::TokenStream {
42 let mut item: TokenStream = item.into();
43 let mut es = Vec::new();
44 match build_mcp_server(attr.into(), item.clone(), &mut es) {
45 Ok(mut s) => {
46 for e in es {
47 s.extend(e.to_compile_error());
48 }
49 s
50 }
51 Err(e) => e.to_compile_error(),
52 }
53 .into()
54}
55
56#[proc_macro]
57pub fn route(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
58 into_macro_output(build_route(item.into()))
59}
60
61#[proc_macro_attribute]
62pub fn tool(
63 attr: proc_macro::TokenStream,
64 item: proc_macro::TokenStream,
65) -> proc_macro::TokenStream {
66 into_macro_output(build_tool(attr.into(), item.into()))
67}
68
69#[proc_macro_attribute]
70pub fn resource(
71 attr: proc_macro::TokenStream,
72 item: proc_macro::TokenStream,
73) -> proc_macro::TokenStream {
74 into_macro_output(build_resource(attr.into(), item.into()))
75}
76
77#[proc_macro_attribute]
78pub fn prompt(
79 attr: proc_macro::TokenStream,
80 item: proc_macro::TokenStream,
81) -> proc_macro::TokenStream {
82 into_macro_output(build_prompt(attr.into(), item.into()))
83}
84
85fn build_mcp_server(
86 attr: TokenStream,
87 item: TokenStream,
88 es: &mut Vec<Error>,
89) -> Result<TokenStream> {
90 let mut item_impl: ItemImpl = parse2(item)?;
91 let mut attr: McpAttr = parse2(attr)?;
92 let trait_path = get_trait_path(&item_impl)?.clone();
93 if item_impl.unsafety.is_some() {
94 bail!(item_impl.span(), "Unsafe is not allowed");
95 }
96 if item_impl.defaultness.is_some() {
97 bail!(item_impl.span(), "Default is not allowed");
98 }
99 let is_defined_resources_list = is_defined(&item_impl.items, "resources_list");
100 let mut b = McpBuilder::new();
101 let mut items_trait = Vec::new();
102 let mut items_type = Vec::new();
103 for mut item in item_impl.items {
104 match b.push(&mut item) {
105 Ok(true) => items_type.push(item),
106 Ok(false) => items_trait.push(item),
107 Err(e) => {
108 items_type.push(item);
109 es.push(e);
110 }
111 }
112 }
113 let b = b.build(&items_trait)?;
114 let (impl_generics, ty_generics, where_clause) = item_impl.generics.split_for_impl();
115
116 let self_ty = &item_impl.self_ty;
117 let attrs = &item_impl.attrs;
118 let ts = quote! {
119 #[automatically_derived]
120 #(#attrs)*
121 impl<#impl_generics> #trait_path for #self_ty #ty_generics #where_clause {
122 #(#items_trait)*
123 #b
124 }
125
126 #[automatically_derived]
127 #(#attrs)*
128 impl<#impl_generics> #self_ty #ty_generics #where_clause {
129 #(#items_type)*
130 }
131 };
132 if attr.dump {
133 dump_code(ts);
134 }
135 Ok(ts)
136}
137
138struct McpBuilder {
139 prompts: Vec<PromptEntry>,
140 resources: Vec<ResourceEntry>,
141 tools: Vec<ToolEntry>,
142}
143
144impl McpBuilder {
145 fn new() -> Self {
146 Self {
147 prompts: Vec::new(),
148 resources: Vec::new(),
149 tools: Vec::new(),
150 }
151 }
152 fn push(&mut self, item: &mut ImplItem) -> Result<bool> {
153 if let ImplItem::Fn(f) = item {
154 let Some(attr) = drain_attr(&mut f.attrs)? else {
155 return Ok(false);
156 };
157 match attr {
158 ItemAttr::Prompt(attr) => {
159 self.prompts.push(PromptEntry::from_impl_item_fn(f, attr)?)
160 }
161 ItemAttr::Resource(attr) => self
162 .resources
163 .push(ResourceEntry::from_impl_item_fn(f, attr)?),
164 ItemAttr::Tool(attr) => self.tools.push(ToolEntry::from_impl_item_fn(f, attr)?),
165 }
166 return Ok(true);
167 }
168 Ok(false)
169 }
170
171 fn build(&self, items: &[ImplItem]) -> Result<TokenStream> {
172 let capabilities = build_if(!is_defined(items, "capabilities"), || {
173 self.build_capabilities(items)
174 })?;
175 let prompts = build_if(!self.prompts.is_empty(), || self.build_prompts())?;
176 let resources = build_if(!self.resources.is_empty(), || self.build_resources(items))?;
177 let tools = build_if(!self.tools.is_empty(), || self.build_tools())?;
178 Ok(quote! {
179 #capabilities
180 #prompts
181 #resources
182 #tools
183 })
184 }
185 fn build_capabilities(&self, items: &[ImplItem]) -> Result<TokenStream> {
186 let prompts = if !self.prompts.is_empty() || is_defined(items, "prompts_list") {
187 quote!(Some(::mcp_attr::schema::ServerCapabilitiesPrompts {
188 ..::std::default::Default::default()
189 }))
190 } else {
191 quote!(None)
192 };
193 let resources = if !self.resources.is_empty() || is_defined(items, "resources_read") {
194 quote!(Some(::mcp_attr::schema::ServerCapabilitiesResources {
195 ..::std::default::Default::default()
196 }))
197 } else {
198 quote!(None)
199 };
200 let tools = if !self.tools.is_empty() || is_defined(items, "tools_list") {
201 quote!(Some(::mcp_attr::schema::ServerCapabilitiesTools {
202 ..::std::default::Default::default()
203 }))
204 } else {
205 quote!(None)
206 };
207 Ok(quote! {
208 fn capabilities(&self) -> ::mcp_attr::schema::ServerCapabilities {
209 ::mcp_attr::schema::ServerCapabilities {
210 prompts: #prompts,
211 resources: #resources,
212 tools: #tools,
213 ..::std::default::Default::default()
214 }
215 }
216 })
217 }
218 fn build_prompts(&self) -> Result<TokenStream> {
219 let list = self.build_prompts_list()?;
220 let get = self.build_prompts_get()?;
221 Ok(quote! {
222 #list
223 #get
224 })
225 }
226 fn build_resources(&self, items: &[ImplItem]) -> Result<TokenStream> {
227 let list = build_if(!is_defined(items, "resources_list"), || {
228 self.build_resources_list()
229 })?;
230 let templates_list = self.build_resources_templates_list()?;
231 let read = self.build_resources_read()?;
232 Ok(quote! {
233 #list
234 #templates_list
235 #read
236 })
237 }
238 fn build_tools(&self) -> Result<TokenStream> {
239 let list = self.build_tools_list()?;
240 let call = self.build_tools_call()?;
241 Ok(quote! {
242 #list
243 #call
244 })
245 }
246 fn build_prompts_list(&self) -> Result<TokenStream> {
247 PromptEntry::build_list(&self.prompts)
248 }
249 fn build_prompts_get(&self) -> Result<TokenStream> {
250 PromptEntry::build_get(&self.prompts)
251 }
252 fn build_resources_list(&self) -> Result<TokenStream> {
253 ResourceEntry::build_list(&self.resources)
254 }
255 fn build_resources_templates_list(&self) -> Result<TokenStream> {
256 ResourceEntry::build_templates_list(&self.resources)
257 }
258 fn build_resources_read(&self) -> Result<TokenStream> {
259 ResourceEntry::build_read(&self.resources)
260 }
261
262 fn build_tools_list(&self) -> Result<TokenStream> {
263 ToolEntry::build_list(&self.tools)
264 }
265 fn build_tools_call(&self) -> Result<TokenStream> {
266 ToolEntry::build_call(&self.tools)
267 }
268}
269
270#[derive(StructMeta, Default)]
271struct McpAttr {
272 dump: bool,
273}
274
275enum ItemAttr {
276 Prompt(PromptAttr),
277 Resource(ResourceAttr),
278 Tool(ToolAttr),
279}
280
281fn build_route(item: TokenStream) -> Result<TokenStream> {
282 struct PathList(Punctuated<Path, Token![,]>);
283 impl Parse for PathList {
284 fn parse(input: ParseStream) -> Result<Self> {
285 Ok(Self(Punctuated::parse_terminated(input)?))
286 }
287 }
288 let mut path_list: PathList = parse2(item)?;
289 let mut exprs = Vec::new();
290 for mut path in path_list.0 {
291 let last = path.segments.last_mut().unwrap();
292 let fn_ident = last.ident.clone();
293 last.ident = route_ident(&fn_ident);
294 last.ident.set_span(Span::call_site());
295 exprs.push(quote! {
296 {
297 let _dummy = #fn_ident; ::std::convert::Into::<::mcp_attr::server::builder::Route>::into(#path()?)
299 }
300 });
301 }
302 Ok(quote! {
303 [#(#exprs),*]
304 })
305}
306fn route_ident(ident: &Ident) -> Ident {
307 format_ident!("__route_of_{}", ident)
308}
309fn build_tool(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
310 let mut f: ItemFn = parse2(item)?;
311 let attr: ToolAttr = parse2(attr)?;
312 let dump = attr.dump;
313 let e = ToolEntry::from_item_fn(&mut f, attr)?;
314 let ret = e.build_route();
315 Ok(make_extend(f, ret, dump))
316}
317
318fn build_resource(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
319 let mut f: ItemFn = parse2(item)?;
320 let attr: ResourceAttr = parse2(attr)?;
321 let dump = attr.dump;
322 let e = ResourceEntry::from_item_fn(&mut f, attr)?;
323 let ret = e.build_route();
324 Ok(make_extend(f, ret, dump))
325}
326
327fn build_prompt(attr: TokenStream, item: TokenStream) -> Result<TokenStream> {
328 let mut f: ItemFn = parse2(item)?;
329 let attr: PromptAttr = parse2(attr)?;
330 let dump = attr.dump;
331 let e = PromptEntry::from_item_fn(&mut f, attr)?;
332 let ret = e.build_route();
333 Ok(make_extend(f, ret, dump))
334}
335
336fn make_extend(source: impl ToTokens, code: Result<TokenStream>, dump: bool) -> TokenStream {
337 let code = match code {
338 Ok(code) => {
339 if dump {
340 dump_code(code);
341 }
342 code
343 }
344 Err(e) => e.to_compile_error(),
345 };
346 quote! {
347 #source
348 #code
349 }
350}
351fn dump_code(code: TokenStream) -> ! {
352 panic!("// ===== start generated code =====\n{code}\n// ===== end generated code =====\n");
353}