pub const VERSION: &str = env!("CARGO_PKG_VERSION");
pub const CRATE_NAME: &str = env!("CARGO_PKG_NAME");
pub mod client;
pub mod handlers;
pub mod integration;
pub mod prelude;
pub mod sampling;
pub mod middleware;
pub use client::{ConnectionInfo, ConnectionState, ManagerConfig, ServerGroup, SessionManager};
use std::sync::Arc;
use std::time::Duration;
pub use turbomcp_transport::Transport;
pub use turbomcp_protocol::{Error, Result};
pub use handlers::{
CancellationHandler,
CancelledNotification,
ElicitationAction,
ElicitationHandler,
ElicitationRequest,
ElicitationResponse,
HandlerError,
HandlerResult,
LogHandler,
LoggingNotification,
ProgressHandler,
ProgressNotification,
PromptListChangedHandler,
ResourceListChangedHandler,
ResourceUpdateHandler,
ResourceUpdatedNotification,
RootsHandler,
ToolListChangedHandler,
};
pub use sampling::{SamplingHandler, ServerInfo, UserInteractionHandler};
pub use middleware::{
Cache, CacheConfig, CacheLayer, CacheService, McpRequest, McpResponse, Metrics, MetricsLayer,
MetricsService, MetricsSnapshot, TracingLayer, TracingService,
};
pub use turbomcp_protocol::types::{
BlobResourceContents,
CallToolResult,
ContentBlock,
EmbeddedResource,
LogLevel,
Prompt,
Resource,
ResourceContent,
ResourceContents,
Role,
TextResourceContents,
Tool,
};
#[cfg(feature = "stdio")]
pub use turbomcp_transport::stdio::StdioTransport;
#[cfg(feature = "http")]
pub use turbomcp_transport::streamable_http_client::{
RetryPolicy, StreamableHttpClientConfig, StreamableHttpClientTransport,
};
#[cfg(feature = "tcp")]
pub use turbomcp_transport::tcp::{TcpTransport, TcpTransportBuilder};
#[cfg(feature = "unix")]
pub use turbomcp_transport::unix::{UnixTransport, UnixTransportBuilder};
#[cfg(feature = "websocket")]
pub use turbomcp_transport::websocket_bidirectional::{
WebSocketBidirectionalConfig, WebSocketBidirectionalTransport,
};
#[derive(Debug, Clone)]
pub struct ClientCapabilities {
pub tools: bool,
pub prompts: bool,
pub resources: bool,
pub sampling: bool,
pub max_concurrent_handlers: usize,
}
impl Default for ClientCapabilities {
fn default() -> Self {
Self {
tools: false,
prompts: false,
resources: false,
sampling: false,
max_concurrent_handlers: 100,
}
}
}
impl ClientCapabilities {
#[must_use]
pub fn all() -> Self {
Self {
tools: true,
prompts: true,
resources: true,
sampling: true,
max_concurrent_handlers: 100,
}
}
#[must_use]
pub fn core() -> Self {
Self {
tools: true,
prompts: true,
resources: true,
sampling: false,
max_concurrent_handlers: 100,
}
}
#[must_use]
pub fn minimal() -> Self {
Self {
tools: true,
prompts: false,
resources: false,
sampling: false,
max_concurrent_handlers: 100,
}
}
#[must_use]
pub fn only_tools() -> Self {
Self::minimal()
}
#[must_use]
pub fn only_resources() -> Self {
Self {
tools: false,
prompts: false,
resources: true,
sampling: false,
max_concurrent_handlers: 100,
}
}
#[must_use]
pub fn only_prompts() -> Self {
Self {
tools: false,
prompts: true,
resources: false,
sampling: false,
max_concurrent_handlers: 100,
}
}
#[must_use]
pub fn only_sampling() -> Self {
Self {
tools: false,
prompts: false,
resources: false,
sampling: true,
max_concurrent_handlers: 100,
}
}
}
pub use client::core::Client;
#[doc = "Result of client initialization"]
#[doc = ""]
#[doc = "Contains information about the server and the negotiated capabilities"]
#[doc = "after a successful initialization handshake."]
pub use client::config::InitializeResult;
#[derive(Debug, Clone)]
pub struct ConnectionConfig {
pub timeout_ms: u64,
pub max_retries: u32,
pub retry_delay_ms: u64,
pub keepalive_ms: u64,
}
fn protocol_transport_config(
connection_config: &ConnectionConfig,
) -> turbomcp_transport::TransportConfig {
let timeout = Duration::from_millis(connection_config.timeout_ms);
turbomcp_transport::TransportConfig {
connect_timeout: timeout,
keep_alive: Some(Duration::from_millis(connection_config.keepalive_ms)),
timeouts: turbomcp_transport::config::TimeoutConfig {
connect: timeout,
request: Some(timeout),
total: Some(timeout),
read: Some(timeout),
},
..Default::default()
}
}
fn resilience_requested(builder: &ClientBuilder) -> bool {
builder.enable_resilience
|| builder.retry_config.is_some()
|| builder.circuit_breaker_config.is_some()
|| builder.health_check_config.is_some()
}
impl Default for ConnectionConfig {
fn default() -> Self {
Self {
timeout_ms: 30_000, max_retries: 3, retry_delay_ms: 1_000, keepalive_ms: 60_000, }
}
}
#[derive(Debug, Default)]
pub struct ClientBuilder {
capabilities: ClientCapabilities,
connection_config: ConnectionConfig,
elicitation_handler: Option<Arc<dyn crate::handlers::ElicitationHandler>>,
log_handler: Option<Arc<dyn crate::handlers::LogHandler>>,
resource_update_handler: Option<Arc<dyn crate::handlers::ResourceUpdateHandler>>,
progress_handler: Option<Arc<dyn crate::handlers::ProgressHandler>>,
enable_resilience: bool,
retry_config: Option<turbomcp_transport::resilience::RetryConfig>,
circuit_breaker_config: Option<turbomcp_transport::resilience::CircuitBreakerConfig>,
health_check_config: Option<turbomcp_transport::resilience::HealthCheckConfig>,
}
impl ClientBuilder {
#[must_use]
pub fn new() -> Self {
Self::default()
}
#[must_use]
pub fn with_tools(mut self, enabled: bool) -> Self {
self.capabilities.tools = enabled;
self
}
#[must_use]
pub fn with_prompts(mut self, enabled: bool) -> Self {
self.capabilities.prompts = enabled;
self
}
#[must_use]
pub fn with_resources(mut self, enabled: bool) -> Self {
self.capabilities.resources = enabled;
self
}
#[must_use]
pub fn with_sampling(mut self, enabled: bool) -> Self {
self.capabilities.sampling = enabled;
self
}
#[must_use]
pub fn with_max_concurrent_handlers(mut self, limit: usize) -> Self {
self.capabilities.max_concurrent_handlers = limit;
self
}
#[must_use]
pub fn with_capabilities(mut self, capabilities: ClientCapabilities) -> Self {
self.capabilities = capabilities;
self
}
#[must_use]
pub fn with_connection_config(mut self, config: ConnectionConfig) -> Self {
self.connection_config = config;
self
}
#[must_use]
pub fn with_timeout(mut self, timeout_ms: u64) -> Self {
self.connection_config.timeout_ms = timeout_ms;
self
}
#[must_use]
pub fn with_max_retries(mut self, max_retries: u32) -> Self {
self.connection_config.max_retries = max_retries;
self
}
#[must_use]
pub fn with_retry_delay(mut self, delay_ms: u64) -> Self {
self.connection_config.retry_delay_ms = delay_ms;
self
}
#[must_use]
pub fn with_keepalive(mut self, interval_ms: u64) -> Self {
self.connection_config.keepalive_ms = interval_ms;
self
}
#[must_use]
pub fn enable_resilience(mut self) -> Self {
self.enable_resilience = true;
self
}
#[must_use]
pub fn with_retry_config(
mut self,
config: turbomcp_transport::resilience::RetryConfig,
) -> Self {
self.retry_config = Some(config);
self.enable_resilience = true; self
}
#[must_use]
pub fn with_circuit_breaker_config(
mut self,
config: turbomcp_transport::resilience::CircuitBreakerConfig,
) -> Self {
self.circuit_breaker_config = Some(config);
self.enable_resilience = true; self
}
#[must_use]
pub fn with_health_check_config(
mut self,
config: turbomcp_transport::resilience::HealthCheckConfig,
) -> Self {
self.health_check_config = Some(config);
self.enable_resilience = true; self
}
pub fn with_elicitation_handler(
mut self,
handler: Arc<dyn crate::handlers::ElicitationHandler>,
) -> Self {
self.elicitation_handler = Some(handler);
self
}
pub fn with_log_handler(mut self, handler: Arc<dyn crate::handlers::LogHandler>) -> Self {
self.log_handler = Some(handler);
self
}
pub fn with_resource_update_handler(
mut self,
handler: Arc<dyn crate::handlers::ResourceUpdateHandler>,
) -> Self {
self.resource_update_handler = Some(handler);
self
}
pub fn with_progress_handler(
mut self,
handler: Arc<dyn crate::handlers::ProgressHandler>,
) -> Self {
self.progress_handler = Some(handler);
self
}
pub async fn build<T: Transport + 'static>(self, transport: T) -> Result<Client<T>> {
if resilience_requested(&self) {
return Err(Error::configuration(
"resilience settings require build_resilient(); build() would otherwise ignore them"
.to_string(),
));
}
let client = Client::with_capabilities_and_config(
transport,
self.capabilities,
protocol_transport_config(&self.connection_config),
);
if let Some(handler) = self.elicitation_handler {
client.set_elicitation_handler(handler);
}
if let Some(handler) = self.log_handler {
client.set_log_handler(handler);
}
if let Some(handler) = self.resource_update_handler {
client.set_resource_update_handler(handler);
}
if let Some(handler) = self.progress_handler {
client.set_progress_handler(handler);
}
Ok(client)
}
pub async fn build_resilient<T: Transport + 'static>(
self,
transport: T,
) -> Result<Client<turbomcp_transport::resilience::TurboTransport>> {
use turbomcp_transport::resilience::TurboTransport;
let retry_config =
self.retry_config
.unwrap_or_else(|| turbomcp_transport::resilience::RetryConfig {
max_attempts: self.connection_config.max_retries.max(1),
base_delay: Duration::from_millis(self.connection_config.retry_delay_ms),
..Default::default()
});
let circuit_config = self.circuit_breaker_config.unwrap_or_default();
let health_config = self.health_check_config.unwrap_or_else(|| {
turbomcp_transport::resilience::HealthCheckConfig {
timeout: Duration::from_millis(self.connection_config.timeout_ms),
..Default::default()
}
});
let robust_transport = TurboTransport::new(
Box::new(transport),
retry_config,
circuit_config,
health_config,
);
let client = Client::with_capabilities_and_config(
robust_transport,
self.capabilities,
protocol_transport_config(&self.connection_config),
);
if let Some(handler) = self.elicitation_handler {
client.set_elicitation_handler(handler);
}
if let Some(handler) = self.log_handler {
client.set_log_handler(handler);
}
if let Some(handler) = self.resource_update_handler {
client.set_resource_update_handler(handler);
}
if let Some(handler) = self.progress_handler {
client.set_progress_handler(handler);
}
Ok(client)
}
pub fn build_sync<T: Transport + 'static>(self, transport: T) -> Client<T> {
assert!(
!resilience_requested(&self),
"resilience settings require build_resilient(); build_sync() would otherwise ignore them"
);
let client = Client::with_capabilities_and_config(
transport,
self.capabilities,
protocol_transport_config(&self.connection_config),
);
if let Some(handler) = self.elicitation_handler {
client.set_elicitation_handler(handler);
}
if let Some(handler) = self.log_handler {
client.set_log_handler(handler);
}
if let Some(handler) = self.resource_update_handler {
client.set_resource_update_handler(handler);
}
if let Some(handler) = self.progress_handler {
client.set_progress_handler(handler);
}
client
}
#[must_use]
pub fn capabilities(&self) -> &ClientCapabilities {
&self.capabilities
}
#[must_use]
pub fn connection_config(&self) -> &ConnectionConfig {
&self.connection_config
}
#[must_use]
pub fn has_handlers(&self) -> bool {
self.elicitation_handler.is_some()
|| self.log_handler.is_some()
|| self.resource_update_handler.is_some()
|| self.progress_handler.is_some()
}
}
pub use turbomcp_protocol::types::ServerCapabilities as PublicServerCapabilities;
#[cfg(test)]
mod tests {
use super::*;
use std::future::Future;
use std::pin::Pin;
use turbomcp_transport::{
TransportCapabilities, TransportConfig, TransportMessage, TransportMetrics,
TransportResult, TransportState, TransportType,
};
#[derive(Debug, Default)]
struct NoopTransport {
capabilities: TransportCapabilities,
}
impl Transport for NoopTransport {
fn transport_type(&self) -> TransportType {
TransportType::Stdio
}
fn capabilities(&self) -> &TransportCapabilities {
&self.capabilities
}
fn state(&self) -> Pin<Box<dyn Future<Output = TransportState> + Send + '_>> {
Box::pin(async { TransportState::Disconnected })
}
fn connect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
fn disconnect(&self) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
fn send(
&self,
_message: TransportMessage,
) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
fn receive(
&self,
) -> Pin<Box<dyn Future<Output = TransportResult<Option<TransportMessage>>> + Send + '_>>
{
Box::pin(async { Ok(None) })
}
fn metrics(&self) -> Pin<Box<dyn Future<Output = TransportMetrics> + Send + '_>> {
Box::pin(async { TransportMetrics::default() })
}
fn configure(
&self,
_config: TransportConfig,
) -> Pin<Box<dyn Future<Output = TransportResult<()>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
}
#[tokio::test]
async fn build_rejects_resilience_flags() {
let result = ClientBuilder::new()
.enable_resilience()
.build(NoopTransport::default())
.await;
assert!(result.is_err());
let err = match result {
Ok(_) => panic!("expected build() to reject resilience settings"),
Err(err) => err,
};
assert!(err.to_string().contains("build_resilient"));
}
}