feat: switch from mcp_core::Role to rmcp::model::Role (#3488)

This commit is contained in:
Alex Hancock
2025-07-18 12:30:23 -04:00
committed by GitHub
parent af872b1794
commit 76da3a9724
42 changed files with 105 additions and 69 deletions
Generated
+1
View File
@@ -5416,6 +5416,7 @@ dependencies = [
"async-trait",
"base64 0.21.7",
"chrono",
"rmcp",
"schemars",
"serde",
"serde_json",
@@ -9,7 +9,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -9,7 +9,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -9,7 +9,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -7,7 +7,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -7,7 +7,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -8,7 +8,7 @@ use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::content::Content;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -9,7 +9,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
#[derive(Debug)]
@@ -7,7 +7,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
use std::fs;
@@ -7,7 +7,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
use std::fs;
@@ -7,7 +7,7 @@ use crate::eval_suites::{
use crate::register_evaluation;
use async_trait::async_trait;
use goose::message::MessageContent;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::{self, Value};
pub struct SquirrelCensus {}
+3 -3
View File
@@ -241,7 +241,7 @@ fn export_session_to_markdown(
for message in &messages {
// Check if this is a User message containing only ToolResponses
let is_only_tool_response = message.role == mcp_core::role::Role::User
let is_only_tool_response = message.role == rmcp::model::Role::User
&& message
.content
.iter()
@@ -263,8 +263,8 @@ fn export_session_to_markdown(
// Output the role prefix except for tool response-only messages
if !is_only_tool_response {
let role_prefix = match message.role {
mcp_core::role::Role::User => "### User:\n",
mcp_core::role::Role::Assistant => "### Assistant:\n",
rmcp::model::Role::User => "### User:\n",
rmcp::model::Role::Assistant => "### Assistant:\n",
};
markdown_output.push_str(role_prefix);
}
+1 -1
View File
@@ -2,7 +2,7 @@ use goose::message::{Message, MessageContent, ToolRequest, ToolResponse};
use goose::utils::safe_truncate;
use mcp_core::content::Content as McpContent;
use mcp_core::resource::ResourceContents;
use mcp_core::role::Role;
use rmcp::model::Role;
use serde_json::Value;
const MAX_STRING_LENGTH_MD_EXPORT: usize = 4096; // Generous limit for export
+5 -5
View File
@@ -1187,7 +1187,7 @@ impl Session {
let tool_requests = self
.messages
.last()
.filter(|msg| msg.role == mcp_core::role::Role::Assistant)
.filter(|msg| msg.role == rmcp::model::Role::Assistant)
.map_or(Vec::new(), |msg| {
msg.content
.iter()
@@ -1259,7 +1259,7 @@ impl Session {
} else {
// An interruption occurred outside of a tool request-response.
if let Some(last_msg) = self.messages.last() {
if last_msg.role == mcp_core::role::Role::User {
if last_msg.role == rmcp::model::Role::User {
match last_msg.content.first() {
Some(MessageContent::ToolResponse(_)) => {
// Interruption occurred after a tool had completed but not assistant reply
@@ -1464,9 +1464,9 @@ impl Session {
let msg = Message::from(prompt_message);
// ensure we get a User - Assistant - User type pattern
let expected_role = if i % 2 == 0 {
mcp_core::Role::User
rmcp::model::Role::User
} else {
mcp_core::Role::Assistant
rmcp::model::Role::Assistant
};
if msg.role != expected_role {
@@ -1480,7 +1480,7 @@ impl Session {
break;
}
if msg.role == mcp_core::Role::User {
if msg.role == rmcp::model::Role::User {
output::render_message(&msg, self.debug);
}
self.push_message(msg);
+1 -1
View File
@@ -232,7 +232,7 @@ fn render_tool_response(resp: &ToolResponse, theme: Theme, debug: bool) {
Ok(contents) => {
for content in contents {
if let Some(audience) = content.audience() {
if !audience.contains(&mcp_core::role::Role::User) {
if !audience.contains(&rmcp::model::Role::User) {
continue;
}
}
+1 -1
View File
@@ -36,7 +36,7 @@ use mcp_core::{
use mcp_server::router::CapabilitiesBuilder;
use mcp_server::Router;
use mcp_core::role::Role;
use rmcp::model::Role;
use self::editor_models::{create_editor_model, EditorModel};
use self::shell::{expand_path, get_shell_config, is_absolute_path, normalize_line_endings};
+29 -2
View File
@@ -14,9 +14,36 @@ use goose::session::SessionMetadata;
use mcp_core::content::{Annotations, Content, EmbeddedResource, ImageContent, TextContent};
use mcp_core::handler::ToolResultSchema;
use mcp_core::resource::ResourceContents;
use mcp_core::role::Role;
use mcp_core::tool::{Tool, ToolAnnotations};
use utoipa::OpenApi;
use utoipa::{OpenApi, ToSchema};
/// A wrapper around rmcp::model::Role that implements ToSchema for utoipa
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize, ToSchema)]
#[serde(rename_all = "camelCase")]
pub enum Role {
/// A human user or client making a request
User,
/// An AI assistant or server providing a response
Assistant,
}
impl From<rmcp::model::Role> for Role {
fn from(role: rmcp::model::Role) -> Self {
match role {
rmcp::model::Role::User => Role::User,
rmcp::model::Role::Assistant => Role::Assistant,
}
}
}
impl From<Role> for rmcp::model::Role {
fn from(role: Role) -> Self {
match role {
Role::User => rmcp::model::Role::User,
Role::Assistant => rmcp::model::Role::Assistant,
}
}
}
#[allow(dead_code)] // Used by utoipa for OpenAPI generation
#[derive(OpenApi)]
+3 -2
View File
@@ -3,7 +3,7 @@ use crate::message::{Message, MessageContent};
use crate::providers::base::Provider;
use crate::token_counter::{AsyncTokenCounter, TokenCounter};
use anyhow::Result;
use mcp_core::Role;
use rmcp::model::Role;
use std::sync::Arc;
// Constants for the summarization prompt and a follow-up user message.
@@ -220,8 +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, Role};
use mcp_core::tool::Tool;
use mcp_core::{Content, TextContent, ToolCall};
use rmcp::model::Role;
use serde_json::json;
use std::sync::Arc;
+2 -1
View File
@@ -1,7 +1,8 @@
use crate::message::{Message, MessageContent};
use crate::utils::safe_truncate;
use anyhow::{anyhow, Result};
use mcp_core::{Content, ResourceContents, Role};
use mcp_core::{Content, ResourceContents};
use rmcp::model::Role;
use std::collections::HashSet;
use tracing::{debug, warn};
+1 -1
View File
@@ -12,8 +12,8 @@ use mcp_core::content::{Content, ImageContent, TextContent};
use mcp_core::handler::ToolResult;
use mcp_core::prompt::{PromptMessage, PromptMessageContent, PromptMessageRole};
use mcp_core::resource::ResourceContents;
use mcp_core::role::Role;
use mcp_core::tool::ToolCall;
use rmcp::model::Role;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use utoipa::ToSchema;
@@ -82,7 +82,7 @@ fn create_check_messages(tool_requests: Vec<&ToolRequest>) -> Vec<Message> {
.collect();
let mut check_messages = vec![];
check_messages.push(Message::new(
mcp_core::Role::User,
rmcp::model::Role::User,
Utc::now().timestamp(),
vec![MessageContent::Text(TextContent {
text: format!(
@@ -270,7 +270,8 @@ mod tests {
use crate::providers::errors::ProviderError;
use chrono::Utc;
use mcp_core::ToolCall;
use mcp_core::{tool::Tool, Role, ToolResult};
use mcp_core::{tool::Tool, ToolResult};
use rmcp::model::Role;
use serde_json::json;
use tempfile::NamedTempFile;
+3 -3
View File
@@ -12,7 +12,7 @@ use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::content::TextContent;
use mcp_core::tool::Tool;
use mcp_core::Role;
use rmcp::model::Role;
pub const CLAUDE_CODE_DEFAULT_MODEL: &str = "default";
pub const CLAUDE_CODE_KNOWN_MODELS: &[&str] = &["default"];
@@ -330,7 +330,7 @@ impl ClaudeCodeProvider {
// Extract the first user message text
let description = messages
.iter()
.find(|m| m.role == mcp_core::Role::User)
.find(|m| m.role == rmcp::model::Role::User)
.and_then(|m| {
m.content.iter().find_map(|c| match c {
MessageContent::Text(text_content) => Some(&text_content.text),
@@ -354,7 +354,7 @@ impl ClaudeCodeProvider {
}
let message = Message::new(
mcp_core::Role::Assistant,
rmcp::model::Role::Assistant,
chrono::Utc::now().timestamp(),
vec![MessageContent::Text(mcp_core::content::TextContent {
text: description.clone(),
+2 -1
View File
@@ -177,7 +177,8 @@ mod tests {
use crate::message::{Message, MessageContent};
use crate::providers::base::{ProviderMetadata, ProviderUsage, Usage};
use chrono::Utc;
use mcp_core::{content::TextContent, Role};
use mcp_core::content::TextContent;
use rmcp::model::Role;
use std::env;
#[allow(dead_code)]
@@ -4,8 +4,8 @@ use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use anyhow::{anyhow, Result};
use mcp_core::content::Content;
use mcp_core::role::Role;
use mcp_core::tool::{Tool, ToolCall};
use rmcp::model::Role;
use serde_json::{json, Value};
use std::collections::HashSet;
@@ -538,7 +538,7 @@ where
// Yield partial text message
let message = Message::new(
mcp_core::role::Role::Assistant,
Role::Assistant,
chrono::Utc::now().timestamp(),
vec![MessageContent::text(text)],
);
@@ -573,7 +573,7 @@ where
format!("Could not parse tool arguments: {}", args)
);
let message = Message::new(
mcp_core::role::Role::Assistant,
Role::Assistant,
chrono::Utc::now().timestamp(),
vec![MessageContent::tool_request(tool_id, Err(error))],
);
@@ -585,7 +585,7 @@ where
let tool_call = ToolCall::new(&name, parsed_args);
let message = Message::new(
mcp_core::role::Role::Assistant,
rmcp::model::Role::Assistant,
chrono::Utc::now().timestamp(),
vec![MessageContent::tool_request(tool_id, Ok(tool_call))],
);
@@ -6,7 +6,8 @@ use aws_sdk_bedrockruntime::types as bedrock;
use aws_smithy_types::{Document, Number};
use base64::Engine;
use chrono::Utc;
use mcp_core::{Content, ResourceContents, Role, Tool, ToolCall, ToolError, ToolResult};
use mcp_core::{Content, ResourceContents, Tool, ToolCall, ToolError, ToolResult};
use rmcp::model::Role;
use serde_json::Value;
use super::super::base::Usage;
@@ -6,7 +6,8 @@ use crate::providers::utils::{
};
use anyhow::{anyhow, Error};
use mcp_core::ToolError;
use mcp_core::{Content, Role, Tool, ToolCall};
use mcp_core::{Content, Tool, ToolCall};
use rmcp::model::Role;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
+1 -1
View File
@@ -5,9 +5,9 @@ use crate::providers::errors::ProviderError;
use crate::providers::utils::{is_valid_function_name, sanitize_function_name};
use anyhow::Result;
use mcp_core::content::Content;
use mcp_core::role::Role;
use mcp_core::tool::{Tool, ToolCall};
use rand::{distributions::Alphanumeric, Rng};
use rmcp::model::Role;
use serde_json::{json, Map, Value};
/// Convert internal Message format to Google's API message specification
+2 -1
View File
@@ -9,7 +9,8 @@ use anyhow::{anyhow, Error};
use async_stream::try_stream;
use futures::Stream;
use mcp_core::ToolError;
use mcp_core::{Content, Role, Tool, ToolCall};
use mcp_core::{Content, Tool, ToolCall};
use rmcp::model::Role;
use serde::{Deserialize, Serialize};
use serde_json::{json, Value};
@@ -4,8 +4,8 @@ use crate::providers::base::Usage;
use crate::providers::errors::ProviderError;
use anyhow::{anyhow, Result};
use mcp_core::content::Content;
use mcp_core::role::Role;
use mcp_core::tool::{Tool, ToolCall};
use rmcp::model::Role;
use serde_json::{json, Value};
use std::collections::HashSet;
+1 -1
View File
@@ -12,7 +12,7 @@ use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use mcp_core::content::TextContent;
use mcp_core::tool::Tool;
use mcp_core::Role;
use rmcp::model::Role;
pub const GEMINI_CLI_DEFAULT_MODEL: &str = "default";
pub const GEMINI_CLI_KNOWN_MODELS: &[&str] = &["default"];
+2 -1
View File
@@ -455,7 +455,8 @@ mod tests {
use crate::message::MessageContent;
use crate::providers::base::{ProviderMetadata, ProviderUsage, Usage};
use chrono::Utc;
use mcp_core::{content::TextContent, Role};
use mcp_core::content::TextContent;
use rmcp::model::Role;
#[derive(Clone)]
struct MockProvider {
+1 -1
View File
@@ -17,7 +17,7 @@ use crate::message::{Message, MessageContent};
use crate::model::ModelConfig;
use chrono::Utc;
use mcp_core::content::TextContent;
use mcp_core::role::Role;
use rmcp::model::Role;
pub const SAGEMAKER_TGI_DOC_LINK: &str =
"https://docs.aws.amazon.com/sagemaker/latest/dg/realtime-endpoints.html";
+2 -1
View File
@@ -10,7 +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, Role, ToolCall, ToolResult};
use mcp_core::{tool::Tool, ToolCall, ToolResult};
use rmcp::model::Role;
// ---------- Capability Flags ----------
#[derive(Debug)]
+3 -2
View File
@@ -1219,7 +1219,7 @@ async fn run_scheduled_job_internal(
match message_result {
Ok(AgentEvent::Message(msg)) => {
if msg.role == mcp_core::role::Role::Assistant {
if msg.role == rmcp::model::Role::Assistant {
tracing::info!("[Job {}] Assistant: {:?}", job.id, msg.content);
}
all_session_messages.push(msg);
@@ -1331,7 +1331,8 @@ mod tests {
providers::base::{ProviderMetadata, ProviderUsage, Usage},
providers::errors::ProviderError,
};
use mcp_core::{content::TextContent, tool::Tool, Role};
use mcp_core::{content::TextContent, tool::Tool};
use rmcp::model::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.
use crate::session::storage::read_metadata;
+7 -7
View File
@@ -862,11 +862,11 @@ fn try_extract_partial_message(json_str: &str) -> Result<Message> {
// Try to extract role
let role = if json_str.contains("\"role\":\"user\"") {
mcp_core::role::Role::User
rmcp::model::Role::User
} else if json_str.contains("\"role\":\"assistant\"") {
mcp_core::role::Role::Assistant
rmcp::model::Role::Assistant
} else {
mcp_core::role::Role::User // Default fallback
rmcp::model::Role::User // Default fallback
};
// Try to extract text content
@@ -901,8 +901,8 @@ fn try_extract_partial_message(json_str: &str) -> Result<Message> {
if !extracted_text.is_empty() {
let message = match role {
mcp_core::role::Role::User => Message::user(),
mcp_core::role::Role::Assistant => Message::assistant(),
rmcp::model::Role::User => Message::user(),
rmcp::model::Role::Assistant => Message::assistant(),
};
return Ok(message.with_text(format!("[PARTIALLY RECOVERED] {}", extracted_text)));
@@ -1079,7 +1079,7 @@ pub async fn persist_messages_with_schedule_id(
// Count user messages
let user_message_count = messages
.iter()
.filter(|m| m.role == mcp_core::role::Role::User && !m.as_concat_text().trim().is_empty())
.filter(|m| m.role == rmcp::model::Role::User && !m.as_concat_text().trim().is_empty())
.count();
// Check if we need to update the description (after 1st or 3rd user message)
@@ -1293,7 +1293,7 @@ pub async fn generate_description_with_schedule_id(
// get context from messages so far, limiting each message to 300 chars for security
let context: Vec<String> = messages
.iter()
.filter(|m| m.role == mcp_core::role::Role::User)
.filter(|m| m.role == rmcp::model::Role::User)
.take(3) // Use up to first 3 user messages for context
.map(|m| {
let text = m.as_concat_text();
+1 -1
View File
@@ -371,8 +371,8 @@ pub async fn create_async_token_counter() -> Result<AsyncTokenCounter, String> {
mod tests {
use super::*;
use crate::message::{Message, MessageContent};
use mcp_core::role::Role;
use mcp_core::tool::Tool;
use rmcp::model::Role;
use serde_json::json;
#[test]
+2 -2
View File
@@ -635,7 +635,7 @@ mod final_output_tool_tests {
let last_message = responses.last().unwrap();
// Check that the last message is an assistant message with our final output
assert_eq!(last_message.role, mcp_core::role::Role::Assistant);
assert_eq!(last_message.role, rmcp::model::Role::Assistant);
let message_text = last_message.as_concat_text();
assert_eq!(message_text, r#"{"result":"Test output"}"#);
@@ -753,7 +753,7 @@ mod final_output_tool_tests {
}
// Check that the last message after the llm stream is the message directing the agent to continue
assert_eq!(last_message.role, mcp_core::role::Role::User);
assert_eq!(last_message.role, rmcp::model::Role::User);
let message_text = last_message.as_concat_text();
assert_eq!(message_text, FINAL_OUTPUT_CONTINUATION_MESSAGE);
+1
View File
@@ -8,6 +8,7 @@ workspace = true
[dependencies]
async-trait = "0.1"
rmcp = { workspace = true }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
thiserror = "1.0"
+1 -1
View File
@@ -1,7 +1,7 @@
/// Content sent around agents, extensions, and LLMs
/// The various content types can be display to humans but also understood by models
/// They include optional annotations used to help inform agent usage
use super::role::Role;
use super::Role;
use crate::resource::ResourceContents;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
+3 -10
View File
@@ -1,10 +1,3 @@
/// Roles to describe the origin/ownership of content
use serde::{Deserialize, Serialize};
use utoipa::ToSchema;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, ToSchema)]
#[serde(rename_all = "lowercase")]
pub enum Role {
User,
Assistant,
}
// passthrough, which will be deleted with the rest of the mcp-core crate after it is no longer used
// needed because it has internal references in this crate which leak out to usages used in goose etc crates
pub use rmcp::model::Role;
+1
View File
@@ -1998,6 +1998,7 @@
},
"Role": {
"type": "string",
"description": "A wrapper around rmcp::model::Role that implements ToSchema for utoipa",
"enum": [
"user",
"assistant"
+3
View File
@@ -338,6 +338,9 @@ export type ResourceContents = {
uri: string;
};
/**
* A wrapper around rmcp::model::Role that implements ToSchema for utoipa
*/
export type Role = 'user' | 'assistant';
export type RunNowResponse = {