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
|
|
@ -37,11 +37,13 @@ pub struct Tool {
|
|||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ToolAnnotations {
|
||||
/// A human-readable title for the tool.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub title: Option<String>,
|
||||
|
||||
/// If true, the tool does not modify its environment.
|
||||
///
|
||||
/// Default: false
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub read_only_hint: Option<bool>,
|
||||
|
||||
/// If true, the tool may perform destructive updates to its environment.
|
||||
|
|
@ -51,6 +53,7 @@ pub struct ToolAnnotations {
|
|||
///
|
||||
/// Default: true
|
||||
/// A human-readable description of the tool's purpose.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub destructive_hint: Option<bool>,
|
||||
|
||||
/// If true, calling the tool repeatedly with the same arguments
|
||||
|
|
@ -59,6 +62,7 @@ pub struct ToolAnnotations {
|
|||
/// (This property is meaningful only when `readOnlyHint == false`)
|
||||
///
|
||||
/// Default: false.
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub idempotent_hint: Option<bool>,
|
||||
|
||||
/// If true, this tool may interact with an "open world" of external
|
||||
|
|
@ -67,6 +71,7 @@ pub struct ToolAnnotations {
|
|||
/// of a memory tool is not.
|
||||
///
|
||||
/// Default: true
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub open_world_hint: Option<bool>,
|
||||
}
|
||||
|
||||
|
|
|
|||
59
crates/rmcp/tests/test_tool_macro_annotations.rs
Normal file
59
crates/rmcp/tests/test_tool_macro_annotations.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use rmcp::{ServerHandler, tool};
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct AnnotatedServer {}
|
||||
|
||||
impl AnnotatedServer {
|
||||
// Tool with inline comments for documentation
|
||||
/// Direct annotation test tool
|
||||
/// This is used to test tool annotations
|
||||
#[tool(
|
||||
name = "direct-annotated-tool",
|
||||
annotations = {
|
||||
title: "Annotated Tool",
|
||||
readOnlyHint: true
|
||||
}
|
||||
)]
|
||||
pub async fn direct_annotated_tool(&self, #[tool(param)] input: String) -> String {
|
||||
format!("Direct: {}", input)
|
||||
}
|
||||
}
|
||||
|
||||
impl ServerHandler for AnnotatedServer {
|
||||
async fn call_tool(
|
||||
&self,
|
||||
request: rmcp::model::CallToolRequestParam,
|
||||
context: rmcp::service::RequestContext<rmcp::RoleServer>,
|
||||
) -> Result<rmcp::model::CallToolResult, rmcp::Error> {
|
||||
let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context);
|
||||
match tcc.name() {
|
||||
"direct-annotated-tool" => Self::direct_annotated_tool_tool_call(tcc).await,
|
||||
_ => Err(rmcp::Error::invalid_params("method not found", None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_direct_tool_attributes() {
|
||||
// Get the tool definition
|
||||
let tool = AnnotatedServer::direct_annotated_tool_tool_attr();
|
||||
|
||||
// Verify basic properties
|
||||
assert_eq!(tool.name, "direct-annotated-tool");
|
||||
|
||||
// Verify description is extracted from doc comments
|
||||
assert!(tool.description.is_some());
|
||||
assert!(
|
||||
tool.description
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.contains("Direct annotation test tool")
|
||||
);
|
||||
|
||||
let annotations = tool.annotations.unwrap();
|
||||
assert_eq!(annotations.title.as_ref().unwrap(), "Annotated Tool");
|
||||
assert_eq!(annotations.read_only_hint, Some(true));
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue