-
Notifications
You must be signed in to change notification settings - Fork 579
Expand file tree
/
Copy pathtask_stdio.rs
More file actions
124 lines (113 loc) · 4.37 KB
/
Copy pathtask_stdio.rs
File metadata and controls
124 lines (113 loc) · 4.37 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
//! Client for the task-demo server in `examples/servers/src/task_stdio.rs`.
//!
//! Walks through the SEP-2663 Tasks extension lifecycle:
//! 1. Call a regular tool (`quick_echo`) — synchronous response.
//! 2. Call `slow_sum` while declaring the `io.modelcontextprotocol/tasks`
//! extension capability. The server decides to materialize a task and
//! returns a `CreateTaskResult` (`resultType: "task"`).
//! 3. Poll `tasks/get` (honoring `pollIntervalMs`) until the task reaches a
//! terminal status; the final `CallToolResult` is inlined in the
//! `completed` task's `result` field.
use anyhow::{Result, anyhow};
use rmcp::{
ServiceExt,
model::{
CallToolRequestParams, CallToolResponse, CallToolResult, ClientCapabilities, GetTaskParams,
TaskPayload, TaskStatus,
},
object,
transport::{ConfigureCommandExt, TokioChildProcess},
};
use tokio::process::Command;
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
#[tokio::main]
async fn main() -> Result<()> {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| format!("info,{}=debug", env!("CARGO_CRATE_NAME")).into()),
)
.with(tracing_subscriber::fmt::layer())
.init();
// Declare the tasks extension in our client capabilities (SEP-2663).
let client_info = rmcp::model::ClientInfo::new(
ClientCapabilities::builder().enable_tasks().build(),
rmcp::model::Implementation::from_build_env(),
);
// Spawn the task-demo server as a child process over stdio.
let client = client_info
.serve(TokioChildProcess::new(Command::new("cargo").configure(
|cmd| {
cmd.arg("run")
.arg("-q")
.arg("-p")
.arg("mcp-server-examples")
.arg("--example")
.arg("servers_task_stdio");
},
))?)
.await?;
// 1) Synchronous call.
let echo = client
.call_tool(
CallToolRequestParams::new("quick_echo")
.with_arguments(object!({ "message": "hi from rmcp" })),
)
.await?;
tracing::info!("quick_echo -> {echo:#?}");
// 2) Task-eligible call. The server sees our tasks capability and
// materializes a task instead of blocking.
let response = client
.call_tool_once(
CallToolRequestParams::new("slow_sum").with_arguments(object!({ "a": 40, "b": 2 })),
)
.await?;
let create = match response {
CallToolResponse::Task(create) => create,
CallToolResponse::Complete(result) => {
// The server is allowed to answer synchronously.
tracing::info!("slow_sum answered synchronously -> {result:#?}");
client.cancel().await?;
return Ok(());
}
other => return Err(anyhow!("unexpected response: {other:?}")),
};
let task_id = create.task.task_id.clone();
let poll_ms = create.task.poll_interval_ms.unwrap_or(500);
tracing::info!(
"slow_sum materialized as task {task_id} (status = {:?})",
create.task.status
);
// 3) Poll `tasks/get` until the task reaches a terminal status.
let final_task = loop {
tokio::time::sleep(std::time::Duration::from_millis(poll_ms)).await;
let info = client
.peer()
.get_task(GetTaskParams::new(task_id.clone()))
.await?;
tracing::info!("status = {:?}", info.task.status());
if info.task.status().is_terminal() {
break info.task;
}
};
// The completed task carries the final CallToolResult inline.
match &final_task.payload {
TaskPayload::Completed { result } => {
let call_result: CallToolResult =
serde_json::from_value(serde_json::Value::Object(result.clone()))?;
tracing::info!("slow_sum result -> {call_result:#?}");
}
TaskPayload::Failed { error } => {
return Err(anyhow!("task failed: {error:?}"));
}
other => {
return Err(anyhow!(
"task ended in unexpected state {:?}",
other.status()
));
}
}
debug_assert_eq!(final_task.status(), TaskStatus::Completed);
client.cancel().await?;
Ok(())
}