feat(server): add annotation to tool macro (#184)
skip serializing the tool annotations if none, fixes mcp inspector
This commit is contained in:
parent
5e4e77efa5
commit
68ddea3ad5
2 changed files with 124 additions and 3 deletions
|
|
@ -18,6 +18,7 @@ proc-macro = true
|
|||
syn = {version = "2", features = ["full"]}
|
||||
quote = "1"
|
||||
proc-macro2 = "1"
|
||||
serde_json = "1.0"
|
||||
|
||||
|
||||
[features]
|
||||
|
|
|
|||
|
|
@ -2,11 +2,45 @@ use std::collections::HashSet;
|
|||
|
||||
use proc_macro2::TokenStream;
|
||||
use quote::{ToTokens, quote};
|
||||
use serde_json::json;
|
||||
use syn::{
|
||||
Expr, FnArg, Ident, ItemFn, ItemImpl, MetaList, PatType, Token, Type, Visibility, parse::Parse,
|
||||
parse_quote, spanned::Spanned,
|
||||
Expr, FnArg, Ident, ItemFn, ItemImpl, Lit, MetaList, PatType, Token, Type, Visibility,
|
||||
parse::Parse, parse_quote, spanned::Spanned,
|
||||
};
|
||||
|
||||
/// Stores tool annotation attributes
|
||||
#[derive(Default, Clone)]
|
||||
struct ToolAnnotationAttrs(pub serde_json::Map<String, serde_json::Value>);
|
||||
|
||||
impl Parse for ToolAnnotationAttrs {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut attrs = serde_json::Map::new();
|
||||
|
||||
while !input.is_empty() {
|
||||
let key: Ident = input.parse()?;
|
||||
input.parse::<Token![:]>()?;
|
||||
let value: Lit = input.parse()?;
|
||||
let value = match value {
|
||||
Lit::Str(s) => json!(s.value()),
|
||||
Lit::Bool(b) => json!(b.value),
|
||||
_ => {
|
||||
return Err(syn::Error::new(
|
||||
key.span(),
|
||||
"annotations must be string or boolean literals",
|
||||
));
|
||||
}
|
||||
};
|
||||
attrs.insert(key.to_string(), value);
|
||||
if input.is_empty() {
|
||||
break;
|
||||
}
|
||||
input.parse::<Token![,]>()?;
|
||||
}
|
||||
|
||||
Ok(ToolAnnotationAttrs(attrs))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct ToolImplItemAttrs {
|
||||
tool_box: Option<Option<Ident>>,
|
||||
|
|
@ -45,6 +79,7 @@ struct ToolFnItemAttrs {
|
|||
name: Option<Expr>,
|
||||
description: Option<Expr>,
|
||||
vis: Option<Visibility>,
|
||||
annotations: Option<ToolAnnotationAttrs>,
|
||||
}
|
||||
|
||||
impl Parse for ToolFnItemAttrs {
|
||||
|
|
@ -52,6 +87,8 @@ impl Parse for ToolFnItemAttrs {
|
|||
let mut name = None;
|
||||
let mut description = None;
|
||||
let mut vis = None;
|
||||
let mut annotations = None;
|
||||
|
||||
while !input.is_empty() {
|
||||
let key: Ident = input.parse()?;
|
||||
input.parse::<Token![=]>()?;
|
||||
|
|
@ -68,6 +105,13 @@ impl Parse for ToolFnItemAttrs {
|
|||
let value: Visibility = input.parse()?;
|
||||
vis = Some(value);
|
||||
}
|
||||
"annotations" => {
|
||||
// Parse the annotations as a nested structure
|
||||
let content;
|
||||
syn::braced!(content in input);
|
||||
let value = content.parse()?;
|
||||
annotations = Some(value);
|
||||
}
|
||||
_ => {
|
||||
return Err(syn::Error::new(key.span(), "unknown attribute"));
|
||||
}
|
||||
|
|
@ -82,6 +126,7 @@ impl Parse for ToolFnItemAttrs {
|
|||
name,
|
||||
description,
|
||||
vis,
|
||||
annotations,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -470,6 +515,17 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
|
|||
};
|
||||
let input_fn_attrs = &input_fn.attrs;
|
||||
let input_fn_vis = &input_fn.vis;
|
||||
|
||||
let annotations_code = if let Some(annotations) = &tool_macro_attrs.fn_item.annotations {
|
||||
let annotations =
|
||||
serde_json::to_string(&annotations.0).expect("failed to serialize annotations");
|
||||
quote! {
|
||||
Some(serde_json::from_str::<rmcp::model::ToolAnnotations>(&#annotations).expect("Could not parse tool annotations"))
|
||||
}
|
||||
} else {
|
||||
quote! { None }
|
||||
};
|
||||
|
||||
quote! {
|
||||
#(#input_fn_attrs)*
|
||||
#input_fn_vis fn #tool_attr_fn_ident() -> rmcp::model::Tool {
|
||||
|
|
@ -477,7 +533,7 @@ pub(crate) fn tool_fn_item(attr: TokenStream, mut input_fn: ItemFn) -> syn::Resu
|
|||
name: #name.into(),
|
||||
description: Some(#description.into()),
|
||||
input_schema: #schema.into(),
|
||||
annotations: None
|
||||
annotations: #annotations_code,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue