use crate::PROTOCOL_VERSIONS;
use crate::client::notification_handler::NotificationsHandler;
use crate::transport::{StdIoClient, TransportProto, stdio::options::StdIoOptions};
#[cfg(not(feature = "proto-2026-07-28-rc"))]
use crate::types::SamplingCapability;
use crate::types::elicitation::ElicitationHandler;
#[cfg(not(feature = "proto-2026-07-28-rc"))]
use crate::types::sampling::SamplingHandler;
use crate::types::{ElicitationCapability, Implementation};
#[cfg(not(feature = "proto-2026-07-28-rc"))]
use crate::types::{Root, RootsCapability, Uri};
#[cfg(not(feature = "proto-2026-07-28-rc"))]
use std::collections::HashMap;
use std::fmt::{Debug, Formatter};
use std::sync::Arc;
use std::time::Duration;
#[cfg(feature = "tasks")]
use crate::types::ClientTasksCapability;
#[cfg(feature = "http-client")]
use crate::transport::http::HttpClient;
const DEFAULT_REQUEST_TIMEOUT: u64 = 10;
#[cfg(feature = "proto-2026-07-28-rc")]
const DEFAULT_MAX_MRTR_ROUNDS: usize = 8;
#[cfg(feature = "proto-2026-07-28-rc")]
#[derive(Debug, Clone)]
pub struct TraceContext {
pub traceparent: String,
pub tracestate: Option<String>,
}
#[cfg(feature = "proto-2026-07-28-rc")]
pub type TraceContextProvider = std::sync::Arc<dyn Fn() -> Option<TraceContext> + Send + Sync>;
pub struct McpOptions {
pub(crate) implementation: Implementation,
pub(super) timeout: Duration,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(super) roots_capability: Option<RootsCapability>,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(super) sampling_capability: Option<SamplingCapability>,
pub(super) elicitation_capability: Option<ElicitationCapability>,
#[cfg(feature = "tasks")]
pub(super) tasks_capability: Option<ClientTasksCapability>,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(super) sampling_handler: Option<SamplingHandler>,
pub(super) elicitation_handler: Option<ElicitationHandler>,
pub(super) notification_handler: Option<Arc<NotificationsHandler>>,
protocol_ver: Option<&'static str>,
proto: Option<TransportProto>,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
roots: HashMap<Uri, Root>,
#[cfg(feature = "proto-2026-07-28-rc")]
pub(crate) trace_context_provider: Option<TraceContextProvider>,
#[cfg(feature = "proto-2026-07-28-rc")]
pub(crate) max_mrtr_rounds: usize,
}
impl Debug for McpOptions {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let mut binding = f.debug_struct("McpOptions");
let dbg = binding
.field("implementation", &self.implementation)
.field("timeout", &self.timeout)
.field("elicitation_capability", &self.elicitation_capability);
#[cfg(not(feature = "proto-2026-07-28-rc"))]
let dbg = dbg
.field("roots_capability", &self.roots_capability)
.field("sampling_capability", &self.sampling_capability);
let dbg = dbg.field("protocol_ver", &self.protocol_ver);
#[cfg(not(feature = "proto-2026-07-28-rc"))]
let dbg = dbg.field("roots", &self.roots);
#[cfg(feature = "tasks")]
dbg.field("tasks_capability", &self.tasks_capability);
dbg.finish()
}
}
impl Default for McpOptions {
#[inline]
fn default() -> Self {
Self {
timeout: Duration::from_secs(DEFAULT_REQUEST_TIMEOUT),
implementation: Default::default(),
#[cfg(not(feature = "proto-2026-07-28-rc"))]
roots: Default::default(),
#[cfg(not(feature = "proto-2026-07-28-rc"))]
roots_capability: None,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
sampling_capability: None,
elicitation_capability: None,
#[cfg(feature = "tasks")]
tasks_capability: None,
proto: None,
protocol_ver: None,
#[cfg(not(feature = "proto-2026-07-28-rc"))]
sampling_handler: None,
elicitation_handler: None,
notification_handler: None,
#[cfg(feature = "proto-2026-07-28-rc")]
trace_context_provider: None,
#[cfg(feature = "proto-2026-07-28-rc")]
max_mrtr_rounds: DEFAULT_MAX_MRTR_ROUNDS,
}
}
}
impl McpOptions {
pub fn with_stdio<T>(mut self, command: &'static str, args: T) -> Self
where
T: IntoIterator<Item = &'static str>,
{
self.proto = Some(TransportProto::StdioClient(StdIoClient::new(
StdIoOptions::new(command, args),
)));
self
}
#[cfg(feature = "http-client")]
pub fn with_http<F: FnOnce(HttpClient) -> HttpClient>(mut self, config: F) -> Self {
self.proto = Some(TransportProto::HttpClient(config(HttpClient::default())));
self
}
#[cfg(feature = "http-client")]
pub fn with_default_http(self) -> Self {
self.with_http(|http| http)
}
pub fn with_name(mut self, name: &str) -> Self {
self.implementation.name = name.into();
self
}
pub fn with_version(mut self, ver: &str) -> Self {
self.implementation.version = ver.into();
self
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub fn with_mcp_version(mut self, ver: &'static str) -> Self {
self.protocol_ver = Some(ver);
self
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
#[deprecated(
note = "Roots are removed in MCP 2026-07-28; this method will be removed when the legacy flag is dropped."
)]
pub fn with_roots<T>(mut self, config: T) -> Self
where
T: FnOnce(RootsCapability) -> RootsCapability,
{
self.roots_capability = Some(config(Default::default()));
self
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
#[deprecated(
note = "Sampling is removed in MCP 2026-07-28; this method will be removed when the legacy flag is dropped."
)]
pub fn with_sampling<T>(mut self, config: T) -> Self
where
T: FnOnce(SamplingCapability) -> SamplingCapability,
{
self.sampling_capability = Some(config(Default::default()));
self
}
pub fn with_elicitation<T>(mut self, config: T) -> Self
where
T: FnOnce(ElicitationCapability) -> ElicitationCapability,
{
self.elicitation_capability = Some(config(Default::default()));
self
}
#[cfg(feature = "tasks")]
pub fn with_tasks<T>(mut self, config: T) -> Self
where
T: FnOnce(ClientTasksCapability) -> ClientTasksCapability,
{
self.tasks_capability = Some(config(Default::default()));
self
}
pub fn with_timeout(mut self, timeout: Duration) -> Self {
self.timeout = timeout;
self
}
#[cfg(feature = "proto-2026-07-28-rc")]
pub fn with_max_mrtr_rounds(mut self, rounds: usize) -> Self {
self.max_mrtr_rounds = rounds;
self
}
#[cfg(feature = "proto-2026-07-28-rc")]
pub fn with_trace_context_provider<F>(mut self, f: F) -> Self
where
F: Fn() -> Option<TraceContext> + Send + Sync + 'static,
{
self.trace_context_provider = Some(std::sync::Arc::new(f));
self
}
#[inline]
pub(crate) fn protocol_ver(&self) -> &'static str {
match self.protocol_ver {
Some(ver) => ver,
None => PROTOCOL_VERSIONS.last().unwrap(),
}
}
pub(crate) fn transport(&mut self) -> TransportProto {
let transport = self.proto.take();
transport.unwrap_or_default()
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub fn add_root(&mut self, root: Root) -> &mut Root {
self.roots.entry(root.uri.clone()).or_insert(root)
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub fn add_roots<T, I>(&mut self, roots: I) -> &mut Self
where
T: Into<Root>,
I: IntoIterator<Item = T>,
{
let roots = roots.into_iter().map(|item| {
let root: Root = item.into();
(root.uri.clone(), root)
});
self.roots.extend(roots);
self
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub fn roots(&self) -> Vec<Root> {
self.roots.values().cloned().collect()
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(crate) fn add_sampling_handler(&mut self, handler: SamplingHandler) {
self.sampling_handler = Some(handler);
}
pub(crate) fn add_elicitation_handler(&mut self, handler: ElicitationHandler) {
self.elicitation_handler = Some(handler);
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(crate) fn roots_capability(&self) -> Option<RootsCapability> {
self.roots_capability
.clone()
.or_else(|| (!self.roots.is_empty()).then(Default::default))
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(crate) fn sampling_capability(&self) -> Option<SamplingCapability> {
self.sampling_capability
.clone()
.or_else(|| self.sampling_handler.is_none().then(Default::default))
}
#[cfg(not(feature = "proto-2026-07-28-rc"))]
pub(crate) fn elicitation_capability(&self) -> Option<ElicitationCapability> {
self.elicitation_capability
.clone()
.or_else(|| self.elicitation_handler.is_none().then(Default::default))
}
#[cfg(all(feature = "tasks", not(feature = "proto-2026-07-28-rc")))]
pub(crate) fn tasks_capability(&self) -> Option<ClientTasksCapability> {
self.tasks_capability.clone()
}
}
#[cfg(test)]
mod tests {
#[cfg(feature = "proto-2026-07-28-rc")]
use super::*;
#[test]
#[cfg(feature = "proto-2026-07-28-rc")]
fn trace_context_provider_can_be_installed() {
let opts = McpOptions::default().with_trace_context_provider(|| {
Some(TraceContext {
traceparent: "tp".into(),
tracestate: Some("ts".into()),
})
});
let tc = (opts.trace_context_provider.as_ref().unwrap())().unwrap();
assert_eq!(tc.traceparent, "tp");
assert_eq!(tc.tracestate.as_deref(), Some("ts"));
}
}