fix: honor supported_protocol_versions when negotiating initialize (#1093)
This commit is contained in:
parent
51c8ee60e3
commit
d272389fd4
5 changed files with 246 additions and 16 deletions
|
|
@ -300,6 +300,10 @@ impl<H: ServerHandler> Service<RoleServer> for H {
|
|||
fn get_info(&self) -> <RoleServer as ServiceRole>::Info {
|
||||
self.get_info()
|
||||
}
|
||||
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
ServerHandler::supported_protocol_versions(self)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! server_handler_methods {
|
||||
|
|
@ -321,10 +325,17 @@ macro_rules! server_handler_methods {
|
|||
info.protocol_version = negotiate_protocol_version(
|
||||
&request.protocol_version,
|
||||
info.protocol_version,
|
||||
&self.supported_protocol_versions(),
|
||||
);
|
||||
std::future::ready(Ok(info))
|
||||
}
|
||||
/// Return the protocol versions supported by this server.
|
||||
///
|
||||
/// Defaults to every version this SDK knows. Override it to narrow the
|
||||
/// set to the revisions the server actually implements: the returned
|
||||
/// list is advertised by [`Self::discover`], bounds what `initialize`
|
||||
/// negotiation may agree to, and is what per-request versions are
|
||||
/// validated against.
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Cow::Borrowed(ProtocolVersion::KNOWN_VERSIONS)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::Arc;
|
||||
use std::{borrow::Cow, sync::Arc};
|
||||
|
||||
use prompt::{IntoPromptRoute, PromptRoute};
|
||||
use tool::{IntoToolRoute, ToolRoute};
|
||||
|
|
@ -6,7 +6,10 @@ use tool::{IntoToolRoute, ToolRoute};
|
|||
use super::ServerHandler;
|
||||
use crate::{
|
||||
RoleServer, Service,
|
||||
model::{ClientNotification, ClientRequest, ListPromptsResult, ListToolsResult, ServerResult},
|
||||
model::{
|
||||
ClientNotification, ClientRequest, ListPromptsResult, ListToolsResult, ProtocolVersion,
|
||||
ServerResult,
|
||||
},
|
||||
service::NotificationContext,
|
||||
};
|
||||
|
||||
|
|
@ -155,6 +158,10 @@ where
|
|||
.list_changed = Some(true);
|
||||
info
|
||||
}
|
||||
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
ServerHandler::supported_protocol_versions(&self.service)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
use std::sync::OnceLock;
|
||||
use std::{borrow::Cow, sync::OnceLock};
|
||||
|
||||
use futures::FutureExt;
|
||||
#[cfg(not(feature = "local"))]
|
||||
|
|
@ -284,6 +284,19 @@ pub trait Service<R: ServiceRole>: Send + Sync + 'static {
|
|||
context: NotificationContext<R>,
|
||||
) -> impl Future<Output = Result<(), McpError>> + MaybeSendFuture + '_;
|
||||
fn get_info(&self) -> R::Info;
|
||||
/// The protocol versions this service can speak, bounding what `initialize`
|
||||
/// negotiation may agree to.
|
||||
///
|
||||
/// Servers normally override
|
||||
/// [`ServerHandler::supported_protocol_versions`] instead of this method;
|
||||
/// the blanket `Service` impl forwards to it. This method exists so the
|
||||
/// transport and handshake layers, which see only a `Service`, can read the
|
||||
/// list and avoid agreeing to a version the server cannot serve.
|
||||
///
|
||||
/// [`ServerHandler::supported_protocol_versions`]: crate::handler::server::ServerHandler::supported_protocol_versions
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Cow::Borrowed(ProtocolVersion::KNOWN_VERSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local")]
|
||||
|
|
@ -299,6 +312,12 @@ pub trait Service<R: ServiceRole>: 'static {
|
|||
context: NotificationContext<R>,
|
||||
) -> impl Future<Output = Result<(), McpError>> + MaybeSendFuture + '_;
|
||||
fn get_info(&self) -> R::Info;
|
||||
/// The protocol versions this service can speak.
|
||||
///
|
||||
/// See the non-`local` variant of this trait for details.
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Cow::Borrowed(ProtocolVersion::KNOWN_VERSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ServiceExt<R: ServiceRole>: Service<R> + Sized {
|
||||
|
|
@ -350,6 +369,10 @@ impl<R: ServiceRole> Service<R> for Box<dyn DynService<R>> {
|
|||
fn get_info(&self) -> R::Info {
|
||||
DynService::get_info(self.as_ref())
|
||||
}
|
||||
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
DynService::supported_protocol_versions(self.as_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "local"))]
|
||||
|
|
@ -365,6 +388,10 @@ pub trait DynService<R: ServiceRole>: Send + Sync {
|
|||
context: NotificationContext<R>,
|
||||
) -> MaybeBoxFuture<'_, Result<(), McpError>>;
|
||||
fn get_info(&self) -> R::Info;
|
||||
/// See [`Service::supported_protocol_versions`].
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Cow::Borrowed(ProtocolVersion::KNOWN_VERSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "local")]
|
||||
|
|
@ -380,6 +407,10 @@ pub trait DynService<R: ServiceRole> {
|
|||
context: NotificationContext<R>,
|
||||
) -> MaybeBoxFuture<'_, Result<(), McpError>>;
|
||||
fn get_info(&self) -> R::Info;
|
||||
/// See [`Service::supported_protocol_versions`].
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Cow::Borrowed(ProtocolVersion::KNOWN_VERSIONS)
|
||||
}
|
||||
}
|
||||
|
||||
impl<R: ServiceRole, S: Service<R>> DynService<R> for S {
|
||||
|
|
@ -400,6 +431,9 @@ impl<R: ServiceRole, S: Service<R>> DynService<R> for S {
|
|||
fn get_info(&self) -> R::Info {
|
||||
self.get_info()
|
||||
}
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
Service::supported_protocol_versions(self)
|
||||
}
|
||||
}
|
||||
|
||||
use std::{
|
||||
|
|
|
|||
|
|
@ -460,12 +460,18 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Echoes the client-requested version if known; otherwise returns `server_fallback`.
|
||||
/// Echoes the client-requested version if the server supports it; otherwise
|
||||
/// returns `server_fallback`.
|
||||
///
|
||||
/// `server_supported` comes from [`Service::supported_protocol_versions`], so a
|
||||
/// server that narrows that list is never made to answer `initialize` with a
|
||||
/// version it cannot serve.
|
||||
pub(crate) fn negotiate_protocol_version(
|
||||
client_requested: &ProtocolVersion,
|
||||
server_fallback: ProtocolVersion,
|
||||
server_supported: &[ProtocolVersion],
|
||||
) -> ProtocolVersion {
|
||||
if ProtocolVersion::KNOWN_VERSIONS.contains(client_requested) {
|
||||
if server_supported.contains(client_requested) {
|
||||
client_requested.clone()
|
||||
} else {
|
||||
tracing::warn!(
|
||||
|
|
@ -578,8 +584,11 @@ where
|
|||
return Err(ServerInitializeError::InitializeFailed(e));
|
||||
}
|
||||
};
|
||||
init_response.protocol_version =
|
||||
negotiate_protocol_version(&requested_protocol_version, init_response.protocol_version);
|
||||
init_response.protocol_version = negotiate_protocol_version(
|
||||
&requested_protocol_version,
|
||||
init_response.protocol_version,
|
||||
&service.supported_protocol_versions(),
|
||||
);
|
||||
// Update peer_info so context.protocol_version() reflects the negotiated
|
||||
// version in all subsequent request handlers.
|
||||
negotiated_peer_info.protocol_version = init_response.protocol_version.clone();
|
||||
|
|
|
|||
|
|
@ -279,8 +279,11 @@ impl<S: Service<RoleServer>> Service<RoleServer> for NegotiatingStatelessHttpSer
|
|||
if let (Some(requested), ServerResult::InitializeResult(result)) =
|
||||
(requested_protocol_version, &mut response)
|
||||
{
|
||||
result.protocol_version =
|
||||
negotiate_protocol_version(&requested, result.protocol_version.clone());
|
||||
result.protocol_version = negotiate_protocol_version(
|
||||
&requested,
|
||||
result.protocol_version.clone(),
|
||||
&self.0.supported_protocol_versions(),
|
||||
);
|
||||
if let Some(peer_info) = peer.peer_info() {
|
||||
let mut peer_info = (*peer_info).clone();
|
||||
peer_info.protocol_version = result.protocol_version.clone();
|
||||
|
|
@ -301,6 +304,10 @@ impl<S: Service<RoleServer>> Service<RoleServer> for NegotiatingStatelessHttpSer
|
|||
fn get_info(&self) -> ServerInfo {
|
||||
self.0.get_info()
|
||||
}
|
||||
|
||||
fn supported_protocol_versions(&self) -> Cow<'static, [ProtocolVersion]> {
|
||||
self.0.supported_protocol_versions()
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(
|
||||
|
|
|
|||
Loading…
Reference in a new issue