forked from paiml/rust-mcp-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path11_progress_countdown.rs
More file actions
167 lines (137 loc) Β· 5.82 KB
/
11_progress_countdown.rs
File metadata and controls
167 lines (137 loc) Β· 5.82 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
//! Progress Reporting with Countdown Tool
//!
//! This example demonstrates progress reporting and cancellation with a simple
//! countdown tool that counts from N to 0, reporting progress at each step.
//!
//! Features demonstrated:
//! - Progress reporting with `extra.report_count()`
//! - Automatic progress token extraction from request metadata
//! - Rate limiting of progress notifications (max 10/second)
//! - Final progress notification always sent
//! - Request cancellation support
//!
//! Run with:
//! ```bash
//! cargo run --example 11_progress_countdown
//! ```
use async_trait::async_trait;
use pmcp::error::Result;
use pmcp::server::cancellation::RequestHandlerExtra;
use pmcp::server::{Server, ToolHandler};
use pmcp::types::{CallToolRequest, ProgressToken, RequestMeta};
use serde_json::{json, Value};
use std::time::Duration;
/// A countdown tool that reports progress at each step.
///
/// This tool demonstrates:
/// - Progress reporting with total value
/// - Cancellation handling
/// - Sleep between iterations to simulate work
struct CountdownTool;
#[async_trait]
impl ToolHandler for CountdownTool {
async fn handle(&self, args: Value, extra: RequestHandlerExtra) -> Result<Value> {
// Extract the starting number (default to 10)
let start = args.get("from").and_then(|v| v.as_u64()).unwrap_or(10) as usize;
tracing::info!("Starting countdown from {}", start);
// Count down from start to 0
for i in (0..=start).rev() {
// Check for cancellation
if extra.is_cancelled() {
tracing::warn!("Countdown cancelled at {}", i);
return Err(pmcp::error::Error::internal(
"Countdown cancelled by client",
));
}
// Report progress: current position in countdown
// We're counting DOWN, so progress goes UP
let current = start - i;
let message = if i == 0 {
"Countdown complete! π".to_string()
} else {
format!("Counting down: {}", i)
};
extra
.report_count(current, start, Some(message.clone()))
.await?;
tracing::info!("Countdown: {} (progress: {}/{})", i, current, start);
// Sleep for 1 second between counts (except at the end)
if i > 0 {
tokio::time::sleep(Duration::from_secs(1)).await;
}
}
Ok(json!({
"result": "Countdown completed successfully",
"from": start,
}))
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Initialize tracing with timestamps
tracing_subscriber::fmt()
.with_target(false)
.with_level(true)
.init();
println!("=== Progress Reporting: Countdown Tool Example ===\n");
// Create server with countdown tool
let _server = Server::builder()
.name("countdown-server")
.version("1.0.0")
.tool("countdown", CountdownTool)
.build()?;
println!("Server created with 'countdown' tool");
println!("Tool schema:");
println!(" countdown(from: number) - Counts down from 'from' to 0, reporting progress\n");
// Simulate client request with progress token
println!("--- Example 1: Countdown from 5 with progress tracking ---\n");
let mut request = CallToolRequest::new("countdown", json!({ "from": 5 }));
request._meta = Some(
RequestMeta::new().with_progress_token(ProgressToken::String("countdown-1".to_string())),
);
println!("Calling countdown tool with progress token 'countdown-1'...\n");
// In a real scenario, this would go through the server's request handling
// For this example, we'll directly call the tool to demonstrate progress
let tool = CountdownTool;
let extra = RequestHandlerExtra::new(
"test-request-1".to_string(),
tokio_util::sync::CancellationToken::new(),
);
// Note: In a real server, progress reporter would be automatically created
// from the request's _meta.progress_token field
let result = tool.handle(request.arguments, extra).await?;
println!("\nβ
Countdown completed!");
println!("Result: {}\n", serde_json::to_string_pretty(&result)?);
// Demonstrate cancellation
println!("--- Example 2: Countdown with cancellation ---\n");
let mut request = CallToolRequest::new("countdown", json!({ "from": 10 }));
request._meta = Some(
RequestMeta::new().with_progress_token(ProgressToken::String("countdown-2".to_string())),
);
println!("Calling countdown from 10 with cancellation after 3 seconds...\n");
let cancellation_token = tokio_util::sync::CancellationToken::new();
let extra = RequestHandlerExtra::new("test-request-2".to_string(), cancellation_token.clone());
// Cancel after 3 seconds
let cancel_handle = tokio::spawn({
let token = cancellation_token.clone();
async move {
tokio::time::sleep(Duration::from_secs(3)).await;
println!("\nπ Cancelling countdown...\n");
token.cancel();
}
});
let result = tool.handle(request.arguments, extra).await;
match result {
Ok(v) => println!("Unexpected success: {}", v),
Err(e) => println!("β Countdown cancelled as expected: {}\n", e),
}
cancel_handle.await.unwrap();
println!("--- Key Features Demonstrated ---\n");
println!("1. β
Progress reporting with extra.report_count(current, total, message)");
println!("2. β
Progress token extracted from request _meta field");
println!("3. β
Rate limiting prevents notification flooding (max 10/sec)");
println!("4. β
Final notification always sent (bypasses rate limiting)");
println!("5. β
Cancellation support with extra.is_cancelled()");
println!("\n=== Example Complete ===");
Ok(())
}