alexhancock/rmcp-tools-annotations (#3617)

This commit is contained in:
Alex Hancock
2025-07-24 14:58:14 -04:00
committed by GitHub
parent 7b2ca43c77
commit b4fc6f7cc8
65 changed files with 770 additions and 731 deletions
+8 -4
View File
@@ -42,8 +42,9 @@ use crate::providers::errors::ProviderError;
use crate::recipe::{Author, Recipe, Response, Settings, SubRecipe};
use crate::scheduler_trait::SchedulerTrait;
use crate::tool_monitor::{ToolCall, ToolMonitor};
use mcp_core::{protocol::GetPromptResult, tool::Tool, ToolError, ToolResult};
use mcp_core::{protocol::GetPromptResult, ToolError, ToolResult};
use regex::Regex;
use rmcp::model::Tool;
use rmcp::model::{Content, JsonRpcMessage, Prompt};
use serde_json::Value;
use tokio::sync::{mpsc, Mutex, RwLock};
@@ -538,10 +539,10 @@ impl Agent {
let mut frontend_tools = self.frontend_tools.lock().await;
for tool in tools {
let frontend_tool = FrontendTool {
name: tool.name.clone(),
name: tool.name.to_string(),
tool: tool.clone(),
};
frontend_tools.insert(tool.name.clone(), frontend_tool);
frontend_tools.insert(tool.name.to_string(), frontend_tool);
}
// Store instructions if provided, using "frontend" as the key
let mut frontend_instructions = self.frontend_instructions.lock().await;
@@ -1181,7 +1182,10 @@ impl Agent {
.map(|tool| {
ToolInfo::new(
&tool.name,
&tool.description,
tool.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default(),
get_parameter_names(&tool),
None,
)
+1 -1
View File
@@ -1,7 +1,7 @@
use std::collections::HashMap;
use mcp_client::client::Error as ClientError;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use serde::{Deserialize, Serialize};
use thiserror::Error;
use tracing::warn;
+14 -9
View File
@@ -19,8 +19,8 @@ use crate::config::{Config, ExtensionConfigManager};
use crate::prompt_template;
use mcp_client::client::{ClientCapabilities, ClientInfo, McpClient, McpClientTrait};
use mcp_client::transport::{SseTransport, StdioTransport, StreamableHttpTransport, Transport};
use mcp_core::{Tool, ToolCall, ToolError};
use rmcp::model::{Content, Prompt, Resource, ResourceContents};
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{Content, Prompt, Resource, ResourceContents, Tool};
use serde_json::Value;
// By default, we set it to Jan 1, 2020 if the resource does not have a timestamp
@@ -380,13 +380,18 @@ impl ExtensionManager {
let mut client_tools = client_guard.list_tools(None).await?;
loop {
for tool in client_tools.tools {
tools.push(Tool::new(
format!("{}__{}", name, tool.name),
&tool.description,
tool.input_schema,
tool.annotations,
));
for client_tool in client_tools.tools {
let mut tool = Tool::new(
format!("{}__{}", name, client_tool.name),
client_tool.description.unwrap_or_default(),
client_tool.input_schema,
);
if tool.annotations.is_some() {
tool = tool.annotate(client_tool.annotations.unwrap())
}
tools.push(tool);
}
// Exit loop when there are no more pages
+16 -13
View File
@@ -1,11 +1,8 @@
use crate::agents::tool_execution::ToolCallResult;
use crate::recipe::Response;
use indoc::formatdoc;
use mcp_core::{
tool::{Tool, ToolAnnotations},
ToolCall, ToolError,
};
use rmcp::model::Content;
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{Content, Tool, ToolAnnotations};
use serde_json::Value;
pub const FINAL_OUTPUT_TOOL_NAME: &str = "recipe__final_output";
@@ -64,15 +61,21 @@ impl FinalOutputTool {
Tool::new(
FINAL_OUTPUT_TOOL_NAME.to_string(),
instructions,
self.response.json_schema.as_ref().unwrap().clone(),
Some(ToolAnnotations {
title: Some("Final Output".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
self.response
.json_schema
.as_ref()
.unwrap()
.as_object()
.unwrap()
.clone(),
)
.annotate(ToolAnnotations {
title: Some("Final Output".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
})
}
pub fn system_prompt(&self) -> String {
+44 -48
View File
@@ -1,6 +1,6 @@
use indoc::indoc;
use mcp_core::tool::{Tool, ToolAnnotations};
use serde_json::json;
use rmcp::model::{Tool, ToolAnnotations};
use rmcp::object;
pub const PLATFORM_READ_RESOURCE_TOOL_NAME: &str = "platform__read_resource";
pub const PLATFORM_LIST_RESOURCES_TOOL_NAME: &str = "platform__list_resources";
@@ -20,22 +20,21 @@ pub fn read_resource_tool() -> Tool {
resource URI in the provided extension, and reads in the resource content. If no extension
is provided, the tool will search all extensions for the resource.
"#}.to_string(),
json!({
object!({
"type": "object",
"required": ["uri"],
"properties": {
"uri": {"type": "string", "description": "Resource URI"},
"extension_name": {"type": "string", "description": "Optional extension name"}
}
}),
Some(ToolAnnotations {
title: Some("Read a resource".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Read a resource".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn list_resources_tool() -> Tool {
@@ -50,20 +49,20 @@ pub fn list_resources_tool() -> Tool {
is provided, the tool will search all extensions for the resource.
"#}
.to_string(),
json!({
object!({
"type": "object",
"properties": {
"extension_name": {"type": "string", "description": "Optional extension name"}
}
}),
Some(ToolAnnotations {
title: Some("List resources".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
.annotate(ToolAnnotations {
title: Some("List resources".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn search_available_extensions_tool() -> Tool {
@@ -73,19 +72,18 @@ pub fn search_available_extensions_tool() -> Tool {
Use this tool when you're unable to find a specific feature or functionality you need to complete your task, or when standard approaches aren't working.
These extensions might provide the exact tools needed to solve your problem.
If you find a relevant one, consider using your tools to enable it.".to_string(),
json!({
object!({
"type": "object",
"required": [],
"properties": {}
}),
Some(ToolAnnotations {
title: Some("Discover extensions".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Discover extensions".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn manage_extensions_tool() -> Tool {
@@ -96,7 +94,7 @@ pub fn manage_extensions_tool() -> Tool {
Enable or disable an extension by providing the extension name.
"
.to_string(),
json!({
object!({
"type": "object",
"required": ["action", "extension_name"],
"properties": {
@@ -104,14 +102,13 @@ pub fn manage_extensions_tool() -> Tool {
"extension_name": {"type": "string", "description": "The name of the extension to enable"}
}
}),
Some(ToolAnnotations {
title: Some("Enable or disable an extension".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
).annotate(ToolAnnotations {
title: Some("Enable or disable an extension".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn manage_schedule_tool() -> Tool {
@@ -133,7 +130,7 @@ pub fn manage_schedule_tool() -> Tool {
- "session_content": Get the full content (messages) of a specific session
"#}
.to_string(),
json!({
object!({
"type": "object",
"required": ["action"],
"properties": {
@@ -149,12 +146,11 @@ pub fn manage_schedule_tool() -> Tool {
"session_id": {"type": "string", "description": "Session identifier for session_content action"}
}
}),
Some(ToolAnnotations {
title: Some("Manage scheduled recipes".to_string()),
read_only_hint: false,
destructive_hint: true, // Can kill jobs
idempotent_hint: false,
open_world_hint: false,
}),
)
).annotate(ToolAnnotations {
title: Some("Manage scheduled recipes".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true), // Can kill jobs
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
@@ -5,8 +5,9 @@
use crate::agents::subagent_execution_tool::tasks_manager::TasksManager;
use crate::agents::subagent_execution_tool::{lib::ExecutionMode, task_types::Task};
use crate::agents::tool_execution::ToolCallResult;
use mcp_core::{tool::ToolAnnotations, Tool, ToolError};
use rmcp::model::Content;
use mcp_core::ToolError;
use rmcp::model::{Content, Tool, ToolAnnotations};
use rmcp::object;
use serde_json::{json, Value};
pub const DYNAMIC_TASK_TOOL_NAME_PREFIX: &str = "dynamic_task__create_task";
@@ -35,7 +36,7 @@ pub fn create_dynamic_task_tool() -> Tool {
text_instruction: Get weather for Los Angeles.
text_instruction: Get weather for San Francisco.
".to_string(),
json!({
object!({
"type": "object",
"properties": {
"task_parameters": {
@@ -56,15 +57,14 @@ pub fn create_dynamic_task_tool() -> Tool {
}
}
}
}),
Some(ToolAnnotations {
title: Some("Dynamic Task Creation".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: true,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Dynamic Task Creation".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(true),
})
}
fn extract_task_parameters(params: &Value) -> Vec<Value> {
@@ -1,8 +1,9 @@
use std::collections::HashSet;
use std::fs;
use std::sync::Arc;
use anyhow::Result;
use mcp_core::tool::{Tool, ToolAnnotations};
use rmcp::model::{Tool, ToolAnnotations};
use serde_json::{json, Map, Value};
use crate::agents::subagent_execution_tool::lib::{ExecutionMode, Task};
@@ -15,6 +16,7 @@ pub const SUB_RECIPE_TASK_TOOL_NAME_PREFIX: &str = "subrecipe__create_task";
pub fn create_sub_recipe_task_tool(sub_recipe: &SubRecipe) -> Tool {
let input_schema = get_input_schema(sub_recipe).unwrap();
Tool::new(
format!("{}_{}", SUB_RECIPE_TASK_TOOL_NAME_PREFIX, sub_recipe.name),
format!(
@@ -27,15 +29,17 @@ pub fn create_sub_recipe_task_tool(sub_recipe: &SubRecipe) -> Tool {
After creating the tasks and execution_mode is provided, pass them to the task executor to run these tasks",
sub_recipe.name
),
input_schema,
Some(ToolAnnotations {
title: Some(format!("create multiple sub recipe tasks for {}", sub_recipe.name)),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: true,
}),
)
Arc::new(input_schema.as_object().unwrap().clone())
).annotate(ToolAnnotations {
title: Some(format!(
"create multiple sub recipe tasks for {}",
sub_recipe.name
)),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(true),
})
}
fn extract_task_parameters(params: &Value) -> Vec<Value> {
+4 -4
View File
@@ -15,7 +15,7 @@ use crate::providers::toolshim::{
modify_system_prompt_for_tool_json, OllamaInterpreter,
};
use crate::session;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use super::super::agents::Agent;
@@ -110,11 +110,11 @@ impl Agent {
.iter()
.fold((HashSet::new(), HashSet::new()), |mut acc, tool| {
match &tool.annotations {
Some(annotations) if annotations.read_only_hint => {
acc.0.insert(tool.name.clone());
Some(annotations) if annotations.read_only_hint.unwrap_or(false) => {
acc.0.insert(tool.name.to_string());
}
_ => {
acc.1.insert(tool.name.clone());
acc.1.insert(tool.name.to_string());
}
}
acc
@@ -1,6 +1,6 @@
use mcp_core::tool::Tool;
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::Tool;
use anyhow::{Context, Result};
use async_trait::async_trait;
@@ -128,7 +128,15 @@ impl RouterToolSelector for VectorToolSelector {
.map(|tool| {
let schema_str = serde_json::to_string_pretty(&tool.input_schema)
.unwrap_or_else(|_| "{}".to_string());
format!("{} {} {}", tool.name, tool.description, schema_str)
format!(
"{} {} {}",
tool.name,
tool.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default(),
schema_str
)
})
.collect();
@@ -154,8 +162,12 @@ impl RouterToolSelector for VectorToolSelector {
let schema_str = serde_json::to_string_pretty(&tool.input_schema)
.unwrap_or_else(|_| "{}".to_string());
crate::agents::tool_vectordb::ToolRecord {
tool_name: tool.name.clone(),
description: tool.description.clone(),
tool_name: tool.name.to_string(),
description: tool
.description
.as_ref()
.map(|d| d.to_string())
.unwrap_or_default(),
schema: schema_str,
vector,
extension_name: extension_name.to_string(),
@@ -305,7 +317,10 @@ impl RouterToolSelector for LLMToolSelector {
let tool_string = format!(
"Tool: {}\nDescription: {}\nSchema: {}",
tool.name,
tool.description,
tool.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default(),
serde_json::to_string_pretty(&tool.input_schema)
.unwrap_or_else(|_| "{}".to_string())
);
+20 -22
View File
@@ -3,8 +3,8 @@ use super::platform_tools::{
PLATFORM_READ_RESOURCE_TOOL_NAME, PLATFORM_SEARCH_AVAILABLE_EXTENSIONS_TOOL_NAME,
};
use indoc::indoc;
use mcp_core::tool::{Tool, ToolAnnotations};
use serde_json::json;
use rmcp::model::{Tool, ToolAnnotations};
use rmcp::object;
pub const ROUTER_VECTOR_SEARCH_TOOL_NAME: &str = "router__vector_search";
pub const ROUTER_LLM_SEARCH_TOOL_NAME: &str = "router__llm_search";
@@ -24,7 +24,7 @@ pub fn vector_search_tool() -> Tool {
Extension name is not optional, it is required.
"#}
.to_string(),
json!({
object!({
"type": "object",
"required": ["query", "extension_name"],
"properties": {
@@ -32,15 +32,14 @@ pub fn vector_search_tool() -> Tool {
"k": {"type": "integer", "description": "The number of tools to retrieve (defaults to 5)", "default": 5},
"extension_name": {"type": "string", "description": "Name of the extension to filter tools by"}
}
}),
Some(ToolAnnotations {
title: Some("Vector search for relevant tools".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Vector search for relevant tools".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn vector_search_tool_prompt() -> String {
@@ -81,7 +80,7 @@ pub fn llm_search_tool() -> Tool {
The returned result will be a list of tool names, descriptions, and schemas from which you, the agent can select the most relevant tool to invoke.
"#}
.to_string(),
json!({
object!({
"type": "object",
"required": ["query", "extension_name"],
"properties": {
@@ -89,15 +88,14 @@ pub fn llm_search_tool() -> Tool {
"query": {"type": "string", "description": "The query to search for the most relevant tools based on the user's messages"},
"k": {"type": "integer", "description": "The number of tools to retrieve (defaults to 5)", "default": 5}
}
}),
Some(ToolAnnotations {
title: Some("LLM search for relevant tools".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
})
).annotate(ToolAnnotations {
title: Some("LLM search for relevant tools".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
pub fn llm_search_tool_prompt() -> String {
@@ -1,5 +1,6 @@
use mcp_core::{Tool, ToolError};
use mcp_core::ToolError;
use rmcp::model::Content;
use rmcp::model::Tool;
use serde_json::Value;
use std::collections::HashMap;
+5 -4
View File
@@ -9,7 +9,8 @@ use crate::{
};
use anyhow::anyhow;
use chrono::{DateTime, Utc};
use mcp_core::{handler::ToolError, tool::Tool};
use mcp_core::handler::ToolError;
use rmcp::model::Tool;
use serde::{Deserialize, Serialize};
// use serde_json::{self};
use std::{collections::HashMap, sync::Arc};
@@ -336,10 +337,10 @@ impl SubAgent {
let tools_with_descriptions: Vec<String> = available_tools
.iter()
.map(|t| {
if t.description.is_empty() {
t.name.clone()
if let Some(description) = &t.description {
format!("{}: {}", t.name, description)
} else {
format!("{}: {}", t.name, t.description)
t.name.to_string()
}
})
.collect();
@@ -1,5 +1,5 @@
use mcp_core::{tool::ToolAnnotations, Tool, ToolError};
use rmcp::model::Content;
use mcp_core::ToolError;
use rmcp::model::{Content, Tool, ToolAnnotations};
use serde_json::Value;
use crate::agents::subagent_task_config::TaskConfig;
@@ -9,6 +9,7 @@ use crate::agents::{
subagent_execution_tool::tasks_manager::TasksManager, tool_execution::ToolCallResult,
};
use rmcp::model::JsonRpcMessage;
use rmcp::object;
use tokio::sync::mpsc;
use tokio_stream;
use tokio_util::sync::CancellationToken;
@@ -18,21 +19,21 @@ pub fn create_subagent_execute_task_tool() -> Tool {
Tool::new(
SUBAGENT_EXECUTE_TASK_TOOL_NAME,
"Only use the subagent__execute_task tool when you execute sub recipe task or dynamic task.
EXECUTION STRATEGY DECISION:
1. If the tasks are created with execution_mode, use the execution_mode.
2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'
EXECUTION STRATEGY DECISION:
1. If the tasks are created with execution_mode, use the execution_mode.
2. Execute tasks sequentially unless user explicitly requests parallel execution. PARALLEL: User uses keywords like 'parallel', 'simultaneously', 'at the same time', 'concurrently'
IMPLEMENTATION:
- Sequential execution: Call this tool multiple times, passing exactly ONE task per call
- Parallel execution: Call this tool once, passing an ARRAY of all tasks
IMPLEMENTATION:
- Sequential execution: Call this tool multiple times, passing exactly ONE task per call
- Parallel execution: Call this tool once, passing an ARRAY of all tasks
EXAMPLES:
User Intent Based:
- User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)
- User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)
- User: 'run these simultaneously' → Parallel (1 tool call with task array)
- User: 'do task A then task B' → Sequential (2 separate tool calls)",
serde_json::json!({
EXAMPLES:
User Intent Based:
- User: 'get weather and tell me a joke' → Sequential (2 separate tool calls, 1 task each)
- User: 'get weather and joke in parallel' → Parallel (1 tool call with array of 2 tasks)
- User: 'run these simultaneously' → Parallel (1 tool call with task array)
- User: 'do task A then task B' → Sequential (2 separate tool calls)",
object!({
"type": "object",
"properties": {
"execution_mode": {
@@ -50,15 +51,14 @@ User Intent Based:
}
},
"required": ["task_ids"]
}),
Some(ToolAnnotations {
title: Some("Run tasks in parallel".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: true,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Run tasks in parallel".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(true),
})
}
pub async fn run_tasks(
+2 -2
View File
@@ -1,6 +1,6 @@
use crate::session;
use mcp_core::{Tool, ToolResult};
use rmcp::model::Content;
use mcp_core::ToolResult;
use rmcp::model::{Content, Tool};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::sync::Arc;