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
+18 -16
View File
@@ -15,9 +15,9 @@ use goose::config::{
};
use goose::message::Message;
use goose::providers::{create, providers};
use mcp_core::tool::ToolAnnotations;
use mcp_core::Tool;
use serde_json::{json, Value};
use rmcp::model::{Tool, ToolAnnotations};
use rmcp::object;
use serde_json::Value;
use std::collections::HashMap;
use std::error::Error;
@@ -387,21 +387,21 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
let sample_tool = Tool::new(
"get_weather".to_string(),
"Get current temperature for a given location.".to_string(),
json!({
object!({
"type": "object",
"required": ["location"],
"properties": {
"location": {"type": "string"}
}
}),
Some(ToolAnnotations {
title: Some("Get weather".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Get weather".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
vec![sample_tool]
} else {
vec![]
@@ -411,9 +411,8 @@ pub async fn configure_provider_dialog() -> Result<bool, Box<dyn Error>> {
.complete(
"You are an AI agent called Goose. You use tools of connected extensions to solve problems.",
&messages,
&tools
)
.await;
&tools.into_iter().collect::<Vec<_>>()
).await;
match result {
Ok((_message, _usage)) => {
@@ -1270,7 +1269,10 @@ pub async fn configure_tool_permissions_dialog() -> Result<(), Box<dyn Error>> {
.map(|tool| {
ToolInfo::new(
&tool.name,
&tool.description,
tool.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default(),
get_parameter_names(&tool),
permission_manager.get_user_permission(&tool.name),
)
+28 -31
View File
@@ -2,7 +2,7 @@ use base64::Engine;
use etcetera::{choose_app_strategy, AppStrategy};
use indoc::{formatdoc, indoc};
use reqwest::{Client, Url};
use serde_json::{json, Value};
use serde_json::Value;
use std::{
collections::HashMap, fs, future::Future, path::PathBuf, pin::Pin, sync::Arc, sync::Mutex,
};
@@ -14,11 +14,13 @@ use std::os::unix::fs::PermissionsExt;
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
tool::{Tool, ToolAnnotations},
};
use mcp_server::router::CapabilitiesBuilder;
use mcp_server::Router;
use rmcp::model::{AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource};
use rmcp::model::{
AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource, Tool, ToolAnnotations,
};
use rmcp::object;
mod docx_tool;
mod pdf_tool;
@@ -58,7 +60,7 @@ impl ComputerControllerRouter {
The content is cached locally and can be accessed later using the cache_path
returned in the response.
"#},
json!({
object!({
"type": "object",
"required": ["url"],
"properties": {
@@ -74,14 +76,14 @@ impl ComputerControllerRouter {
}
}
}),
Some(ToolAnnotations {
title: Some("Web Scrape".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: true,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Web Scrape".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(true),
});
let computer_control_desc = match std::env::consts::OS {
"windows" => indoc! {r#"
@@ -131,7 +133,7 @@ impl ComputerControllerRouter {
let computer_control_tool = Tool::new(
"computer_control",
computer_control_desc.to_string(),
json!({
object!({
"type": "object",
"required": ["script"],
"properties": {
@@ -146,7 +148,6 @@ impl ComputerControllerRouter {
}
}
}),
None,
);
let quick_script_desc = match std::env::consts::OS {
@@ -177,7 +178,7 @@ impl ComputerControllerRouter {
let quick_script_tool = Tool::new(
"automation_script",
quick_script_desc.to_string(),
json!({
object!({
"type": "object",
"required": ["language", "script"],
"properties": {
@@ -197,7 +198,6 @@ impl ComputerControllerRouter {
}
}
}),
None,
);
let cache_tool = Tool::new(
@@ -209,7 +209,7 @@ impl ComputerControllerRouter {
- delete: Delete a cached file
- clear: Clear all cached files
"#},
json!({
object!({
"type": "object",
"required": ["command"],
"properties": {
@@ -224,7 +224,6 @@ impl ComputerControllerRouter {
}
}
}),
None,
);
let pdf_tool = Tool::new(
@@ -237,7 +236,7 @@ impl ComputerControllerRouter {
Use this when there is a .pdf file or files that need to be processed.
"#},
json!({
object!({
"type": "object",
"required": ["path", "operation"],
"properties": {
@@ -252,14 +251,14 @@ impl ComputerControllerRouter {
}
}
}),
Some(ToolAnnotations {
title: Some("PDF process".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("PDF process".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});
let docx_tool = Tool::new(
"docx_tool",
@@ -276,7 +275,7 @@ impl ComputerControllerRouter {
Use this when there is a .docx file that needs to be processed or created.
"#},
json!({
object!({
"type": "object",
"required": ["path", "operation"],
"properties": {
@@ -357,7 +356,6 @@ impl ComputerControllerRouter {
}
}
}),
None,
);
let xlsx_tool = Tool::new(
@@ -375,7 +373,7 @@ impl ComputerControllerRouter {
Use this when working with Excel spreadsheets to analyze or modify data.
"#},
json!({
object!({
"type": "object",
"required": ["path", "operation"],
"properties": {
@@ -419,7 +417,6 @@ impl ComputerControllerRouter {
}
}
}),
None,
);
// choose_app_strategy().cache_dir()
+69 -61
View File
@@ -6,7 +6,7 @@ use anyhow::Result;
use base64::Engine;
use etcetera::{choose_app_strategy, AppStrategy};
use indoc::formatdoc;
use serde_json::{json, Value};
use serde_json::Value;
use std::{
collections::HashMap,
future::Future,
@@ -25,7 +25,6 @@ use include_dir::{include_dir, Dir};
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
tool::{Tool, ToolAnnotations},
};
use mcp_server::router::CapabilitiesBuilder;
@@ -33,7 +32,7 @@ use mcp_server::Router;
use rmcp::model::{
Content, JsonRpcMessage, JsonRpcNotification, JsonRpcVersion2_0, Notification, Prompt,
PromptArgument, PromptTemplate, Resource, Role,
PromptArgument, PromptTemplate, Resource, Role, Tool, ToolAnnotations,
};
use rmcp::object;
@@ -166,14 +165,13 @@ impl DeveloperRouter {
let bash_tool = Tool::new(
"shell".to_string(),
shell_tool_desc.to_string(),
json!({
object!({
"type": "object",
"required": ["command"],
"properties": {
"command": {"type": "string"}
}
}),
None,
);
let glob_tool = Tool::new(
@@ -193,22 +191,21 @@ impl DeveloperRouter {
Use this tool when you need to locate files by name patterns rather than content.
"#}.to_string(),
json!({
object!({
"type": "object",
"required": ["pattern"],
"properties": {
"pattern": {"type": "string", "description": "The glob pattern to search for"},
"path": {"type": "string", "description": "The directory to search in (defaults to current directory)"}
}
}),
Some(ToolAnnotations {
title: Some("Search files by pattern".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Search files by pattern".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});
let grep_tool = Tool::new(
"grep".to_string(),
@@ -242,21 +239,20 @@ impl DeveloperRouter {
properly filters results to respect ignored files.
"#}
.to_string(),
json!({
object!({
"type": "object",
"required": ["command"],
"properties": {
"command": {"type": "string", "description": "The search command to execute (rg, grep, find, etc.)"}
}
}),
Some(ToolAnnotations {
title: Some("Search file contents".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Search file contents".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});
// Create text editor tool with different descriptions based on editor API configuration
let (text_editor_desc, str_replace_command) = if let Some(ref editor) = editor_model {
@@ -307,7 +303,7 @@ impl DeveloperRouter {
let text_editor_tool = Tool::new(
"text_editor".to_string(),
text_editor_desc.to_string(),
json!({
object!({
"type": "object",
"required": ["command", "path"],
"properties": {
@@ -336,7 +332,6 @@ impl DeveloperRouter {
"file_text": {"type": "string"}
}
}),
None,
);
let list_windows_tool = Tool::new(
@@ -346,19 +341,19 @@ impl DeveloperRouter {
Returns a list of window titles that can be used with the window_title parameter
of the screen_capture tool.
"#},
json!({
object!({
"type": "object",
"required": [],
"properties": {}
}),
Some(ToolAnnotations {
title: Some("List available windows".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("List available windows".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let screen_capture_tool = Tool::new(
"screen_capture",
@@ -370,7 +365,7 @@ impl DeveloperRouter {
Only one of display or window_title should be specified.
"#},
json!({
object!({
"type": "object",
"required": [],
"properties": {
@@ -385,15 +380,14 @@ impl DeveloperRouter {
"description": "Optional: the exact title of the window to capture. use the list_windows tool to find the available windows."
}
}
}),
Some(ToolAnnotations {
title: Some("Capture a full screen".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Capture a full screen".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let image_processor_tool = Tool::new(
"image_processor",
@@ -405,7 +399,7 @@ impl DeveloperRouter {
This allows processing image files for use in the conversation.
"#},
json!({
object!({
"type": "object",
"required": ["path"],
"properties": {
@@ -415,14 +409,14 @@ impl DeveloperRouter {
}
}
}),
Some(ToolAnnotations {
title: Some("Process Image".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Process Image".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});
// Get base instructions and working directory
let cwd = std::env::current_dir().expect("should have a current working dir");
@@ -2342,20 +2336,34 @@ mod tests {
// Should use traditional description with str_replace command
assert!(text_editor_tool
.description
.contains("Replace a string in a file with a new string"));
.as_ref()
.map_or(false, |desc| desc
.contains("Replace a string in a file with a new string")));
assert!(text_editor_tool
.description
.contains("the `old_str` needs to exactly match one"));
assert!(text_editor_tool.description.contains("str_replace"));
.as_ref()
.map_or(false, |desc| desc
.contains("the `old_str` needs to exactly match one")));
assert!(text_editor_tool
.description
.as_ref()
.map_or(false, |desc| desc.contains("str_replace")));
// Should not contain editor API description or edit_file command
assert!(!text_editor_tool
.description
.contains("Edit the file with the new content"));
assert!(!text_editor_tool.description.contains("edit_file"));
.as_ref()
.map_or(false, |desc| desc
.contains("Edit the file with the new content")));
assert!(!text_editor_tool
.description
.contains("work out how to place old_str with it intelligently"));
.as_ref()
.map_or(false, |desc| desc.contains("edit_file")));
assert!(!text_editor_tool
.description
.as_ref()
.map_or(false, |desc| desc
.contains("work out how to place old_str with it intelligently")));
temp_dir.close().unwrap();
}
+121 -129
View File
@@ -7,17 +7,18 @@ use base64::Engine;
use chrono::NaiveDate;
use indoc::indoc;
use lazy_static::lazy_static;
use mcp_core::tool::ToolAnnotations;
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
tool::Tool,
};
use mcp_server::router::CapabilitiesBuilder;
use mcp_server::Router;
use oauth_pkce::PkceOAuth2Client;
use regex::Regex;
use rmcp::model::{AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource};
use rmcp::model::{
AnnotateAble, Content, JsonRpcMessage, Prompt, RawResource, Resource, Tool, ToolAnnotations,
};
use rmcp::object;
use serde_json::{json, Value};
use std::io::Cursor;
use std::{env, fs, future::Future, path::Path, pin::Pin, sync::Arc};
@@ -219,8 +220,8 @@ impl GoogleDriveRouter {
indoc! {r#"
List or search for files or labels in google drive by name, given an input search query. At least one of ('name', 'mimeType', or 'parent') are required for file searches.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"driveType": {
@@ -257,15 +258,14 @@ impl GoogleDriveRouter {
}
},
"required": ["driveType"],
}),
Some(ToolAnnotations {
title: Some("Search GDrive".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Search GDrive".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let read_tool = Tool::new(
"read".to_string(),
@@ -280,8 +280,8 @@ impl GoogleDriveRouter {
Pass in "gdrive:///1QG8d8wtWe7ZfmG93sW-1h2WXDJDUkOi-9hDnvJLmWrc"
Do not include any other path parameters when using URI.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"uri": {
@@ -297,23 +297,22 @@ impl GoogleDriveRouter {
"description": "Whether or not to include images as base64 encoded strings, defaults to false",
}
},
}),
Some(ToolAnnotations {
title: Some("Read GDrive".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Read GDrive".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false)
});
let create_file_tool = Tool::new(
"create_file".to_string(),
indoc! {r#"
Create a new file, including Document, Spreadsheet, Slides, folder, or shortcut, in Google Drive.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"name": {
@@ -346,23 +345,22 @@ impl GoogleDriveRouter {
}
},
"required": ["name", "mimeType"],
}),
Some(ToolAnnotations {
title: Some("Create new file in GDrive".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Create new file in GDrive".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let move_file_tool = Tool::new(
"move_file".to_string(),
indoc! {r#"
Move a Google Drive file, folder, or shortcut to a new parent folder. You cannot move a folder to a different drive.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"fileId": {
@@ -379,23 +377,22 @@ impl GoogleDriveRouter {
},
},
"required": ["fileId", "currentFolderId", "newFolderId"],
}),
Some(ToolAnnotations {
title: Some("Move file".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Move file".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let update_file_tool = Tool::new(
"update_file".to_string(),
indoc! {r#"
Update an existing file in Google Drive with new content or edit the file's labels.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"fileId": {
@@ -486,15 +483,14 @@ impl GoogleDriveRouter {
"body": ["mimeType"],
"path": ["mimeType"]
}
}),
Some(ToolAnnotations {
title: Some("Update a file's contents or labels".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Update a file's contents or labels".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let sheets_tool = Tool::new(
"sheets_tool".to_string(),
@@ -509,8 +505,8 @@ impl GoogleDriveRouter {
- add_sheet: Add a new sheet (tab) to a spreadsheet
- clear_values: Clear values from a range
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"spreadsheetId": {
@@ -553,15 +549,14 @@ impl GoogleDriveRouter {
}
},
"required": ["spreadsheetId", "operation"],
}),
Some(ToolAnnotations {
title: Some("Work with Google Sheets data using various operations.".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Work with Google Sheets data using various operations.".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let docs_tool = Tool::new(
"docs_tool".to_string(),
@@ -575,8 +570,8 @@ impl GoogleDriveRouter {
- create_paragraph: Create a new paragraph
- delete_content: Delete content between positions
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"documentId": {
@@ -610,15 +605,14 @@ impl GoogleDriveRouter {
}
},
"required": ["documentId", "operation"],
}),
Some(ToolAnnotations {
title: Some("Work with Google Docs data using various operations.".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Work with Google Docs data using various operations.".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let get_comments_tool = Tool::new(
"get_comments".to_string(),
@@ -626,7 +620,7 @@ impl GoogleDriveRouter {
List comments for a file in google drive.
"#}
.to_string(),
json!({
object!({
"type": "object",
"properties": {
"fileId": {
@@ -636,14 +630,14 @@ impl GoogleDriveRouter {
},
"required": ["fileId"],
}),
Some(ToolAnnotations {
title: Some("List file comments".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("List file comments".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let manage_comment_tool = Tool::new(
"manage_comment".to_string(),
@@ -654,8 +648,8 @@ impl GoogleDriveRouter {
- create: Create a comment for the latest revision of a Google Drive file. The Google Drive API only supports unanchored comments (they don't refer to a specific location in the file).
- reply: Add a reply to a comment thread, or resolve a comment.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"fileId": {
@@ -681,15 +675,14 @@ impl GoogleDriveRouter {
}
},
"required": ["fileId", "operation", "content"],
}),
Some(ToolAnnotations {
title: Some("Manage file comment".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Manage file comment".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let list_drives_tool = Tool::new(
"list_drives".to_string(),
@@ -697,7 +690,7 @@ impl GoogleDriveRouter {
List shared Google drives.
"#}
.to_string(),
json!({
object!({
"type": "object",
"properties": {
"name_contains": {
@@ -706,14 +699,14 @@ impl GoogleDriveRouter {
}
},
}),
Some(ToolAnnotations {
title: Some("List shared google drives".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("List shared google drives".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let get_permissions_tool = Tool::new(
"get_permissions".to_string(),
@@ -721,7 +714,7 @@ impl GoogleDriveRouter {
List sharing permissions for a file, folder, or shared drive.
"#}
.to_string(),
json!({
object!({
"type": "object",
"properties": {
"fileId": {
@@ -731,14 +724,14 @@ impl GoogleDriveRouter {
},
"required": ["fileId"],
}),
Some(ToolAnnotations {
title: Some("List sharing permissions".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("List sharing permissions".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let sharing_tool = Tool::new(
"sharing".to_string(),
@@ -750,8 +743,8 @@ impl GoogleDriveRouter {
- update: Update an existing permission to a different role. (You cannot change the type or to whom it is targeted).
- delete: Delete an existing permission.
"#}
.to_string(),
json!({
.to_string(),
object!({
"type": "object",
"properties": {
"fileId": {
@@ -787,15 +780,14 @@ impl GoogleDriveRouter {
},
},
"required": ["fileId", "operation"],
}),
Some(ToolAnnotations {
title: Some("Manage file sharing".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Manage file sharing".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let instructions = indoc::formatdoc! {r#"
Google Drive MCP Server Instructions
+40 -40
View File
@@ -4,13 +4,13 @@ use indoc::formatdoc;
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
tool::{Tool, ToolAnnotations, ToolCall},
tool::ToolCall,
};
use mcp_server::router::CapabilitiesBuilder;
use mcp_server::Router;
use rmcp::model::JsonRpcMessage;
use rmcp::model::{Content, Prompt, Resource};
use serde_json::{json, Value};
use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Tool, ToolAnnotations};
use rmcp::object;
use serde_json::Value;
use std::{
collections::HashMap,
fs,
@@ -41,7 +41,7 @@ impl MemoryRouter {
let remember_memory = Tool::new(
"remember_memory",
"Stores a memory with optional tags in a specified category",
json!({
object!({
"type": "object",
"properties": {
"category": {"type": "string"},
@@ -51,19 +51,19 @@ impl MemoryRouter {
},
"required": ["category", "data", "is_global"]
}),
Some(ToolAnnotations {
title: Some("Remember Memory".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: true,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Remember Memory".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(true),
open_world_hint: Some(false),
});
let retrieve_memories = Tool::new(
"retrieve_memories",
"Retrieves all memories from a specified category",
json!({
object!({
"type": "object",
"properties": {
"category": {"type": "string"},
@@ -71,19 +71,19 @@ impl MemoryRouter {
},
"required": ["category", "is_global"]
}),
Some(ToolAnnotations {
title: Some("Retrieve Memory".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Retrieve Memory".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let remove_memory_category = Tool::new(
"remove_memory_category",
"Removes all memories within a specified category",
json!({
object!({
"type": "object",
"properties": {
"category": {"type": "string"},
@@ -91,19 +91,19 @@ impl MemoryRouter {
},
"required": ["category", "is_global"]
}),
Some(ToolAnnotations {
title: Some("Remove Memory Category".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Remove Memory Category".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let remove_specific_memory = Tool::new(
"remove_specific_memory",
"Removes a specific memory within a specified category",
json!({
object!({
"type": "object",
"properties": {
"category": {"type": "string"},
@@ -112,14 +112,14 @@ impl MemoryRouter {
},
"required": ["category", "memory_content", "is_global"]
}),
Some(ToolAnnotations {
title: Some("Remove Specific Memory".to_string()),
read_only_hint: false,
destructive_hint: true,
idempotent_hint: false,
open_world_hint: false,
}),
);
)
.annotate(ToolAnnotations {
title: Some("Remove Specific Memory".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(true),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
let instructions = formatdoc! {r#"
This extension allows storage and retrieval of categorized information with tagging support. It's designed to help
+12 -13
View File
@@ -4,12 +4,12 @@ use indoc::formatdoc;
use mcp_core::{
handler::{PromptError, ResourceError, ToolError},
protocol::ServerCapabilities,
tool::{Tool, ToolAnnotations},
};
use mcp_server::router::CapabilitiesBuilder;
use mcp_server::Router;
use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Role};
use serde_json::{json, Value};
use rmcp::model::{Content, JsonRpcMessage, Prompt, Resource, Role, Tool, ToolAnnotations};
use rmcp::object;
use serde_json::Value;
use std::{future::Future, pin::Pin};
use tokio::sync::mpsc;
@@ -31,7 +31,7 @@ impl TutorialRouter {
let load_tutorial = Tool::new(
"load_tutorial".to_string(),
"Load a specific tutorial by name. The tutorial will be returned as markdown content that provides step by step instructions.".to_string(),
json!({
object!({
"type": "object",
"required": ["name"],
"properties": {
@@ -40,15 +40,14 @@ impl TutorialRouter {
"description": "Name of the tutorial to load, e.g. 'getting-started' or 'developer-mcp'"
}
}
}),
Some(ToolAnnotations {
title: Some("Load Tutorial".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
);
})
).annotate(ToolAnnotations {
title: Some("Load Tutorial".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
});
// Get base instructions and available tutorials
let available_tutorials = Self::get_available_tutorials();
+4 -1
View File
@@ -207,7 +207,10 @@ async fn get_tools(
ToolInfo::new(
&tool.name,
&tool.description,
tool.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default(),
get_parameter_names(&tool),
permission,
)
+2 -1
View File
@@ -8,6 +8,7 @@ use crate::state::AppState;
use axum::{extract::State, routing::post, Json, Router};
use goose::agents::{extension::Envs, ExtensionConfig};
use http::{HeaderMap, StatusCode};
use rmcp::model::Tool;
use serde::{Deserialize, Serialize};
use tracing;
@@ -80,7 +81,7 @@ enum ExtensionConfigRequest {
/// The name to identify this extension
name: String,
/// The tools provided by this extension
tools: Vec<mcp_core::tool::Tool>,
tools: Vec<Tool>,
/// Optional instructions for using the tools
instructions: Option<String>,
},
+1 -2
View File
@@ -404,7 +404,6 @@ mod tests {
errors::ProviderError,
},
};
use mcp_core::tool::Tool;
#[derive(Clone)]
struct MockProvider {
@@ -421,7 +420,7 @@ mod tests {
&self,
_system: &str,
_messages: &[Message],
_tools: &[Tool],
_tools: &[rmcp::model::Tool],
) -> anyhow::Result<(Message, ProviderUsage), ProviderError> {
Ok((
Message::assistant().with_text("Mock response"),
+5 -4
View File
@@ -5,8 +5,9 @@ use goose::{
message::Message,
providers::{bedrock::BedrockProvider, databricks::DatabricksProvider, openai::OpenAiProvider},
};
use mcp_core::tool::{Tool, ToolCall};
use rmcp::model::Content;
use mcp_core::tool::ToolCall;
use rmcp::model::{Content, Tool};
use rmcp::object;
use serde_json::json;
use std::fs;
@@ -42,7 +43,7 @@ async fn main() -> Result<()> {
];
// Get a response from the model about the image
let input_schema = json!({
let input_schema = object!({
"type": "object",
"required": ["path"],
"properties": {
@@ -57,7 +58,7 @@ async fn main() -> Result<()> {
.complete(
"You are a helpful assistant. Please describe any text you see in the image.",
&messages,
&[Tool::new("view_image", "View an image", input_schema, None)],
&[Tool::new("view_image", "View an image", input_schema)],
)
.await?;
+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;
+1 -1
View File
@@ -1,6 +1,6 @@
use std::sync::Arc;
use mcp_core::Tool;
use rmcp::model::Tool;
use crate::{
message::Message,
+1 -1
View File
@@ -220,9 +220,9 @@ mod tests {
use crate::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage};
use crate::providers::errors::ProviderError;
use chrono::Utc;
use mcp_core::tool::Tool;
use mcp_core::ToolCall;
use rmcp::model::Role;
use rmcp::model::Tool;
use rmcp::model::{AnnotateAble, Content, RawTextContent};
use serde_json::json;
use std::sync::Arc;
+18 -17
View File
@@ -5,10 +5,10 @@ use crate::message::{Message, MessageContent, ToolRequest};
use crate::providers::base::Provider;
use chrono::Utc;
use indoc::indoc;
use mcp_core::tool::Tool;
use mcp_core::tool::ToolAnnotations;
use rmcp::model::{Tool, ToolAnnotations};
use rmcp::object;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use serde_json::Value;
use std::collections::HashSet;
use std::sync::Arc;
@@ -45,7 +45,7 @@ fn create_read_only_tool() -> Tool {
Use this analysis to generate the list of tools performing read-only operations from the provided tool requests.
"#}
.to_string(),
json!({
object!({
"type": "object",
"properties": {
"read_only_tools": {
@@ -57,15 +57,14 @@ fn create_read_only_tool() -> Tool {
}
},
"required": []
}),
Some(ToolAnnotations {
title: Some("Check tool operation".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
)
})
).annotate(ToolAnnotations {
title: Some("Check tool operation".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
})
}
/// Builds the message to be sent to the LLM for detecting read-only operations.
@@ -266,9 +265,8 @@ mod tests {
use crate::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage};
use crate::providers::errors::ProviderError;
use chrono::Utc;
use mcp_core::ToolCall;
use mcp_core::{tool::Tool, ToolResult};
use rmcp::model::Role;
use mcp_core::{ToolCall, ToolResult};
use rmcp::model::{Role, Tool};
use serde_json::json;
use tempfile::NamedTempFile;
@@ -324,7 +322,10 @@ mod tests {
async fn test_create_read_only_tool() {
let tool = create_read_only_tool();
assert_eq!(tool.name, "platform__tool_by_tool_permission");
assert!(tool.description.contains("read-only operation"));
assert!(tool
.description
.as_ref()
.map_or(false, |desc| desc.contains("read-only operation")));
}
#[test]
+1 -1
View File
@@ -19,7 +19,7 @@ use super::formats::anthropic::{
use super::utils::{emit_debug_trace, get_model};
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const ANTHROPIC_DEFAULT_MODEL: &str = "claude-3-5-sonnet-latest";
pub const ANTHROPIC_KNOWN_MODELS: &[&str] = &[
+1 -1
View File
@@ -13,7 +13,7 @@ use super::formats::openai::{create_request, get_usage, response_to_message};
use super::utils::{emit_debug_trace, get_model, handle_response_openai_compat, ImageFormat};
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const AZURE_DEFAULT_MODEL: &str = "gpt-4o";
pub const AZURE_DOC_URL: &str =
+1 -1
View File
@@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize};
use super::errors::ProviderError;
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use utoipa::ToSchema;
use once_cell::sync::Lazy;
+1 -1
View File
@@ -6,7 +6,7 @@ use async_trait::async_trait;
use aws_sdk_bedrockruntime::config::ProvideCredentials;
use aws_sdk_bedrockruntime::operation::converse::ConverseError;
use aws_sdk_bedrockruntime::{types as bedrock, Client};
use mcp_core::Tool;
use rmcp::model::Tool;
use serde_json::Value;
use tokio::time::sleep;
+1 -1
View File
@@ -13,7 +13,7 @@ use super::utils::emit_debug_trace;
use crate::config::Config;
use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const CLAUDE_CODE_DEFAULT_MODEL: &str = "claude-3-5-sonnet-latest";
pub const CLAUDE_CODE_KNOWN_MODELS: &[&str] = &["sonnet", "opus", "claude-3-5-sonnet-latest"];
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::config::ConfigError;
use crate::message::Message;
use crate::model::ModelConfig;
use crate::providers::formats::openai::{get_usage, response_to_streaming_message};
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use serde_json::json;
use tokio::time::sleep;
use tokio_stream::StreamExt;
+1 -1
View File
@@ -27,7 +27,7 @@ use anyhow::Result;
#[cfg(test)]
use super::errors::ProviderError;
#[cfg(test)]
use mcp_core::tool::Tool;
use rmcp::model::Tool;
fn default_lead_turns() -> usize {
3
@@ -3,8 +3,8 @@ use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use anyhow::{anyhow, Result};
use mcp_core::tool::{Tool, ToolCall};
use rmcp::model::Role;
use mcp_core::tool::ToolCall;
use rmcp::model::{Role, Tool};
use serde_json::{json, Value};
use std::collections::HashSet;
@@ -676,6 +676,7 @@ where
#[cfg(test)]
mod tests {
use super::*;
use rmcp::object;
use serde_json::json;
#[test]
@@ -858,7 +859,7 @@ mod tests {
Tool::new(
"calculator",
"Calculate mathematical expressions",
json!({
object!({
"type": "object",
"properties": {
"expression": {
@@ -867,12 +868,11 @@ mod tests {
}
}
}),
None,
),
Tool::new(
"weather",
"Get weather information",
json!({
object!({
"type": "object",
"properties": {
"location": {
@@ -881,7 +881,6 @@ mod tests {
}
}
}),
None,
),
];
@@ -6,8 +6,8 @@ use aws_sdk_bedrockruntime::types as bedrock;
use aws_smithy_types::{Document, Number};
use base64::Engine;
use chrono::Utc;
use mcp_core::{Tool, ToolCall, ToolError, ToolResult};
use rmcp::model::{Content, RawContent, ResourceContents, Role};
use mcp_core::{ToolCall, ToolError, ToolResult};
use rmcp::model::{Content, RawContent, ResourceContents, Role, Tool};
use serde_json::Value;
use super::super::base::Usage;
@@ -184,9 +184,14 @@ pub fn to_bedrock_tool(tool: &Tool) -> Result<bedrock::Tool> {
Ok(bedrock::Tool::ToolSpec(
bedrock::ToolSpecification::builder()
.name(tool.name.to_string())
.description(tool.description.to_string())
.description(
tool.description
.as_ref()
.map(|d| d.to_string())
.unwrap_or_default(),
)
.input_schema(bedrock::ToolInputSchema::Json(to_bedrock_json(
&tool.input_schema,
&Value::Object(tool.input_schema.as_ref().clone()),
)))
.build()?,
))
@@ -5,10 +5,8 @@ use crate::providers::utils::{
sanitize_function_name, ImageFormat,
};
use anyhow::{anyhow, Error};
use mcp_core::ToolError;
use mcp_core::{Tool, ToolCall};
use rmcp::model::Role;
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents};
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
@@ -589,6 +587,7 @@ pub fn create_request(
#[cfg(test)]
mod tests {
use super::*;
use rmcp::object;
use serde_json::json;
#[test]
@@ -703,7 +702,7 @@ mod tests {
let tool = Tool::new(
"test_tool",
"A test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -713,7 +712,6 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let spec = format_tools(&[tool])?;
@@ -795,7 +793,7 @@ mod tests {
let tool1 = Tool::new(
"test_tool",
"Test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -805,13 +803,12 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let tool2 = Tool::new(
"test_tool",
"Test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -821,7 +818,6 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let result = format_tools(&[tool1, tool2]);
@@ -3,7 +3,7 @@ use crate::message::Message;
use crate::model::ModelConfig;
use crate::providers::base::Usage;
use anyhow::{Context, Result};
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use serde_json::Value;
use std::fmt;
+131 -127
View File
@@ -4,9 +4,10 @@ use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use crate::providers::utils::{is_valid_function_name, sanitize_function_name};
use anyhow::Result;
use mcp_core::tool::{Tool, ToolCall};
use mcp_core::tool::ToolCall;
use rand::{distributions::Alphanumeric, Rng};
use rmcp::model::{AnnotateAble, RawContent, Role};
use rmcp::model::{AnnotateAble, RawContent, Role, Tool};
use serde_json::{json, Map, Value};
use std::ops::Deref;
@@ -132,18 +133,17 @@ pub fn format_tools(tools: &[Tool]) -> Vec<Value> {
let mut parameters = Map::new();
parameters.insert("name".to_string(), json!(tool.name));
parameters.insert("description".to_string(), json!(tool.description));
if let Some(tool_input_schema) = tool.input_schema.as_object() {
// Only add the parameters key if the tool schema has non-empty properties.
if tool_input_schema
.get("properties")
.and_then(|v| v.as_object())
.is_some_and(|p| !p.is_empty())
{
parameters.insert(
"parameters".to_string(),
process_map(tool_input_schema, None),
);
}
let tool_input_schema = &tool.input_schema;
// Only add the parameters key if the tool schema has non-empty properties.
if tool_input_schema
.get("properties")
.and_then(|v| v.as_object())
.is_some_and(|p| !p.is_empty())
{
parameters.insert(
"parameters".to_string(),
process_map(tool_input_schema, None),
);
}
json!(parameters)
})
@@ -320,7 +320,7 @@ pub fn create_request(
}
let mut generation_config = Map::new();
if let Some(temp) = model_config.temperature {
generation_config.insert("temperature".to_string(), json!(temp));
generation_config.insert("temperature".to_string(), json!(temp as f64));
}
if let Some(tokens) = model_config.max_tokens {
generation_config.insert("maxOutputTokens".to_string(), json!(tokens));
@@ -329,13 +329,14 @@ pub fn create_request(
payload.insert("generationConfig".to_string(), json!(generation_config));
}
Ok(Value::Object(payload))
Ok(json!(payload))
}
#[cfg(test)]
mod tests {
use super::*;
use rmcp::model::Content;
use rmcp::object;
use serde_json::json;
fn set_up_text_message(text: &str, role: Role) -> Message {
@@ -374,17 +375,6 @@ mod tests {
)
}
fn set_up_tool(name: &str, description: &str, params: Value) -> Tool {
Tool {
name: name.to_string(),
description: description.to_string(),
input_schema: json!({
"properties": params
}),
annotations: None,
}
}
#[test]
fn test_get_usage() {
let data = json!({
@@ -420,8 +410,11 @@ mod tests {
"param1": "value1"
});
let messages = vec![
set_up_tool_request_message("id", ToolCall::new("tool_name", json!(arguments))),
set_up_tool_confirmation_message("id2", ToolCall::new("tool_name_2", json!(arguments))),
set_up_tool_request_message("id", ToolCall::new("tool_name", arguments.clone())),
set_up_tool_confirmation_message(
"id2",
ToolCall::new("tool_name_2", arguments.clone()),
),
];
let payload = format_messages(&messages);
assert_eq!(payload.len(), 1);
@@ -478,100 +471,106 @@ mod tests {
#[test]
fn test_tools_to_google_spec_with_valid_tools() {
let params1 = json!({
"param1": {
"type": "string",
"description": "A parameter",
"field_does_not_accept": ["value1", "value2"]
}
});
let params2 = json!({
"param2": {
"type": "string",
"description": "B parameter",
}
});
let params3 = json!({
"body": {
"description": "Review comment text",
"type": "string"
},
"comments": {
"description": "Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters.",
"type": "array",
"items": {
"additionalProperties": false,
"properties": {
"body": {
"description": "comment body",
"type": "string"
},
"line": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "line number in the file to comment on. For multi-line comments, the end of the line range"
},
"path": {
"description": "path to the file",
"type": "string"
},
"position": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "position of the comment in the diff"
},
"side": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
],
"description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)"
},
"start_line": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "The first line of the range to which the comment refers. Required for multi-line comments."
},
"start_side": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
],
"description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)"
}
},
"required": ["path", "body", "position", "line", "side", "start_line", "start_side"],
"type": "object"
let params1 = object!({
"properties": {
"param1": {
"type": "string",
"description": "A parameter",
"field_does_not_accept": ["value1", "value2"]
}
}
});
let params2 = object!({
"properties": {
"param2": {
"type": "string",
"description": "B parameter",
}
}
});
let params3 = object!({
"properties": {
"body": {
"description": "Review comment text",
"type": "string"
},
"comments": {
"description": "Line-specific comments array of objects to place comments on pull request changes. Requires path and body. For line comments use line or position. For multi-line comments use start_line and line with optional side parameters.",
"type": "array",
"items": {
"additionalProperties": false,
"properties": {
"body": {
"description": "comment body",
"type": "string"
},
"line": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "line number in the file to comment on. For multi-line comments, the end of the line range"
},
"path": {
"description": "path to the file",
"type": "string"
},
"position": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "position of the comment in the diff"
},
"side": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
],
"description": "The side of the diff on which the line resides. For multi-line comments, this is the side for the end of the line range. (LEFT or RIGHT)"
},
"start_line": {
"anyOf": [
{ "type": "number" },
{ "type": "null" }
],
"description": "The first line of the range to which the comment refers. Required for multi-line comments."
},
"start_side": {
"anyOf": [
{ "type": "string" },
{ "type": "null" }
],
"description": "The side of the diff on which the start line resides for multi-line comments. (LEFT or RIGHT)"
}
},
"required": ["path", "body", "position", "line", "side", "start_line", "start_side"],
"type": "object"
}
},
"commitId": {
"description": "SHA of commit to review",
"type": "string"
},
"event": {
"description": "Review action to perform",
"enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
}
},
"commitId": {
"description": "SHA of commit to review",
"type": "string"
},
"event": {
"description": "Review action to perform",
"enum": ["APPROVE", "REQUEST_CHANGES", "COMMENT"],
"type": "string"
},
"owner": {
"description": "Repository owner",
"type": "string"
},
"pullNumber": {
"description": "Pull request number",
"type": "number"
}
});
let tools = vec![
set_up_tool("tool1", "description1", params1),
set_up_tool("tool2", "description2", params2),
set_up_tool("tool3", "description3", params3),
Tool::new("tool1", "description1", params1),
Tool::new("tool2", "description2", params2),
Tool::new("tool3", "description3", params3),
];
let result = format_tools(&tools);
assert_eq!(result.len(), 3);
@@ -681,14 +680,19 @@ mod tests {
#[test]
fn test_tools_to_google_spec_with_empty_properties() {
let tools = vec![Tool {
name: "tool1".to_string(),
description: "description1".to_string(),
input_schema: json!({
"properties": {}
}),
annotations: None,
}];
use rmcp::model::object;
use std::borrow::Cow;
use std::sync::Arc;
let schema = json!({
"properties": {}
});
let tools = vec![Tool::new(
Cow::Borrowed("tool1"),
Cow::Borrowed("description1"),
Arc::new(object(schema)),
)];
let result = format_tools(&tools);
assert_eq!(result.len(), 1);
assert_eq!(result[0]["name"], "tool1");
+6 -10
View File
@@ -8,10 +8,8 @@ use crate::providers::utils::{
use anyhow::{anyhow, Error};
use async_stream::try_stream;
use futures::Stream;
use mcp_core::ToolError;
use mcp_core::{Tool, ToolCall};
use rmcp::model::Role;
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents};
use mcp_core::{ToolCall, ToolError};
use rmcp::model::{AnnotateAble, Content, RawContent, ResourceContents, Role, Tool};
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
use std::ops::Deref;
@@ -635,6 +633,7 @@ pub fn create_request(
#[cfg(test)]
mod tests {
use super::*;
use rmcp::object;
use serde_json::json;
use tokio::pin;
use tokio_stream::{self, StreamExt};
@@ -751,7 +750,7 @@ mod tests {
let tool = Tool::new(
"test_tool",
"A test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -761,7 +760,6 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let spec = format_tools(&[tool])?;
@@ -843,7 +841,7 @@ mod tests {
let tool1 = Tool::new(
"test_tool",
"Test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -853,13 +851,12 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let tool2 = Tool::new(
"test_tool",
"Test tool",
json!({
object!({
"type": "object",
"properties": {
"input": {
@@ -869,7 +866,6 @@ mod tests {
},
"required": ["input"]
}),
None,
);
let result = format_tools(&[tool1, tool2]);
@@ -3,8 +3,8 @@ use crate::model::ModelConfig;
use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use anyhow::{anyhow, Result};
use mcp_core::tool::{Tool, ToolCall};
use rmcp::model::Role;
use mcp_core::tool::ToolCall;
use rmcp::model::{Role, Tool};
use serde_json::{json, Value};
use std::collections::HashSet;
@@ -359,6 +359,7 @@ pub fn create_request(
#[cfg(test)]
mod tests {
use super::*;
use rmcp::object;
use serde_json::json;
#[test]
@@ -460,7 +461,7 @@ mod tests {
Tool::new(
"calculator",
"Calculate mathematical expressions",
json!({
object!({
"type": "object",
"properties": {
"expression": {
@@ -469,12 +470,11 @@ mod tests {
}
}
}),
None,
),
Tool::new(
"weather",
"Get weather information",
json!({
object!({
"type": "object",
"properties": {
"location": {
@@ -483,7 +483,6 @@ mod tests {
}
}
}),
None,
),
];
@@ -557,7 +556,7 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet","
let tools = vec![Tool::new(
"get_stock_price",
"Get stock price information",
json!({
object!({
"type": "object",
"properties": {
"symbol": {
@@ -567,7 +566,6 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet","
},
"required": ["symbol"]
}),
None,
)];
let request = create_request(&model_config, system, &messages, &tools)?;
@@ -664,8 +662,7 @@ data: {"id":"a9537c2c-2017-4906-9817-2456168d89fa","model":"claude-3-5-sonnet","
let tools = vec![Tool::new(
"test_tool",
"Test tool",
json!({"type": "object", "properties": {}}),
None,
object!({"type": "object", "properties": {}}),
)];
let request = create_request(&model_config, system, &messages, &tools)?;
+1 -1
View File
@@ -21,7 +21,7 @@ use crate::providers::formats::gcpvertexai::{
use crate::providers::formats::gcpvertexai::GcpLocation::Iowa;
use crate::providers::gcpauth::GcpAuth;
use crate::providers::utils::emit_debug_trace;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
/// Base URL for GCP Vertex AI documentation
const GCP_VERTEX_AI_DOC_URL: &str = "https://cloud.google.com/vertex-ai";
+1 -1
View File
@@ -11,8 +11,8 @@ use super::errors::ProviderError;
use super::utils::emit_debug_trace;
use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Role;
use rmcp::model::Tool;
pub const GEMINI_CLI_DEFAULT_MODEL: &str = "gemini-2.5-pro";
pub const GEMINI_CLI_KNOWN_MODELS: &[&str] = &["gemini-2.5-pro"];
+1 -1
View File
@@ -20,7 +20,7 @@ use crate::config::{Config, ConfigError};
use crate::message::Message;
use crate::model::ModelConfig;
use crate::providers::base::ConfigKey;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const GITHUB_COPILOT_DEFAULT_MODEL: &str = "gpt-4o";
pub const GITHUB_COPILOT_KNOWN_MODELS: &[&str] = &[
+1 -1
View File
@@ -9,8 +9,8 @@ use crate::providers::utils::{
use anyhow::Result;
use async_trait::async_trait;
use axum::http::HeaderMap;
use mcp_core::tool::Tool;
use reqwest::Client;
use rmcp::model::Tool;
use serde_json::Value;
use std::time::Duration;
use url::Url;
+1 -1
View File
@@ -6,8 +6,8 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m
use crate::providers::utils::get_model;
use anyhow::Result;
use async_trait::async_trait;
use mcp_core::Tool;
use reqwest::{Client, StatusCode};
use rmcp::model::Tool;
use serde_json::Value;
use std::time::Duration;
use url::Url;
+1 -1
View File
@@ -8,7 +8,7 @@ use super::base::{LeadWorkerProviderTrait, Provider, ProviderMetadata, ProviderU
use super::errors::ProviderError;
use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use rmcp::model::{Content, RawContent};
/// A provider that switches between a lead model and a worker model based on turn count
+1 -1
View File
@@ -12,7 +12,7 @@ use super::errors::ProviderError;
use super::utils::{emit_debug_trace, get_model, handle_response_openai_compat, ImageFormat};
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const LITELLM_DEFAULT_MODEL: &str = "gpt-4o-mini";
pub const LITELLM_DOC_URL: &str = "https://docs.litellm.ai/docs/";
+1 -1
View File
@@ -6,8 +6,8 @@ use crate::model::ModelConfig;
use crate::providers::formats::openai::{create_request, get_usage, response_to_message};
use anyhow::Result;
use async_trait::async_trait;
use mcp_core::tool::Tool;
use reqwest::Client;
use rmcp::model::Tool;
use serde_json::Value;
use std::time::Duration;
use url::Url;
+1 -1
View File
@@ -22,7 +22,7 @@ use crate::model::ModelConfig;
use crate::providers::base::MessageStream;
use crate::providers::formats::openai::response_to_streaming_message;
use crate::providers::utils::handle_status_openai_compat;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
pub const OPEN_AI_DEFAULT_MODEL: &str = "gpt-4o";
pub const OPEN_AI_KNOWN_MODELS: &[&str] = &[
+1 -1
View File
@@ -13,7 +13,7 @@ use super::utils::{
use crate::message::Message;
use crate::model::ModelConfig;
use crate::providers::formats::openai::{create_request, get_usage, response_to_message};
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use url::Url;
pub const OPENROUTER_DEFAULT_MODEL: &str = "anthropic/claude-3.5-sonnet";
+1 -1
View File
@@ -6,7 +6,7 @@ use async_trait::async_trait;
use aws_config;
use aws_sdk_bedrockruntime::config::ProvideCredentials;
use aws_sdk_sagemakerruntime::Client as SageMakerClient;
use mcp_core::Tool;
use rmcp::model::Tool;
use serde_json::{json, Value};
use tokio::time::sleep;
+1 -1
View File
@@ -12,7 +12,7 @@ use super::utils::{get_model, ImageFormat};
use crate::config::ConfigError;
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use url::Url;
pub const SNOWFLAKE_DEFAULT_MODEL: &str = "claude-3-7-sonnet";
+1 -1
View File
@@ -11,7 +11,7 @@ use super::base::{Provider, ProviderMetadata, ProviderUsage};
use super::errors::ProviderError;
use crate::message::Message;
use crate::model::ModelConfig;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestInput {
+3 -3
View File
@@ -37,9 +37,9 @@ use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use crate::providers::formats::openai::create_request;
use anyhow::Result;
use mcp_core::tool::{Tool, ToolCall};
use mcp_core::tool::ToolCall;
use reqwest::Client;
use rmcp::model::RawContent;
use rmcp::model::{RawContent, Tool};
use serde_json::{json, Value};
use std::ops::Deref;
use std::time::Duration;
@@ -297,7 +297,7 @@ pub fn format_tool_info(tools: &[Tool]) -> String {
let mut tool_info = String::new();
for tool in tools {
tool_info.push_str(&format!(
"Tool Name: {}\nSchema: {}\nDescription: {}\n\n",
"Tool Name: {}\nSchema: {}\nDescription: {:?}\n\n",
tool.name,
serde_json::to_string_pretty(&tool.input_schema).unwrap_or_default(),
tool.description
+2 -2
View File
@@ -10,8 +10,8 @@ use super::base::{ConfigKey, Provider, ProviderMetadata, ProviderUsage, Usage};
use super::errors::ProviderError;
use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::{tool::Tool, ToolCall, ToolResult};
use rmcp::model::Role;
use mcp_core::{ToolCall, ToolResult};
use rmcp::model::{Role, Tool};
// ---------- Capability Flags ----------
#[derive(Debug)]
+1 -1
View File
@@ -6,8 +6,8 @@ use crate::providers::formats::openai::{create_request, get_usage, response_to_m
use crate::providers::utils::get_model;
use anyhow::Result;
use async_trait::async_trait;
use mcp_core::Tool;
use reqwest::{Client, StatusCode};
use rmcp::model::Tool;
use serde_json::Value;
use std::time::Duration;
use url::Url;
+1 -1
View File
@@ -1332,7 +1332,7 @@ mod tests {
providers::base::{ProviderMetadata, ProviderUsage, Usage},
providers::errors::ProviderError,
};
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use rmcp::model::{AnnotateAble, RawTextContent, Role};
// Removed: use crate::session::storage::{get_most_recent_session, read_metadata};
// `read_metadata` is still used by the test itself, so keep it or its module.
-1
View File
@@ -100,7 +100,6 @@ pub fn get_valid_sorted_sessions(sort_order: SortOrder) -> Result<Vec<SessionInf
#[cfg(test)]
mod tests {
use super::*;
use crate::session::SessionMetadata;
use std::fs;
use tempfile::tempdir;
+25 -18
View File
@@ -1,6 +1,6 @@
use ahash::AHasher;
use dashmap::DashMap;
use mcp_core::Tool;
use rmcp::model::Tool;
use std::hash::{Hash, Hasher};
use std::sync::Arc;
use tiktoken_rs::CoreBPE;
@@ -79,7 +79,12 @@ impl AsyncTokenCounter {
for tool in tools {
func_token_count += func_init;
let name = &tool.name;
let description = &tool.description.trim_end_matches('.');
let description = &tool
.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default()
.trim_end_matches('.');
// Note: the separator (:) is likely tokenized with adjacent tokens, so we use original approach for accuracy
let line = format!("{}:{}", name, description);
@@ -225,7 +230,12 @@ impl TokenCounter {
for tool in tools {
func_token_count += func_init; // Add tokens for start of each function
let name = &tool.name;
let description = &tool.description.trim_end_matches('.');
let description = &tool
.description
.as_ref()
.map(|d| d.as_ref())
.unwrap_or_default()
.trim_end_matches('.');
let line = format!("{}:{}", name, description);
func_token_count += self.count_tokens(&line); // Add tokens for name and description
@@ -371,9 +381,8 @@ pub async fn create_async_token_counter() -> Result<AsyncTokenCounter, String> {
mod tests {
use super::*;
use crate::message::{Message, MessageContent};
use mcp_core::tool::Tool;
use rmcp::model::Role;
use serde_json::json;
use rmcp::model::{Role, Tool};
use rmcp::object;
#[test]
fn test_token_counter_basic() {
@@ -428,10 +437,10 @@ mod tests {
),
];
let tools = vec![Tool {
name: "get_current_weather".to_string(),
description: "Get the current weather in a given location".to_string(),
input_schema: json!({
let tools = vec![Tool::new(
"get_current_weather",
"Get the current weather in a given location",
object!({
"properties": {
"location": {
"type": "string",
@@ -445,8 +454,7 @@ mod tests {
},
"required": ["location"]
}),
annotations: None,
}];
)];
let token_count_without_tools = counter.count_chat_tokens(system_prompt, &messages, &[]);
println!("Total tokens without tools: {}", token_count_without_tools);
@@ -526,10 +534,10 @@ mod tests {
),
];
let tools = vec![Tool {
name: "get_current_weather".to_string(),
description: "Get the current weather in a given location".to_string(),
input_schema: json!({
let tools = vec![Tool::new(
"get_current_weather",
"Get the current weather in a given location",
object!({
"properties": {
"location": {
"type": "string",
@@ -543,8 +551,7 @@ mod tests {
},
"required": ["location"]
}),
annotations: None,
}];
)];
let token_count_without_tools = counter.count_chat_tokens(system_prompt, &messages, &[]);
println!(
+9 -4
View File
@@ -448,6 +448,8 @@ mod schedule_tool_tests {
let tool = schedule_tool.unwrap();
assert!(tool
.description
.clone()
.unwrap_or_default()
.contains("Manage scheduled recipe execution"));
}
@@ -478,6 +480,8 @@ mod schedule_tool_tests {
let tool = schedule_tool.unwrap();
assert!(tool
.description
.clone()
.unwrap_or_default()
.contains("Manage scheduled recipe execution"));
// Verify the tool has the expected actions in its schema
@@ -548,7 +552,7 @@ mod final_output_tool_tests {
use goose::model::ModelConfig;
use goose::providers::base::{Provider, ProviderUsage, Usage};
use goose::providers::errors::ProviderError;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
#[derive(Clone)]
struct MockProvider {
@@ -648,7 +652,7 @@ mod final_output_tool_tests {
use goose::model::ModelConfig;
use goose::providers::base::{Provider, ProviderUsage};
use goose::providers::errors::ProviderError;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
#[derive(Clone)]
struct MockProvider {
@@ -769,7 +773,7 @@ mod retry_tests {
use goose::model::ModelConfig;
use goose::providers::base::{Provider, ProviderUsage, Usage};
use goose::providers::errors::ProviderError;
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use std::sync::atomic::{AtomicUsize, Ordering};
use std::sync::Arc;
@@ -950,7 +954,8 @@ mod max_turns_tests {
use goose::providers::base::{Provider, ProviderMetadata, ProviderUsage, Usage};
use goose::providers::errors::ProviderError;
use goose::session::storage::Identifier;
use mcp_core::tool::{Tool, ToolCall};
use mcp_core::tool::ToolCall;
use rmcp::model::Tool;
use std::path::PathBuf;
struct MockToolProvider {}
+4 -5
View File
@@ -7,8 +7,9 @@ use goose::providers::{
anthropic, azure, bedrock, databricks, google, groq, litellm, ollama, openai, openrouter,
snowflake, xai,
};
use mcp_core::tool::Tool;
use rmcp::model::Tool;
use rmcp::model::{AnnotateAble, Content, RawImageContent};
use rmcp::object;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::Mutex;
@@ -118,7 +119,7 @@ impl ProviderTester {
let weather_tool = Tool::new(
"get_weather",
"Get the weather for a location",
serde_json::json!({
object!({
"type": "object",
"required": ["location"],
"properties": {
@@ -128,7 +129,6 @@ impl ProviderTester {
}
}
}),
None,
);
let message = Message::user().with_text("What's the weather like in San Francisco?");
@@ -309,11 +309,10 @@ impl ProviderTester {
let screenshot_tool = Tool::new(
"get_screenshot",
"Get a screenshot of the current screen",
serde_json::json!({
object!({
"type": "object",
"properties": {}
}),
None,
);
let user_message = Message::user().with_text("Take a screenshot please");
+1 -1
View File
@@ -1,5 +1,5 @@
/// The protocol messages exchanged between client and server
use crate::tool::Tool;
use rmcp::model::Tool;
use rmcp::model::{Content, ErrorData, Prompt, PromptMessage, Resource, ResourceContents};
use serde::{Deserialize, Serialize};
use serde_json::Value;
+32 -30
View File
@@ -1,10 +1,12 @@
use anyhow::Result;
use mcp_core::handler::{PromptError, ResourceError};
use mcp_core::tool::ToolAnnotations;
use mcp_core::{handler::ToolError, protocol::ServerCapabilities, tool::Tool};
use mcp_core::{handler::ToolError, protocol::ServerCapabilities};
use mcp_server::router::{CapabilitiesBuilder, RouterService};
use mcp_server::{ByteTransport, Router, Server};
use rmcp::model::{Content, JsonRpcMessage, Prompt, PromptArgument, RawResource, Resource};
use rmcp::model::{
Content, JsonRpcMessage, Prompt, PromptArgument, RawResource, Resource, Tool, ToolAnnotations,
};
use rmcp::object;
use serde_json::Value;
use std::{future::Future, pin::Pin, sync::Arc};
use tokio::sync::mpsc;
@@ -72,51 +74,51 @@ impl Router for CounterRouter {
Tool::new(
"increment".to_string(),
"Increment the counter by 1".to_string(),
serde_json::json!({
object!({
"type": "object",
"properties": {},
"required": []
}),
Some(ToolAnnotations {
title: Some("Increment Tool".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
),
)
.annotate(ToolAnnotations {
title: Some("Increment Tool".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
}),
Tool::new(
"decrement".to_string(),
"Decrement the counter by 1".to_string(),
serde_json::json!({
object!({
"type": "object",
"properties": {},
"required": []
}),
Some(ToolAnnotations {
title: Some("Decrement Tool".to_string()),
read_only_hint: false,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
),
)
.annotate(ToolAnnotations {
title: Some("Decrement Tool".to_string()),
read_only_hint: Some(false),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
}),
Tool::new(
"get_value".to_string(),
"Get the current counter value".to_string(),
serde_json::json!({
object!({
"type": "object",
"properties": {},
"required": []
}),
Some(ToolAnnotations {
title: Some("Get Value Tool".to_string()),
read_only_hint: true,
destructive_hint: false,
idempotent_hint: false,
open_world_hint: false,
}),
),
)
.annotate(ToolAnnotations {
title: Some("Get Value Tool".to_string()),
read_only_hint: Some(true),
destructive_hint: Some(false),
idempotent_hint: Some(false),
open_world_hint: Some(false),
}),
]
}
+1 -1
View File
@@ -86,7 +86,7 @@ pub trait Router: Send + Sync + 'static {
// in the protocol, instructions are optional but we make it required
fn instructions(&self) -> String;
fn capabilities(&self) -> ServerCapabilities;
fn list_tools(&self) -> Vec<mcp_core::tool::Tool>;
fn list_tools(&self) -> Vec<rmcp::model::Tool>;
fn call_tool(
&self,
tool_name: &str,