use std::{any::TypeId, error::Error}; use rmcp::{ service::ClientInitializeError, transport::{ AuthError, DynamicTransportError, streamable_http_client::{AuthRequiredError, InsufficientScopeError, StreamableHttpError}, }, }; use thiserror::Error; type TestHttpError = StreamableHttpError; #[derive(Debug, Error)] #[error("outer transport wrapper")] struct OuterError(#[source] TestHttpError); fn initialization_error(error: impl Error + Send + Sync + 'static) -> ClientInitializeError { ClientInitializeError::TransportError { error: DynamicTransportError::from_parts( "test transport", TypeId::of::<()>(), Box::new(error), ), context: "initialize".into(), } } #[test] fn classifies_local_authorization_required() { let error = TestHttpError::Auth(AuthError::AuthorizationRequired); assert!(initialization_error(error).is_authorization_required()); } #[test] fn classifies_http_authorization_challenge() { let error = TestHttpError::AuthRequired(AuthRequiredError::new("Bearer realm=\"mcp\"".to_owned())); assert!(initialization_error(error).is_authorization_required()); } #[test] fn classifies_authorization_required_through_multiple_sources() { let error = OuterError(TestHttpError::Auth(AuthError::AuthorizationRequired)); assert!(initialization_error(error).is_authorization_required()); } #[test] fn does_not_classify_unrelated_transport_errors() { let closed = TestHttpError::TransportChannelClosed; let refresh = TestHttpError::Auth(AuthError::TokenRefreshFailed("timeout".to_owned())); let scope = TestHttpError::InsufficientScope(InsufficientScopeError::new( "Bearer error=\"insufficient_scope\"".to_owned(), Some("admin".to_owned()), )); assert!(!initialization_error(closed).is_authorization_required()); assert!(!initialization_error(refresh).is_authorization_required()); assert!(!initialization_error(scope).is_authorization_required()); } #[test] fn does_not_classify_non_transport_initialization_errors() { assert!(!ClientInitializeError::Cancelled.is_authorization_required()); assert!( !ClientInitializeError::ConnectionClosed("server closed the connection".to_owned()) .is_authorization_required() ); } #[test] fn http_challenge_remains_available_as_an_error_source() { let error = TestHttpError::AuthRequired(AuthRequiredError::new("Bearer realm=\"mcp\"".to_owned())); let source = error.source().expect("auth challenge should be a source"); let challenge = source .downcast_ref::() .expect("source should retain the challenge type"); assert_eq!(challenge.www_authenticate_header, "Bearer realm=\"mcp\""); }