rust-sdk/examples/servers/src/common/progress_demo.rs
Teddy Tennant 3075dc9152
fix(model): deprecate ServerInfo and ClientInfo aliases (#1156)
* fix(model): deprecate ServerInfo and ClientInfo aliases

Those names collide with the protocol's serverInfo/clientInfo fields,
which are only Implementation identity. Prefer InitializeResult and
InitializeRequestParams. Handler trait signatures, the tool_handler
macro output, tests, examples and docs now use the canonical types; the
aliases remain as deprecated shims for the rest of 3.x.

Fixes #1082

* fix(model): keep public signatures on the deprecated aliases

The public API check compares rendered signatures, and rustdoc records a
type alias by the name it was written with. Respelling
`ServerHandler::get_info`, `ClientHandler::get_info`,
`DiscoverResult::from_server_info` and `impl ClientHandler for ClientInfo`
onto `InitializeResult`/`InitializeRequestParams` therefore reads as
changed public items even though the aliases resolve to those exact types
and nothing downstream breaks.

Put those four signatures back on the aliases so the published API is
byte-identical, and note why at the alias definitions. The deprecation
still steers new code, and the macro output, tests, examples and docs
keep using the canonical names.

* fix(model): migrate leftover ClientInfo test sites

Two tests landed on main after the alias deprecation and still named
ClientInfo. Clippy -D warnings and the no-local feature test job both
failed on them.
2026-09-12 12:11:30 -04:00

144 lines
4.3 KiB
Rust

use std::{
io,
pin::Pin,
task::{Context, Poll},
};
use futures::Stream;
use rmcp::{
ErrorData as McpError, RoleServer, ServerHandler, model::*, service::RequestContext, tool,
tool_handler, tool_router,
};
use serde_json::json;
use tokio_stream::StreamExt;
use tracing::{debug, info};
// a Stream data source that generates data in chunks
#[derive(Clone)]
struct StreamDataSource {
data: Vec<u8>,
chunk_size: usize,
position: usize,
}
impl StreamDataSource {
pub fn new(data: Vec<u8>, chunk_size: usize) -> Self {
Self {
data,
chunk_size,
position: 0,
}
}
pub fn from_text(text: &str) -> Self {
Self::new(text.as_bytes().to_vec(), 5)
}
}
impl Stream for StreamDataSource {
type Item = Result<Vec<u8>, io::Error>;
fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
if this.position >= this.data.len() {
return Poll::Ready(None);
}
let start = this.position;
let end = (start + this.chunk_size).min(this.data.len());
let chunk = this.data[start..end].to_vec();
this.position = end;
Poll::Ready(Some(Ok(chunk)))
}
}
#[derive(Clone)]
pub struct ProgressDemo {
data_source: StreamDataSource,
}
#[tool_router]
impl ProgressDemo {
#[allow(dead_code)]
pub fn new() -> Self {
Self {
data_source: StreamDataSource::from_text("1111122222333334444455555"),
}
}
#[tool(description = "Process data stream with progress updates")]
async fn stream_processor(
&self,
ctx: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError> {
let mut counter = 0;
info!(
"Processing stream with progress token {:?}",
ctx.meta.get_key_value("progressToken")
);
let Some((_, progress_token)) = ctx.meta.get_key_value("progressToken") else {
return Err(McpError::internal_error("No progress token", None));
};
let Ok(progress_token) = serde_json::from_value::<NumberOrString>(progress_token.clone())
else {
return Err(McpError::internal_error(
"Invalid format of the progress token",
None,
));
};
let mut data_source = self.data_source.clone();
loop {
let chunk = data_source.next().await;
if chunk.is_none() {
break;
}
let chunk = chunk.unwrap().unwrap();
let chunk_str = String::from_utf8_lossy(&chunk);
counter += 1;
// create progress notification param
let progress_param = ProgressNotificationParam::new(
ProgressToken(progress_token.clone()),
counter as f64,
)
.with_total(5.0)
.with_message(chunk_str.to_string());
match ctx.peer.notify_progress(progress_param).await {
Ok(_) => {
debug!("Processed record: {}", chunk_str);
}
Err(e) => {
return Err(McpError::internal_error(
format!("Failed to notify progress: {}", e),
Some(json!({
"record": chunk_str,
"progress": counter,
"error": e.to_string()
})),
));
}
}
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
Ok(CallToolResult::success(vec![ContentBlock::text(format!(
"Processed {} records successfully",
counter
))]))
}
}
#[tool_handler]
impl ServerHandler for ProgressDemo {
fn get_info(&self) -> InitializeResult {
InitializeResult::new(ServerCapabilities::builder().enable_tools().build())
.with_protocol_version(ProtocolVersion::V_2024_11_05)
.with_server_info(Implementation::from_build_env())
.with_instructions(
"This server demonstrates progress notifications during long-running operations. \
Use the tools to see real-time progress updates for batch processing"
.to_string(),
)
}
}