forked from paiml/rust-mcp-sdk
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy path12_prompt_workflow_progress.rs
More file actions
219 lines (184 loc) · 7.57 KB
/
12_prompt_workflow_progress.rs
File metadata and controls
219 lines (184 loc) · 7.57 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
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
//! Prompt Workflow with Progress Reporting
//!
//! This example demonstrates progress reporting in long-running prompt workflows.
//! A multi-step analysis prompt reports progress at each workflow stage.
//!
//! Features demonstrated:
//! - Progress reporting in prompts (not just tools)
//! - Multi-step workflow with clear progress tracking
//! - Cancellation support during workflow execution
//! - Best practice pattern for workflow prompts
//!
//! Run with:
//! ```bash
//! cargo run --example 12_prompt_workflow_progress
//! ```
use async_trait::async_trait;
use pmcp::error::Result;
use pmcp::server::cancellation::RequestHandlerExtra;
use pmcp::server::{PromptHandler, Server};
use pmcp::types::{
Content, GetPromptRequest, GetPromptResult, ProgressToken, PromptMessage, RequestMeta,
};
use std::collections::HashMap;
use std::time::Duration;
/// A workflow prompt that performs multi-step analysis with progress reporting.
///
/// This demonstrates the recommended pattern for long-running prompts:
/// - Break workflow into clear steps
/// - Report progress at each step
/// - Support cancellation between steps
/// - Provide meaningful status messages
struct AnalysisWorkflowPrompt;
#[async_trait]
impl PromptHandler for AnalysisWorkflowPrompt {
async fn handle(
&self,
args: HashMap<String, String>,
extra: RequestHandlerExtra,
) -> Result<GetPromptResult> {
let topic = args
.get("topic")
.cloned()
.unwrap_or_else(|| "general analysis".to_string());
tracing::info!("Starting analysis workflow for topic: {}", topic);
// Define workflow steps
let steps = [
("gather", "Gathering information and context"),
("analyze", "Analyzing data and patterns"),
("synthesize", "Synthesizing insights"),
("validate", "Validating conclusions"),
("format", "Formatting final report"),
];
let mut results = Vec::new();
// Execute each step with progress reporting
for (i, (step_name, step_description)) in steps.iter().enumerate() {
// Check for cancellation before each step
if extra.is_cancelled() {
tracing::warn!("Workflow cancelled at step: {}", step_name);
return Err(pmcp::error::Error::internal(format!(
"Analysis workflow cancelled during {} step",
step_name
)));
}
// Report progress for this step
extra
.report_count(
i + 1,
steps.len(),
Some(format!(
"Step {}/{}: {}",
i + 1,
steps.len(),
step_description
)),
)
.await?;
tracing::info!(
"Executing step {}/{}: {} - {}",
i + 1,
steps.len(),
step_name,
step_description
);
// Simulate work for this step
tokio::time::sleep(Duration::from_secs(1)).await;
// Record step completion
results.push(format!(
"✓ {} - {} (completed)",
step_name, step_description
));
}
// Build the final prompt result
let workflow_summary = results.join("\n");
Ok(GetPromptResult::new(
vec![PromptMessage::user(Content::text(format!(
"Analysis Workflow Complete\n\nTopic: {}\n\nWorkflow Steps:\n{}\n\nAll {} steps completed successfully. Ready for review.",
topic, workflow_summary, steps.len()
)))],
Some(format!(
"Multi-step analysis workflow for: {}",
topic
)),
))
}
}
#[tokio::main]
async fn main() -> Result<()> {
// Initialize tracing
tracing_subscriber::fmt()
.with_target(false)
.with_level(true)
.init();
println!("=== Prompt Workflow Progress Example ===\n");
// Create server with workflow prompt
let _server = Server::builder()
.name("workflow-server")
.version("1.0.0")
.prompt("analysis_workflow", AnalysisWorkflowPrompt)
.build()?;
println!("Server created with 'analysis_workflow' prompt");
println!("Prompt arguments:");
println!(" topic (optional) - Topic to analyze (default: 'general analysis')\n");
// Example 1: Normal workflow execution
println!("--- Example 1: Complete workflow with progress tracking ---\n");
let request = GetPromptRequest {
name: "analysis_workflow".to_string(),
arguments: HashMap::from([("topic".to_string(), "Machine Learning".to_string())]),
_meta: Some(
RequestMeta::new().with_progress_token(ProgressToken::String("workflow-1".to_string())),
),
};
println!("Executing workflow with progress token 'workflow-1'...\n");
let prompt = AnalysisWorkflowPrompt;
let extra = RequestHandlerExtra::new(
"test-request-1".to_string(),
tokio_util::sync::CancellationToken::new(),
);
let result = prompt.handle(request.arguments, extra).await?;
println!("\n✅ Workflow completed!");
println!("Description: {}", result.description.unwrap_or_default());
println!("\nMessages generated: {}", result.messages.len());
// Example 2: Workflow with cancellation
println!("\n--- Example 2: Workflow with mid-execution cancellation ---\n");
let request = GetPromptRequest {
name: "analysis_workflow".to_string(),
arguments: HashMap::from([("topic".to_string(), "Data Science".to_string())]),
_meta: Some(
RequestMeta::new().with_progress_token(ProgressToken::String("workflow-2".to_string())),
),
};
println!("Executing workflow with cancellation after 2.5 seconds...\n");
let cancellation_token = tokio_util::sync::CancellationToken::new();
let extra = RequestHandlerExtra::new("test-request-2".to_string(), cancellation_token.clone());
// Cancel after 2.5 seconds (should interrupt at step 3)
let cancel_handle = tokio::spawn({
let token = cancellation_token.clone();
async move {
tokio::time::sleep(Duration::from_millis(2500)).await;
println!("\n🛑 Cancelling workflow...\n");
token.cancel();
}
});
let result = prompt.handle(request.arguments, extra).await;
match result {
Ok(v) => println!("Unexpected success: {}", v.description.unwrap_or_default()),
Err(e) => println!("❌ Workflow cancelled as expected: {}\n", e),
}
cancel_handle.await.unwrap();
println!("--- Best Practices Demonstrated ---\n");
println!("1. ✅ Multi-step workflow with clear progress tracking");
println!("2. ✅ Progress reported at each workflow step");
println!("3. ✅ Meaningful status messages for each step");
println!("4. ✅ Cancellation checked between steps");
println!("5. ✅ Same API as tools - extra.report_count()");
println!("6. ✅ Automatic progress notification handling\n");
println!("--- When to Use Progress in Prompts ---\n");
println!("✓ Multi-step workflows (analysis → planning → execution)");
println!("✓ Long-running data processing or generation");
println!("✓ Prompts with multiple external API calls");
println!("✓ Complex reasoning chains with distinct phases");
println!("✗ Simple single-step prompts (no progress needed)\n");
println!("=== Example Complete ===");
Ok(())
}