feat(cli): add mcp prompt support via slash commands (#1323)

This commit is contained in:
Kalvin C
2025-02-27 15:47:29 -08:00
committed by GitHub
parent 5bf05d545e
commit d0ca46983e
24 changed files with 958 additions and 82 deletions
+190
View File
@@ -10,6 +10,8 @@ use std::collections::HashSet;
use chrono::Utc;
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 serde_json::Value;
@@ -156,6 +158,37 @@ impl From<Content> for MessageContent {
}
}
impl From<PromptMessage> for Message {
fn from(prompt_message: PromptMessage) -> Self {
// Create a new message with the appropriate role
let message = match prompt_message.role {
PromptMessageRole::User => Message::user(),
PromptMessageRole::Assistant => Message::assistant(),
};
// Convert and add the content
let content = match prompt_message.content {
PromptMessageContent::Text { text } => MessageContent::text(text),
PromptMessageContent::Image { image } => {
MessageContent::image(image.data, image.mime_type)
}
PromptMessageContent::Resource { resource } => {
// For resources, convert to text content with the resource text
match resource.resource {
ResourceContents::TextResourceContents { text, .. } => {
MessageContent::text(text)
}
ResourceContents::BlobResourceContents { blob, .. } => {
MessageContent::text(format!("[Binary content: {}]", blob))
}
}
}
};
message.with_content(content)
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
/// A message to or from an LLM
#[serde(rename_all = "camelCase")]
@@ -305,7 +338,10 @@ impl Message {
#[cfg(test)]
mod tests {
use super::*;
use mcp_core::content::EmbeddedResource;
use mcp_core::handler::ToolError;
use mcp_core::prompt::PromptMessageContent;
use mcp_core::resource::ResourceContents;
use serde_json::{json, Value};
#[test]
@@ -420,4 +456,158 @@ mod tests {
panic!("Expected ToolRequest content");
}
}
#[test]
fn test_from_prompt_message_text() {
let prompt_content = PromptMessageContent::Text {
text: "Hello, world!".to_string(),
};
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: prompt_content,
};
let message = Message::from(prompt_message);
if let MessageContent::Text(text_content) = &message.content[0] {
assert_eq!(text_content.text, "Hello, world!");
} else {
panic!("Expected MessageContent::Text");
}
}
#[test]
fn test_from_prompt_message_image() {
let prompt_content = PromptMessageContent::Image {
image: ImageContent {
data: "base64data".to_string(),
mime_type: "image/jpeg".to_string(),
annotations: None,
},
};
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: prompt_content,
};
let message = Message::from(prompt_message);
if let MessageContent::Image(image_content) = &message.content[0] {
assert_eq!(image_content.data, "base64data");
assert_eq!(image_content.mime_type, "image/jpeg");
} else {
panic!("Expected MessageContent::Image");
}
}
#[test]
fn test_from_prompt_message_text_resource() {
let resource = ResourceContents::TextResourceContents {
uri: "file:///test.txt".to_string(),
mime_type: Some("text/plain".to_string()),
text: "Resource content".to_string(),
};
let prompt_content = PromptMessageContent::Resource {
resource: EmbeddedResource {
resource,
annotations: None,
},
};
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: prompt_content,
};
let message = Message::from(prompt_message);
if let MessageContent::Text(text_content) = &message.content[0] {
assert_eq!(text_content.text, "Resource content");
} else {
panic!("Expected MessageContent::Text");
}
}
#[test]
fn test_from_prompt_message_blob_resource() {
let resource = ResourceContents::BlobResourceContents {
uri: "file:///test.bin".to_string(),
mime_type: Some("application/octet-stream".to_string()),
blob: "binary_data".to_string(),
};
let prompt_content = PromptMessageContent::Resource {
resource: EmbeddedResource {
resource,
annotations: None,
},
};
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: prompt_content,
};
let message = Message::from(prompt_message);
if let MessageContent::Text(text_content) = &message.content[0] {
assert_eq!(text_content.text, "[Binary content: binary_data]");
} else {
panic!("Expected MessageContent::Text");
}
}
#[test]
fn test_from_prompt_message() {
// Test user message conversion
let prompt_message = PromptMessage {
role: PromptMessageRole::User,
content: PromptMessageContent::Text {
text: "Hello, world!".to_string(),
},
};
let message = Message::from(prompt_message);
assert_eq!(message.role, Role::User);
assert_eq!(message.content.len(), 1);
assert_eq!(message.as_concat_text(), "Hello, world!");
// Test assistant message conversion
let prompt_message = PromptMessage {
role: PromptMessageRole::Assistant,
content: PromptMessageContent::Text {
text: "I can help with that.".to_string(),
},
};
let message = Message::from(prompt_message);
assert_eq!(message.role, Role::Assistant);
assert_eq!(message.content.len(), 1);
assert_eq!(message.as_concat_text(), "I can help with that.");
}
#[test]
fn test_message_with_text() {
let message = Message::user().with_text("Hello");
assert_eq!(message.as_concat_text(), "Hello");
}
#[test]
fn test_message_with_tool_request() {
let tool_call = Ok(ToolCall {
name: "test_tool".to_string(),
arguments: serde_json::json!({}),
});
let message = Message::assistant().with_tool_request("req1", tool_call);
assert!(message.is_tool_call());
assert!(!message.is_tool_response());
let ids = message.get_tool_ids();
assert_eq!(ids.len(), 1);
assert!(ids.contains("req1"));
}
}