use mcpkit_core::capability::{ClientCapabilities, ServerCapabilities};
use mcpkit_core::error::McpError;
use mcpkit_core::protocol::{Notification, ProgressToken, RequestId};
use mcpkit_core::protocol_version::ProtocolVersion;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::task::{Context as TaskContext, Poll};
use event_listener::Event;
pub trait Peer: Send + Sync {
fn notify(
&self,
notification: Notification,
) -> Pin<Box<dyn Future<Output = Result<(), McpError>> + Send + '_>>;
}
#[derive(Clone)]
pub struct CancellationToken {
cancelled: Arc<AtomicBool>,
event: Arc<Event>,
}
impl CancellationToken {
#[must_use]
pub fn new() -> Self {
Self {
cancelled: Arc::new(AtomicBool::new(false)),
event: Arc::new(Event::new()),
}
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::SeqCst)
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::SeqCst);
self.event.notify(usize::MAX);
}
#[must_use]
pub fn cancelled(&self) -> CancelledFuture {
CancelledFuture::new(self.cancelled.clone(), self.event.clone())
}
}
impl std::fmt::Debug for CancellationToken {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("CancellationToken")
.field("cancelled", &self.is_cancelled())
.finish()
}
}
impl Default for CancellationToken {
fn default() -> Self {
Self::new()
}
}
pub struct CancelledFuture {
inner: Pin<Box<dyn Future<Output = ()> + Send>>,
}
impl CancelledFuture {
fn new(cancelled: Arc<AtomicBool>, event: Arc<Event>) -> Self {
Self {
inner: Box::pin(async move {
loop {
if cancelled.load(Ordering::SeqCst) {
return;
}
let listener = event.listen();
if cancelled.load(Ordering::SeqCst) {
return;
}
listener.await;
}
}),
}
}
}
impl Future for CancelledFuture {
type Output = ();
fn poll(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Self::Output> {
self.inner.as_mut().poll(cx)
}
}
pub struct Context<'a> {
pub request_id: &'a RequestId,
pub progress_token: Option<&'a ProgressToken>,
pub client_caps: &'a ClientCapabilities,
pub server_caps: &'a ServerCapabilities,
pub protocol_version: ProtocolVersion,
peer: &'a dyn Peer,
cancel: CancellationToken,
}
impl<'a> Context<'a> {
#[must_use]
pub fn new(
request_id: &'a RequestId,
progress_token: Option<&'a ProgressToken>,
client_caps: &'a ClientCapabilities,
server_caps: &'a ServerCapabilities,
protocol_version: ProtocolVersion,
peer: &'a dyn Peer,
) -> Self {
Self {
request_id,
progress_token,
client_caps,
server_caps,
protocol_version,
peer,
cancel: CancellationToken::new(),
}
}
#[must_use]
pub fn with_cancellation(
request_id: &'a RequestId,
progress_token: Option<&'a ProgressToken>,
client_caps: &'a ClientCapabilities,
server_caps: &'a ServerCapabilities,
protocol_version: ProtocolVersion,
peer: &'a dyn Peer,
cancel: CancellationToken,
) -> Self {
Self {
request_id,
progress_token,
client_caps,
server_caps,
protocol_version,
peer,
cancel,
}
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancel.is_cancelled()
}
pub fn cancelled(&self) -> impl Future<Output = ()> + '_ {
self.cancel.cancelled()
}
#[must_use]
pub const fn cancellation_token(&self) -> &CancellationToken {
&self.cancel
}
pub async fn notify(
&self,
method: &str,
params: Option<serde_json::Value>,
) -> Result<(), McpError> {
let notification = if let Some(p) = params {
Notification::with_params(method.to_string(), p)
} else {
Notification::new(method.to_string())
};
self.peer.notify(notification).await
}
pub async fn progress(
&self,
current: u64,
total: Option<u64>,
message: Option<&str>,
) -> Result<(), McpError> {
let Some(token) = self.progress_token else {
return Ok(());
};
let params = serde_json::json!({
"progressToken": token,
"progress": current,
"total": total,
"message": message,
});
self.notify("notifications/progress", Some(params)).await
}
}
impl std::fmt::Debug for Context<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Context")
.field("request_id", &self.request_id)
.field("progress_token", &self.progress_token)
.field("client_caps", &self.client_caps)
.field("server_caps", &self.server_caps)
.field("protocol_version", &self.protocol_version)
.field("is_cancelled", &self.is_cancelled())
.finish()
}
}
#[derive(Debug, Clone, Copy)]
pub struct NoOpPeer;
impl Peer for NoOpPeer {
fn notify(
&self,
_notification: Notification,
) -> Pin<Box<dyn Future<Output = Result<(), McpError>> + Send + '_>> {
Box::pin(async { Ok(()) })
}
}
pub struct ContextData {
pub request_id: RequestId,
pub progress_token: Option<ProgressToken>,
pub client_caps: ClientCapabilities,
pub server_caps: ServerCapabilities,
pub protocol_version: ProtocolVersion,
}
impl ContextData {
#[must_use]
pub const fn new(
request_id: RequestId,
client_caps: ClientCapabilities,
server_caps: ServerCapabilities,
protocol_version: ProtocolVersion,
) -> Self {
Self {
request_id,
progress_token: None,
client_caps,
server_caps,
protocol_version,
}
}
#[must_use]
pub fn with_progress_token(mut self, token: ProgressToken) -> Self {
self.progress_token = Some(token);
self
}
#[must_use]
pub fn to_context<'a>(&'a self, peer: &'a dyn Peer) -> Context<'a> {
Context::new(
&self.request_id,
self.progress_token.as_ref(),
&self.client_caps,
&self.server_caps,
self.protocol_version,
peer,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cancellation_token() {
let token = CancellationToken::new();
assert!(!token.is_cancelled());
token.cancel();
assert!(token.is_cancelled());
}
#[test]
fn cancelled_future_parks_and_wakes_on_cancel() {
use std::sync::atomic::AtomicUsize;
use std::task::{Wake, Waker};
struct CountingWaker(AtomicUsize);
impl Wake for CountingWaker {
fn wake(self: Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
fn wake_by_ref(self: &Arc<Self>) {
self.0.fetch_add(1, Ordering::SeqCst);
}
}
let counter = Arc::new(CountingWaker(AtomicUsize::new(0)));
let waker = Waker::from(counter.clone());
let mut cx = TaskContext::from_waker(&waker);
let token = CancellationToken::new();
let mut fut = Box::pin(token.cancelled());
assert_eq!(fut.as_mut().poll(&mut cx), Poll::Pending);
assert_eq!(
counter.0.load(Ordering::SeqCst),
0,
"cancelled future must park, not busy-spin (no self-wake)"
);
token.cancel();
assert!(
counter.0.load(Ordering::SeqCst) >= 1,
"cancel() must wake the parked waiter"
);
assert_eq!(fut.as_mut().poll(&mut cx), Poll::Ready(()));
}
#[test]
fn cancelled_future_ready_when_already_cancelled() {
let waker = std::task::Waker::noop();
let mut cx = TaskContext::from_waker(waker);
let token = CancellationToken::new();
token.cancel();
let mut fut = Box::pin(token.cancelled());
assert_eq!(fut.as_mut().poll(&mut cx), Poll::Ready(()));
}
#[test]
fn test_context_creation() {
let request_id = RequestId::Number(1);
let client_caps = ClientCapabilities::default();
let server_caps = ServerCapabilities::default();
let peer = NoOpPeer;
let ctx = Context::new(
&request_id,
None,
&client_caps,
&server_caps,
ProtocolVersion::LATEST,
&peer,
);
assert!(!ctx.is_cancelled());
assert!(ctx.progress_token.is_none());
assert_eq!(ctx.protocol_version, ProtocolVersion::LATEST);
}
#[test]
fn test_context_with_progress_token() {
let request_id = RequestId::Number(1);
let progress_token = ProgressToken::String("token".to_string());
let client_caps = ClientCapabilities::default();
let server_caps = ServerCapabilities::default();
let peer = NoOpPeer;
let ctx = Context::new(
&request_id,
Some(&progress_token),
&client_caps,
&server_caps,
ProtocolVersion::V2025_03_26,
&peer,
);
assert!(ctx.progress_token.is_some());
assert_eq!(ctx.protocol_version, ProtocolVersion::V2025_03_26);
}
#[test]
fn test_context_data() {
let data = ContextData::new(
RequestId::Number(42),
ClientCapabilities::default(),
ServerCapabilities::default(),
ProtocolVersion::V2025_06_18,
)
.with_progress_token(ProgressToken::String("test".to_string()));
let peer = NoOpPeer;
let ctx = data.to_context(&peer);
assert!(ctx.progress_token.is_some());
assert_eq!(ctx.protocol_version, ProtocolVersion::V2025_06_18);
assert!(ctx.protocol_version.supports_elicitation());
assert!(!ctx.protocol_version.supports_tasks()); }
}