use serde::{Deserialize, Serialize};
use serde_json::Value;
use crate::error::McpAdapterError;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum McpPromptRole {
User,
Assistant,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "camelCase")]
#[non_exhaustive]
pub enum McpPromptContent {
Text {
text: String,
},
Image {
mime_type: String,
data: String,
},
Resource {
uri: String,
text: Option<String>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct McpPromptMessage {
pub role: McpPromptRole,
pub content: McpPromptContent,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConvertedPromptMessage {
pub role: String,
pub text: String,
pub raw_content: Option<Value>,
}
pub async fn get_prompt(
transport: &dyn synwire_core::mcp::traits::McpTransport,
prompt_name: &str,
arguments: Option<Value>,
) -> Result<Vec<ConvertedPromptMessage>, McpAdapterError> {
let params = serde_json::json!({
"name": prompt_name,
"arguments": arguments.unwrap_or_else(|| serde_json::json!({})),
});
let response = transport
.call_tool("prompts/get", params)
.await
.map_err(|e| McpAdapterError::Transport {
message: format!("prompts/get failed for '{prompt_name}': {e}"),
})?;
let messages = response["messages"]
.as_array()
.ok_or_else(|| McpAdapterError::Transport {
message: format!("prompts/get response missing 'messages' for '{prompt_name}'"),
})?;
let converted: Vec<ConvertedPromptMessage> = messages
.iter()
.filter_map(
|m| match serde_json::from_value::<McpPromptMessage>(m.clone()) {
Ok(msg) => Some(convert_mcp_prompt_message(msg)),
Err(e) => {
tracing::warn!(error = %e, "Skipping malformed prompt message");
None
}
},
)
.collect();
Ok(converted)
}
pub fn convert_mcp_prompt_message(message: McpPromptMessage) -> ConvertedPromptMessage {
let role = match message.role {
McpPromptRole::User => "user".to_owned(),
McpPromptRole::Assistant => "assistant".to_owned(),
};
match message.content {
McpPromptContent::Text { text } => ConvertedPromptMessage {
role,
text,
raw_content: None,
},
McpPromptContent::Image { mime_type, data } => ConvertedPromptMessage {
role,
text: format!("[image: {mime_type}]"),
raw_content: Some(serde_json::json!({
"type": "image",
"mime_type": mime_type,
"data": data,
})),
},
McpPromptContent::Resource { uri, text } => {
let display = text.clone().unwrap_or_else(|| uri.clone());
ConvertedPromptMessage {
role,
text: display,
raw_content: Some(serde_json::json!({
"type": "resource",
"uri": uri,
"text": text,
})),
}
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn text_message_converts() {
let msg = McpPromptMessage {
role: McpPromptRole::User,
content: McpPromptContent::Text {
text: "Hello!".into(),
},
};
let converted = convert_mcp_prompt_message(msg);
assert_eq!(converted.role, "user");
assert_eq!(converted.text, "Hello!");
assert!(converted.raw_content.is_none());
}
#[test]
fn assistant_role_maps() {
let msg = McpPromptMessage {
role: McpPromptRole::Assistant,
content: McpPromptContent::Text { text: "ok".into() },
};
let converted = convert_mcp_prompt_message(msg);
assert_eq!(converted.role, "assistant");
}
#[test]
fn image_content_preserves_raw() {
let msg = McpPromptMessage {
role: McpPromptRole::User,
content: McpPromptContent::Image {
mime_type: "image/png".into(),
data: "AAAA".into(),
},
};
let converted = convert_mcp_prompt_message(msg);
assert!(converted.text.contains("image/png"));
assert!(converted.raw_content.is_some());
}
}