use rmcp::service::{Peer, RoleServer};
use crate::{
ElicitCommunicator, ElicitError, ElicitErrorKind, ElicitResult, ElicitationContext,
ElicitationStyle, StyleContext,
};
#[derive(Clone)]
pub struct ElicitServer {
peer: Peer<RoleServer>,
style_context: StyleContext,
elicitation_context: ElicitationContext,
}
impl ElicitServer {
#[tracing::instrument(skip(peer))]
pub fn new(peer: Peer<RoleServer>) -> Self {
tracing::debug!("Creating new ElicitServer");
Self {
peer,
style_context: StyleContext::default(),
elicitation_context: ElicitationContext::default(),
}
}
#[tracing::instrument(skip(self), level = "trace")]
pub fn peer(&self) -> &Peer<RoleServer> {
&self.peer
}
}
impl ElicitCommunicator for ElicitServer {
#[tracing::instrument(skip(self, prompt), fields(prompt_len = prompt.len()))]
async fn send_prompt(&self, prompt: &str) -> ElicitResult<String> {
tracing::debug!("Sending prompt to client via create_message");
let params = rmcp::model::CreateMessageRequestParams {
meta: None,
task: None,
messages: vec![rmcp::model::SamplingMessage {
role: rmcp::model::Role::User,
content: rmcp::model::SamplingContent::Single(
rmcp::model::SamplingMessageContent::Text(rmcp::model::RawTextContent {
text: prompt.to_string(),
meta: None,
}),
),
meta: None,
}],
model_preferences: None,
system_prompt: Some(
"You are helping elicit structured data. Provide clear, concise responses."
.to_string(),
),
include_context: None,
temperature: None,
max_tokens: 1000,
stop_sequences: None,
metadata: None,
tools: None,
tool_choice: None,
};
let result = self.peer.create_message(params).await.map_err(|e| {
tracing::error!(error = ?e, "create_message failed");
ElicitError::new(ElicitErrorKind::Service(e.into()))
})?;
tracing::debug!(model = %result.model, stop_reason = ?result.stop_reason, "Received response");
use rmcp::model::{SamplingContent, SamplingMessageContent};
match &result.message.content {
SamplingContent::Single(SamplingMessageContent::Text(text_content)) => {
tracing::debug!(
response_len = text_content.text.len(),
"Extracted text response"
);
Ok(text_content.text.clone())
}
SamplingContent::Single(SamplingMessageContent::Image(_)) => {
tracing::warn!("Received image content when expecting text");
Err(ElicitError::new(ElicitErrorKind::InvalidFormat {
expected: "text".to_string(),
received: "image".to_string(),
}))
}
_ => {
tracing::warn!("Received unexpected content type");
Err(ElicitError::new(ElicitErrorKind::InvalidFormat {
expected: "text".to_string(),
received: "other".to_string(),
}))
}
}
}
async fn call_tool(
&self,
_params: rmcp::model::CallToolRequestParams,
) -> Result<rmcp::model::CallToolResult, rmcp::service::ServiceError> {
Err(rmcp::service::ServiceError::McpError(
rmcp::ErrorData::internal_error(
"call_tool not supported in server-side elicitation",
None,
),
))
}
fn style_context(&self) -> &StyleContext {
&self.style_context
}
fn with_style<T: 'static, S: ElicitationStyle>(&self, style: S) -> Self {
let mut ctx = self.style_context.clone();
let _ = ctx.set_style::<T, S>(style);
Self {
peer: self.peer.clone(),
style_context: ctx,
elicitation_context: self.elicitation_context.clone(),
}
}
fn elicitation_context(&self) -> &ElicitationContext {
&self.elicitation_context
}
}