Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 12 additions & 9 deletions conformance/src/bin/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@ impl ServerHandler for ConformanceServer {
&self,
request: CallToolRequestParams,
cx: RequestContext<RoleServer>,
) -> Result<CallToolResult, ErrorData> {
) -> Result<CallToolResponse, ErrorData> {
let args = request.arguments.unwrap_or_default();
match request.name.as_ref() {
let result = match request.name.as_ref() {
"test_simple_text" => Ok(CallToolResult::success(vec![ContentBlock::text(
"This is a simple text response for testing.",
)])),
Expand Down Expand Up @@ -530,7 +530,8 @@ impl ServerHandler for ConformanceServer {
format!("Unknown tool: {}", request.name),
None,
)),
}
};
result.map(Into::into)
}

async fn list_resources(
Expand All @@ -555,9 +556,9 @@ impl ServerHandler for ConformanceServer {
&self,
request: ReadResourceRequestParams,
_cx: RequestContext<RoleServer>,
) -> Result<ReadResourceResult, ErrorData> {
) -> Result<ReadResourceResponse, ErrorData> {
let uri = request.uri.as_str();
match uri {
let result = match uri {
"test://static-text" => Ok(ReadResourceResult::new(vec![
ResourceContents::TextResourceContents {
uri: uri.into(),
Expand Down Expand Up @@ -598,7 +599,8 @@ impl ServerHandler for ConformanceServer {
))
}
}
}
};
result.map(Into::into)
}

async fn list_resource_templates(
Expand Down Expand Up @@ -679,8 +681,8 @@ impl ServerHandler for ConformanceServer {
&self,
request: GetPromptRequestParams,
_cx: RequestContext<RoleServer>,
) -> Result<GetPromptResult, ErrorData> {
match request.name.as_str() {
) -> Result<GetPromptResponse, ErrorData> {
let result = match request.name.as_str() {
"test_simple_prompt" => Ok(GetPromptResult::new(vec![PromptMessage::new_text(
Role::User,
"This is a simple test prompt.",
Expand Down Expand Up @@ -724,7 +726,8 @@ impl ServerHandler for ConformanceServer {
format!("Unknown prompt: {}", request.name),
None,
)),
}
};
result.map(Into::into)
}

async fn complete(
Expand Down
2 changes: 1 addition & 1 deletion crates/rmcp-macros/src/prompt_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub fn prompt_handler(attr: TokenStream, input: TokenStream) -> syn::Result<Toke
&self,
request: rmcp::model::GetPromptRequestParams,
context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::GetPromptResult, rmcp::ErrorData> {
) -> Result<rmcp::model::GetPromptResponse, rmcp::ErrorData> {
let prompt_context = rmcp::handler::server::prompt::PromptContext::new(
self,
request.name,
Expand Down
2 changes: 1 addition & 1 deletion crates/rmcp-macros/src/tool_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ pub fn tool_handler(attr: TokenStream, input: TokenStream) -> syn::Result<TokenS
&self,
request: rmcp::model::CallToolRequestParams,
context: rmcp::service::RequestContext<rmcp::RoleServer>,
) -> Result<rmcp::model::CallToolResult, rmcp::ErrorData> {
) -> Result<rmcp::model::CallToolResponse, rmcp::ErrorData> {
let tcc = rmcp::handler::server::tool::ToolCallContext::new(self, request, context);
#router.call(tcc).await
}
Expand Down
32 changes: 23 additions & 9 deletions crates/rmcp/src/handler/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ impl<H: ServerHandler> Service<RoleServer> for H {
) -> Result<<RoleServer as ServiceRole>::Resp, McpError> {
// `context` is moved into the dispatch below, so read the negotiated version first.
let protocol_version = context.protocol_version();
let mrtr_supported = protocol_version
.as_ref()
.is_some_and(|v| v.as_str() >= ProtocolVersion::V_2026_07_28.as_str());
let result = match request {
ClientRequest::InitializeRequest(request) => self
.initialize(request.params, context)
Expand All @@ -45,7 +48,7 @@ impl<H: ServerHandler> Service<RoleServer> for H {
ClientRequest::GetPromptRequest(request) => self
.get_prompt(request.params, context)
.await
.map(ServerResult::GetPromptResult),
.map(ServerResult::from),
ClientRequest::ListPromptsRequest(request) => self
.list_prompts(request.params, context)
.await
Expand All @@ -61,7 +64,7 @@ impl<H: ServerHandler> Service<RoleServer> for H {
ClientRequest::ReadResourceRequest(request) => self
.read_resource(request.params, context)
.await
.map(ServerResult::ReadResourceResult),
.map(ServerResult::from),
ClientRequest::SubscribeRequest(request) => self
.subscribe(request.params, context)
.await
Expand Down Expand Up @@ -104,7 +107,7 @@ impl<H: ServerHandler> Service<RoleServer> for H {
} else {
self.call_tool(request.params, context)
.await
.map(ServerResult::CallToolResult)
.map(ServerResult::from)
}
}
ClientRequest::ListToolsRequest(request) => self
Expand Down Expand Up @@ -132,6 +135,17 @@ impl<H: ServerHandler> Service<RoleServer> for H {
.await
.map(ServerResult::CancelTaskResult),
};
let result = result.and_then(|result| {
if matches!(result, ServerResult::InputRequiredResult(_)) && !mrtr_supported {
Err(McpError::invalid_request(
"InputRequiredResult requires negotiated protocol version 2026-07-28 or newer",
None,
))
} else {
Ok(result)
}
});

// SEP-2164: peers negotiating 2026-07-28+ get the standard INVALID_PARAMS code for
// resource-not-found; older peers keep RESOURCE_NOT_FOUND. ISO `YYYY-MM-DD` versions
// compare lexically the same as chronologically.
Expand Down Expand Up @@ -223,7 +237,7 @@ macro_rules! server_handler_methods {
&self,
request: GetPromptRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetPromptResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<GetPromptResponse, McpError>> + MaybeSendFuture + '_ {
std::future::ready(Err(McpError::method_not_found::<GetPromptRequestMethod>()))
}
fn list_prompts(
Expand Down Expand Up @@ -253,7 +267,7 @@ macro_rules! server_handler_methods {
&self,
request: ReadResourceRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ReadResourceResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<ReadResourceResponse, McpError>> + MaybeSendFuture + '_ {
std::future::ready(Err(
McpError::method_not_found::<ReadResourceRequestMethod>(),
))
Expand Down Expand Up @@ -306,7 +320,7 @@ macro_rules! server_handler_methods {
&self,
request: CallToolRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CallToolResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<CallToolResponse, McpError>> + MaybeSendFuture + '_ {
std::future::ready(Err(McpError::method_not_found::<CallToolRequestMethod>()))
}
fn list_tools(
Expand Down Expand Up @@ -479,7 +493,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: GetPromptRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<GetPromptResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<GetPromptResponse, McpError>> + MaybeSendFuture + '_ {
(**self).get_prompt(request, context)
}

Expand Down Expand Up @@ -512,7 +526,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: ReadResourceRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<ReadResourceResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<ReadResourceResponse, McpError>> + MaybeSendFuture + '_ {
(**self).read_resource(request, context)
}

Expand All @@ -536,7 +550,7 @@ macro_rules! impl_server_handler_for_wrapper {
&self,
request: CallToolRequestParams,
context: RequestContext<RoleServer>,
) -> impl Future<Output = Result<CallToolResult, McpError>> + MaybeSendFuture + '_ {
) -> impl Future<Output = Result<CallToolResponse, McpError>> + MaybeSendFuture + '_ {
(**self).call_tool(request, context)
}

Expand Down
39 changes: 23 additions & 16 deletions crates/rmcp/src/handler/server/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ pub use super::common::{Extension, RequestId};
use crate::{
RoleServer,
handler::server::wrapper::Parameters,
model::{GetPromptResult, PromptMessage},
model::{GetPromptResponse, GetPromptResult, InputRequiredResult, PromptMessage},
service::{MaybeBoxFuture, MaybeSend, MaybeSendFuture, RequestContext},
};

Expand Down Expand Up @@ -59,12 +59,12 @@ pub trait GetPromptHandler<S, A> {
fn handle(
self,
context: PromptContext<'_, S>,
) -> MaybeBoxFuture<'_, Result<GetPromptResult, crate::ErrorData>>;
) -> MaybeBoxFuture<'_, Result<GetPromptResponse, crate::ErrorData>>;
}

/// Type alias for dynamic prompt handlers
#[cfg(not(feature = "local"))]
pub type DynGetPromptHandler<S> = dyn for<'a> Fn(PromptContext<'a, S>) -> BoxFuture<'a, Result<GetPromptResult, crate::ErrorData>>
pub type DynGetPromptHandler<S> = dyn for<'a> Fn(PromptContext<'a, S>) -> BoxFuture<'a, Result<GetPromptResponse, crate::ErrorData>>
+ Send
+ Sync;

Expand All @@ -73,7 +73,7 @@ pub type DynGetPromptHandler<S> = dyn for<'a> Fn(
PromptContext<'a, S>,
) -> futures::future::LocalBoxFuture<
'a,
Result<GetPromptResult, crate::ErrorData>,
Result<GetPromptResponse, crate::ErrorData>,
>;

/// Adapter type for async methods that return `Vec<PromptMessage>`
Expand All @@ -91,28 +91,35 @@ pub struct SyncPromptMethodAdapter<P, R>(PhantomData<fn(P) -> R>);

/// Trait for types that can be converted into GetPromptResult
pub trait IntoGetPromptResult {
fn into_get_prompt_result(self) -> Result<GetPromptResult, crate::ErrorData>;
fn into_get_prompt_result(self) -> Result<GetPromptResponse, crate::ErrorData>;
}

impl IntoGetPromptResult for GetPromptResult {
fn into_get_prompt_result(self) -> Result<GetPromptResult, crate::ErrorData> {
Ok(self)
fn into_get_prompt_result(self) -> Result<GetPromptResponse, crate::ErrorData> {
Ok(self.into())
}
}

impl IntoGetPromptResult for InputRequiredResult {
fn into_get_prompt_result(self) -> Result<GetPromptResponse, crate::ErrorData> {
Ok(self.into())
}
}

impl IntoGetPromptResult for Vec<PromptMessage> {
fn into_get_prompt_result(self) -> Result<GetPromptResult, crate::ErrorData> {
fn into_get_prompt_result(self) -> Result<GetPromptResponse, crate::ErrorData> {
Ok(GetPromptResult {
result_type: Default::default(),
description: None,
messages: self,
meta: None,
})
}
.into())
}
}

impl<T: IntoGetPromptResult> IntoGetPromptResult for Result<T, crate::ErrorData> {
fn into_get_prompt_result(self) -> Result<GetPromptResult, crate::ErrorData> {
fn into_get_prompt_result(self) -> Result<GetPromptResponse, crate::ErrorData> {
self.and_then(|v| v.into_get_prompt_result())
}
}
Expand All @@ -129,7 +136,7 @@ pin_project_lite::pin_project! {
},
Ready {
#[pin]
result: futures::future::Ready<Result<GetPromptResult, crate::ErrorData>>,
result: futures::future::Ready<Result<GetPromptResponse, crate::ErrorData>>,
}
}
}
Expand All @@ -139,7 +146,7 @@ where
F: Future<Output = R>,
R: IntoGetPromptResult,
{
type Output = Result<GetPromptResult, crate::ErrorData>;
type Output = Result<GetPromptResponse, crate::ErrorData>;

fn poll(
self: std::pin::Pin<&mut Self>,
Expand Down Expand Up @@ -216,7 +223,7 @@ macro_rules! impl_prompt_handler_for {
fn handle(
self,
mut context: PromptContext<'_, S>,
) -> MaybeBoxFuture<'_, Result<GetPromptResult, crate::ErrorData>>
) -> MaybeBoxFuture<'_, Result<GetPromptResponse, crate::ErrorData>>
{
$(
let result = $Tn::from_context_part(&mut context);
Expand Down Expand Up @@ -249,7 +256,7 @@ macro_rules! impl_prompt_handler_for {
fn handle(
self,
mut context: PromptContext<'_, S>,
) -> MaybeBoxFuture<'_, Result<GetPromptResult, crate::ErrorData>>
) -> MaybeBoxFuture<'_, Result<GetPromptResponse, crate::ErrorData>>
{
$(
let result = $Tn::from_context_part(&mut context);
Expand Down Expand Up @@ -280,7 +287,7 @@ macro_rules! impl_prompt_handler_for {
fn handle(
self,
mut context: PromptContext<'_, S>,
) -> MaybeBoxFuture<'_, Result<GetPromptResult, crate::ErrorData>>
) -> MaybeBoxFuture<'_, Result<GetPromptResponse, crate::ErrorData>>
{
// Extract all parameters before moving into the async block
$(
Expand Down Expand Up @@ -315,7 +322,7 @@ macro_rules! impl_prompt_handler_for {
fn handle(
self,
mut context: PromptContext<'_, S>,
) -> MaybeBoxFuture<'_, Result<GetPromptResult, crate::ErrorData>>
) -> MaybeBoxFuture<'_, Result<GetPromptResponse, crate::ErrorData>>
{
$(
let result = $Tn::from_context_part(&mut context);
Expand Down
6 changes: 3 additions & 3 deletions crates/rmcp/src/handler/server/router.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ where
context,
);
let result = self.tool_router.call(tool_call_context).await?;
Ok(ServerResult::CallToolResult(result))
Ok(ServerResult::from(result))
} else {
self.service
.handle_request(ClientRequest::CallToolRequest(request), context)
Expand All @@ -129,7 +129,7 @@ where
context,
);
let result = self.prompt_router.get_prompt(prompt_context).await?;
Ok(ServerResult::GetPromptResult(result))
Ok(ServerResult::from(result))
} else {
self.service
.handle_request(ClientRequest::GetPromptRequest(request), context)
Expand Down Expand Up @@ -193,7 +193,7 @@ mod tests {
async fn test_router_deferred_notifier_e2e() {
let mut router = Router::new(DummyHandler).with_tool(tool::ToolRoute::new_dyn(
Tool::new("my_tool", "test", Arc::new(Default::default())),
|_ctx| Box::pin(async { Ok(CallToolResult::default()) }),
|_ctx| Box::pin(async { Ok(CallToolResult::default().into()) }),
));

let id_provider: Arc<dyn RequestIdProvider> =
Expand Down
7 changes: 4 additions & 3 deletions crates/rmcp/src/handler/server/router/prompt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use std::{borrow::Cow, sync::Arc};

use crate::{
handler::server::prompt::{DynGetPromptHandler, GetPromptHandler, PromptContext},
model::{GetPromptResult, Prompt},
model::{GetPromptResponse, Prompt},
service::{MaybeBoxFuture, MaybeSend},
};

Expand Down Expand Up @@ -50,7 +50,8 @@ impl<S: MaybeSend + 'static> PromptRoute<S> {
where
H: for<'a> Fn(
PromptContext<'a, S>,
) -> MaybeBoxFuture<'a, Result<GetPromptResult, crate::ErrorData>>
)
-> MaybeBoxFuture<'a, Result<GetPromptResponse, crate::ErrorData>>
+ MaybeSend
+ 'static,
{
Expand Down Expand Up @@ -175,7 +176,7 @@ where
pub async fn get_prompt(
&self,
context: PromptContext<'_, S>,
) -> Result<GetPromptResult, crate::ErrorData> {
) -> Result<GetPromptResponse, crate::ErrorData> {
let item = self.map.get(context.name.as_str()).ok_or_else(|| {
crate::ErrorData::invalid_params(
format!("prompt '{}' not found", context.name),
Expand Down
Loading