use async_trait::async_trait;
use pmcp::server::workflow::dsl::field;
use pmcp::server::workflow::{SequentialWorkflow, ToolHandle, WorkflowStep};
use pmcp::{
Content, ListResourcesResult, ReadResourceResult, RequestHandlerExtra, ResourceHandler,
ResourceInfo, Result, Server, SimpleTool,
};
use serde_json::json;
struct WalkthroughResources;
#[async_trait]
impl ResourceHandler for WalkthroughResources {
async fn read(&self, uri: &str, _extra: RequestHandlerExtra) -> Result<ReadResourceResult> {
match uri {
"if://walkthrough/zork1" => Ok(ReadResourceResult::new(vec![Content::text(
r#"
# Zork I Walkthrough
## West of House
You are standing in an open field west of a white house, with a boarded front door.
### Hints:
1. Go around to the back of the house (type: "east")
2. There's a window you can open
3. Enter through the window to start your adventure
4. Don't forget to pick up useful items!
## Scoring:
- Opening the mailbox: +5 points
- Entering the house: +10 points
- Finding the lamp: +10 points
"#,
)])),
"if://walkthrough/planetfall" => Ok(ReadResourceResult::new(vec![Content::text(
"# Planetfall Walkthrough\n\nYour adventure in space...",
)])),
_ => Err(pmcp::Error::validation(format!(
"Unknown resource: {}",
uri
))),
}
}
async fn list(
&self,
_cursor: Option<String>,
_extra: RequestHandlerExtra,
) -> Result<ListResourcesResult> {
Ok(ListResourcesResult::new(vec![
ResourceInfo::new("if://walkthrough/zork1", "Zork I Walkthrough")
.with_description("Complete walkthrough for Zork I")
.with_mime_type("text/markdown"),
ResourceInfo::new("if://walkthrough/planetfall", "Planetfall Walkthrough")
.with_description("Complete walkthrough for Planetfall")
.with_mime_type("text/markdown"),
]))
}
}
#[tokio::main]
async fn main() -> Result<()> {
println!("🎮 Interactive Fiction Server - Dynamic Resource Example\n");
let server = Server::builder()
.name("interactive-fiction")
.version("1.0.0")
.tool(
"get_my_progress",
SimpleTool::new("get_my_progress", |_args, _extra| {
Box::pin(async move {
Ok(json!({
"game_id": "zork1",
"location": "West of House",
"moves": 42,
"score": 15
}))
})
})
.with_description("Get your current game progress"),
)
.resources(WalkthroughResources)
.prompt_workflow(
SequentialWorkflow::new("get_hint", "Get a helpful hint for your current game")
.argument("hint_level", "How detailed should the hint be? (subtle, clear, explicit)", false)
.step(
WorkflowStep::new("get_progress", ToolHandle::new("get_my_progress"))
.with_guidance(
"I'll first check which game you're currently playing and where you are..."
)
.bind("user_progress"),
)
.step(
WorkflowStep::fetch_resources("fetch_walkthrough")
.with_resource("if://walkthrough/{game_id}")
.expect("Valid resource URI")
.with_template_binding("game_id", field("user_progress", "game_id"))
.with_guidance(
"Now I'll fetch the walkthrough guide specifically for your current game..."
),
),
)?
.build()?;
println!("Server capabilities:");
println!(" ✓ Tool: get_my_progress");
println!(" ✓ Resources: if://walkthrough/zork1, if://walkthrough/planetfall");
println!(" ✓ Workflow Prompt: get_hint (with dynamic resource fetching)\n");
println!("📋 Testing the Dynamic Resource Workflow:\n");
println!("Calling prompt: get_hint");
println!("Arguments: hint_level=clear\n");
let mut args = std::collections::HashMap::new();
args.insert("hint_level".to_string(), "clear".to_string());
let extra = RequestHandlerExtra::new("demo-request".to_string(), Default::default());
let prompt_handler = server
.get_prompt("get_hint")
.expect("Workflow should be registered");
match prompt_handler.handle(args, extra).await {
Ok(result) => {
println!("✅ Workflow executed successfully!\n");
println!("Messages returned:");
println!("{}", "─".repeat(80));
for (i, message) in result.messages.iter().enumerate() {
println!("\n[Message {}] Role: {:?}", i + 1, message.role);
match &message.content {
Content::Text { text } => {
let display_text = if text.len() > 300 {
format!(
"{}...\n[truncated {} chars]",
&text[..300],
text.len() - 300
)
} else {
text.clone()
};
println!("{}", display_text);
},
Content::Image { .. } => {
println!("[Image content]");
},
Content::Resource { .. } => {
println!("[Resource content]");
},
_ => {
println!("[Other content]");
},
}
}
println!("{}", "─".repeat(80));
println!("\n✨ Key Observation:");
println!(" The workflow automatically:");
println!(" 1. Called get_my_progress tool");
println!(" 2. Extracted game_id='zork1' from the result");
println!(" 3. Fetched resource at if://walkthrough/zork1 (interpolated!)");
println!(" 4. Embedded the walkthrough content in the conversation");
},
Err(e) => {
eprintln!("❌ Error executing workflow: {}", e);
return Err(e);
},
}
println!("\n🎓 Pattern Summary:");
println!(" This demonstrates the BEFORE/AFTER difference:\n");
println!(" BEFORE (manual implementation):");
println!(" → 100+ lines of custom PromptHandler code");
println!(" → Manual tool calling, resource fetching, error handling");
println!(" → Code duplication across similar prompts\n");
println!(" AFTER (workflow with dynamic resources):");
println!(" → 10 lines of declarative workflow DSL");
println!(" → Automatic resource URI interpolation");
println!(" → Reusable pattern for similar use cases\n");
println!("📚 Template Binding Patterns:\n");
println!(" Pattern 1: Field from previous step");
println!(" .with_template_binding(\"game_id\", field(\"user_progress\", \"game_id\"))\n");
println!(" Pattern 2: Prompt argument");
println!(" .with_template_binding(\"doc_id\", prompt_arg(\"document_id\"))\n");
println!(" Pattern 3: Multiple variables");
println!(" .with_template_binding(\"org\", field(\"project\", \"organization\"))");
println!(" .with_template_binding(\"repo\", field(\"project\", \"repository\"))\n");
println!(" Pattern 4: Nested field access (dot notation)");
println!(" .with_template_binding(\"user_id\", field(\"context\", \"user.profile.id\"))\n");
Ok(())
}