fix: conformance syntax changes (#723)
This commit is contained in:
parent
a8ea0f49b1
commit
770937a9fe
2 changed files with 176 additions and 319 deletions
|
|
@ -5,8 +5,7 @@ use rmcp::{
|
||||||
model::*,
|
model::*,
|
||||||
service::RequestContext,
|
service::RequestContext,
|
||||||
transport::{
|
transport::{
|
||||||
AuthClient, AuthorizationManager, StreamableHttpClientTransport,
|
AuthClient, AuthorizationManager, StreamableHttpClientTransport, auth::OAuthState,
|
||||||
auth::{OAuthClientConfig, OAuthState},
|
|
||||||
streamable_http_client::StreamableHttpClientTransportConfig,
|
streamable_http_client::StreamableHttpClientTransportConfig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -17,9 +16,6 @@ use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||||
|
|
||||||
#[derive(Debug, Default, serde::Deserialize)]
|
#[derive(Debug, Default, serde::Deserialize)]
|
||||||
struct ConformanceContext {
|
struct ConformanceContext {
|
||||||
#[serde(default)]
|
|
||||||
name: Option<String>,
|
|
||||||
// pre-registration / client-credentials-basic
|
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
client_id: Option<String>,
|
client_id: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
|
|
@ -29,15 +25,6 @@ struct ConformanceContext {
|
||||||
private_key_pem: Option<String>,
|
private_key_pem: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
signing_algorithm: Option<String>,
|
signing_algorithm: Option<String>,
|
||||||
// cross-app-access
|
|
||||||
#[serde(default)]
|
|
||||||
idp_client_id: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
idp_id_token: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
idp_issuer: Option<String>,
|
|
||||||
#[serde(default)]
|
|
||||||
idp_token_endpoint: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_context() -> ConformanceContext {
|
fn load_context() -> ConformanceContext {
|
||||||
|
|
@ -175,17 +162,17 @@ impl ClientHandler for FullClientHandler {
|
||||||
.and_then(|c| c.as_text())
|
.and_then(|c| c.as_text())
|
||||||
.map(|t| t.text.clone())
|
.map(|t| t.text.clone())
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
Ok(CreateMessageResult {
|
Ok(CreateMessageResult::new(
|
||||||
message: SamplingMessage::new(
|
SamplingMessage::new(
|
||||||
Role::Assistant,
|
Role::Assistant,
|
||||||
SamplingMessageContent::text(format!(
|
SamplingMessageContent::text(format!(
|
||||||
"This is a mock LLM response to: {}",
|
"This is a mock LLM response to: {}",
|
||||||
prompt_text
|
prompt_text
|
||||||
)),
|
)),
|
||||||
),
|
),
|
||||||
model: "mock-model".into(),
|
"mock-model".into(),
|
||||||
stop_reason: Some("endTurn".into()),
|
)
|
||||||
})
|
.with_stop_reason("endTurn"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -216,7 +203,7 @@ const REDIRECT_URI: &str = "http://localhost:3000/callback";
|
||||||
/// 4. Return an `AuthClient` wrapping `reqwest::Client`
|
/// 4. Return an `AuthClient` wrapping `reqwest::Client`
|
||||||
async fn perform_oauth_flow(
|
async fn perform_oauth_flow(
|
||||||
server_url: &str,
|
server_url: &str,
|
||||||
ctx: &ConformanceContext,
|
_ctx: &ConformanceContext,
|
||||||
) -> anyhow::Result<AuthClient<reqwest::Client>> {
|
) -> anyhow::Result<AuthClient<reqwest::Client>> {
|
||||||
let mut oauth = OAuthState::new(server_url, None).await?;
|
let mut oauth = OAuthState::new(server_url, None).await?;
|
||||||
|
|
||||||
|
|
@ -335,12 +322,7 @@ async fn run_auth_client(server_url: &str, ctx: &ConformanceContext) -> anyhow::
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -352,7 +334,7 @@ async fn run_auth_client(server_url: &str, ctx: &ConformanceContext) -> anyhow::
|
||||||
/// then call tool which triggers 403 → re-auth with expanded scopes → retry.
|
/// then call tool which triggers 403 → re-auth with expanded scopes → retry.
|
||||||
async fn run_auth_scope_step_up_client(
|
async fn run_auth_scope_step_up_client(
|
||||||
server_url: &str,
|
server_url: &str,
|
||||||
ctx: &ConformanceContext,
|
_ctx: &ConformanceContext,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
// First auth
|
// First auth
|
||||||
let mut oauth = OAuthState::new(server_url, None).await?;
|
let mut oauth = OAuthState::new(server_url, None).await?;
|
||||||
|
|
@ -388,12 +370,7 @@ async fn run_auth_scope_step_up_client(
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
match client
|
match client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args.clone()))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args.clone(),
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {
|
Ok(_) => {
|
||||||
|
|
@ -428,12 +405,7 @@ async fn run_auth_scope_step_up_client(
|
||||||
);
|
);
|
||||||
let client2 = BasicClientHandler.serve(transport2).await?;
|
let client2 = BasicClientHandler.serve(transport2).await?;
|
||||||
let _ = client2
|
let _ = client2
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
client2.cancel().await.ok();
|
client2.cancel().await.ok();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
|
|
@ -481,12 +453,7 @@ async fn run_auth_scope_retry_limit_client(
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
match client
|
match client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
|
|
@ -539,12 +506,7 @@ async fn run_auth_preregistered_client(
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
@ -597,12 +559,7 @@ async fn run_client_credentials_basic(
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
@ -667,12 +624,7 @@ async fn run_client_credentials_jwt(
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
@ -783,6 +735,18 @@ async fn headless_authorize(auth_url: &str) -> anyhow::Result<(String, String)>
|
||||||
Ok((code, state))
|
Ok((code, state))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a `CallToolRequestParams` for a tool, optionally with arguments.
|
||||||
|
fn call_tool_params(
|
||||||
|
name: std::borrow::Cow<'static, str>,
|
||||||
|
arguments: Option<serde_json::Map<String, Value>>,
|
||||||
|
) -> CallToolRequestParams {
|
||||||
|
let mut p = CallToolRequestParams::new(name);
|
||||||
|
if let Some(a) = arguments {
|
||||||
|
p = p.with_arguments(a);
|
||||||
|
}
|
||||||
|
p
|
||||||
|
}
|
||||||
|
|
||||||
/// Build arguments for a tool based on its input schema.
|
/// Build arguments for a tool based on its input schema.
|
||||||
fn build_tool_arguments(tool: &Tool) -> Option<serde_json::Map<String, Value>> {
|
fn build_tool_arguments(tool: &Tool) -> Option<serde_json::Map<String, Value>> {
|
||||||
let schema = &tool.input_schema;
|
let schema = &tool.input_schema;
|
||||||
|
|
@ -840,12 +804,7 @@ async fn run_tools_call_client(server_url: &str) -> anyhow::Result<()> {
|
||||||
for tool in &tools.tools {
|
for tool in &tools.tools {
|
||||||
let args = build_tool_arguments(tool);
|
let args = build_tool_arguments(tool);
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), args))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: args,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
@ -862,12 +821,7 @@ async fn run_elicitation_defaults_client(server_url: &str) -> anyhow::Result<()>
|
||||||
});
|
});
|
||||||
if let Some(tool) = test_tool {
|
if let Some(tool) = test_tool {
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), None))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: None,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
@ -884,12 +838,7 @@ async fn run_sse_retry_client(server_url: &str) -> anyhow::Result<()> {
|
||||||
.find(|t| t.name.as_ref() == "test_reconnection")
|
.find(|t| t.name.as_ref() == "test_reconnection")
|
||||||
{
|
{
|
||||||
let _ = client
|
let _ = client
|
||||||
.call_tool(CallToolRequestParams {
|
.call_tool(call_tool_params(tool.name.clone(), None))
|
||||||
meta: None,
|
|
||||||
name: tool.name.clone(),
|
|
||||||
arguments: None,
|
|
||||||
task: None,
|
|
||||||
})
|
|
||||||
.await?;
|
.await?;
|
||||||
}
|
}
|
||||||
client.cancel().await?;
|
client.cancel().await?;
|
||||||
|
|
|
||||||
|
|
@ -48,24 +48,16 @@ impl ServerHandler for ConformanceServer {
|
||||||
_cx: RequestContext<RoleServer>,
|
_cx: RequestContext<RoleServer>,
|
||||||
) -> impl Future<Output = Result<InitializeResult, ErrorData>> + Send + '_ {
|
) -> impl Future<Output = Result<InitializeResult, ErrorData>> + Send + '_ {
|
||||||
async {
|
async {
|
||||||
Ok(InitializeResult {
|
Ok(InitializeResult::new(
|
||||||
server_info: Implementation {
|
ServerCapabilities::builder()
|
||||||
name: "rust-conformance-server".into(),
|
|
||||||
title: None,
|
|
||||||
version: "0.1.0".into(),
|
|
||||||
description: None,
|
|
||||||
icons: None,
|
|
||||||
website_url: None,
|
|
||||||
},
|
|
||||||
capabilities: ServerCapabilities::builder()
|
|
||||||
.enable_prompts()
|
.enable_prompts()
|
||||||
.enable_resources()
|
.enable_resources()
|
||||||
.enable_tools()
|
.enable_tools()
|
||||||
.enable_logging()
|
.enable_logging()
|
||||||
.build(),
|
.build(),
|
||||||
instructions: Some("Rust MCP conformance test server".into()),
|
)
|
||||||
..Default::default()
|
.with_server_info(Implementation::new("rust-conformance-server", "0.1.0"))
|
||||||
})
|
.with_instructions("Rust MCP conformance test server"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -232,19 +224,14 @@ impl ServerHandler for ConformanceServer {
|
||||||
async move {
|
async move {
|
||||||
let args = request.arguments.unwrap_or_default();
|
let args = request.arguments.unwrap_or_default();
|
||||||
match request.name.as_ref() {
|
match request.name.as_ref() {
|
||||||
"test_simple_text" => Ok(CallToolResult {
|
"test_simple_text" => Ok(CallToolResult::success(vec![Content::text(
|
||||||
content: vec![Content::text("This is a simple text response for testing.")],
|
"This is a simple text response for testing.",
|
||||||
structured_content: None,
|
)])),
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
"test_image_content" => Ok(CallToolResult {
|
"test_image_content" => Ok(CallToolResult::success(vec![Content::image(
|
||||||
content: vec![Content::image(TEST_IMAGE_DATA, "image/png")],
|
TEST_IMAGE_DATA,
|
||||||
structured_content: None,
|
"image/png",
|
||||||
is_error: None,
|
)])),
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
"test_audio_content" => {
|
"test_audio_content" => {
|
||||||
// No Content::audio() helper, construct manually
|
// No Content::audio() helper, construct manually
|
||||||
|
|
@ -253,41 +240,28 @@ impl ServerHandler for ConformanceServer {
|
||||||
mime_type: "audio/wav".into(),
|
mime_type: "audio/wav".into(),
|
||||||
})
|
})
|
||||||
.no_annotation();
|
.no_annotation();
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![audio]))
|
||||||
content: vec![audio],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"test_embedded_resource" => Ok(CallToolResult {
|
"test_embedded_resource" => Ok(CallToolResult::success(vec![Content::resource(
|
||||||
content: vec![Content::resource(ResourceContents::TextResourceContents {
|
ResourceContents::TextResourceContents {
|
||||||
uri: "test://embedded-resource".into(),
|
uri: "test://embedded-resource".into(),
|
||||||
mime_type: Some("text/plain".into()),
|
mime_type: Some("text/plain".into()),
|
||||||
text: "This is an embedded resource content.".into(),
|
text: "This is an embedded resource content.".into(),
|
||||||
meta: None,
|
meta: None,
|
||||||
})],
|
},
|
||||||
structured_content: None,
|
)])),
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
"test_multiple_content_types" => Ok(CallToolResult {
|
"test_multiple_content_types" => Ok(CallToolResult::success(vec![
|
||||||
content: vec![
|
Content::text("Multiple content types test:"),
|
||||||
Content::text("Multiple content types test:"),
|
Content::image(TEST_IMAGE_DATA, "image/png"),
|
||||||
Content::image(TEST_IMAGE_DATA, "image/png"),
|
Content::resource(ResourceContents::TextResourceContents {
|
||||||
Content::resource(ResourceContents::TextResourceContents {
|
uri: "test://mixed-content-resource".into(),
|
||||||
uri: "test://mixed-content-resource".into(),
|
mime_type: Some("application/json".into()),
|
||||||
mime_type: Some("application/json".into()),
|
text: r#"{"test":"data","value":123}"#.into(),
|
||||||
text: r#"{"test":"data","value":123}"#.into(),
|
meta: None,
|
||||||
meta: None,
|
}),
|
||||||
}),
|
])),
|
||||||
],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
"test_tool_with_logging" => {
|
"test_tool_with_logging" => {
|
||||||
for msg in [
|
for msg in [
|
||||||
|
|
@ -306,22 +280,14 @@ impl ServerHandler for ConformanceServer {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![Content::text(
|
||||||
content: vec![Content::text("Logging test completed")],
|
"Logging test completed",
|
||||||
structured_content: None,
|
)]))
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"test_error_handling" => Ok(CallToolResult {
|
"test_error_handling" => Ok(CallToolResult::error(vec![Content::text(
|
||||||
content: vec![Content::text(
|
"This tool intentionally returns an error for testing",
|
||||||
"This tool intentionally returns an error for testing",
|
)])),
|
||||||
)],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: Some(true),
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
|
|
||||||
"test_tool_with_progress" => {
|
"test_tool_with_progress" => {
|
||||||
let progress_token = cx.meta.get_progress_token();
|
let progress_token = cx.meta.get_progress_token();
|
||||||
|
|
@ -343,12 +309,9 @@ impl ServerHandler for ConformanceServer {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![Content::text(
|
||||||
content: vec![Content::text("Progress test completed")],
|
"Progress test completed",
|
||||||
structured_content: None,
|
)]))
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"test_sampling" => {
|
"test_sampling" => {
|
||||||
|
|
@ -359,20 +322,10 @@ impl ServerHandler for ConformanceServer {
|
||||||
|
|
||||||
match cx
|
match cx
|
||||||
.peer
|
.peer
|
||||||
.create_message(CreateMessageRequestParams {
|
.create_message(CreateMessageRequestParams::new(
|
||||||
meta: None,
|
vec![SamplingMessage::user_text(prompt)],
|
||||||
task: None,
|
100,
|
||||||
messages: vec![SamplingMessage::user_text(prompt)],
|
))
|
||||||
max_tokens: 100,
|
|
||||||
model_preferences: None,
|
|
||||||
system_prompt: None,
|
|
||||||
include_context: None,
|
|
||||||
temperature: None,
|
|
||||||
stop_sequences: None,
|
|
||||||
metadata: None,
|
|
||||||
tools: None,
|
|
||||||
tool_choice: None,
|
|
||||||
})
|
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => {
|
Ok(result) => {
|
||||||
|
|
@ -383,19 +336,15 @@ impl ServerHandler for ConformanceServer {
|
||||||
.and_then(|c| c.as_text())
|
.and_then(|c| c.as_text())
|
||||||
.map(|t| t.text.clone())
|
.map(|t| t.text.clone())
|
||||||
.unwrap_or_else(|| "No text response".into());
|
.unwrap_or_else(|| "No text response".into());
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!("LLM response: {}", text))],
|
"LLM response: {}",
|
||||||
structured_content: None,
|
text
|
||||||
is_error: None,
|
))]))
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
Err(e) => Ok(CallToolResult {
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!("Sampling error: {}", e))],
|
"Sampling error: {}",
|
||||||
structured_content: None,
|
e
|
||||||
is_error: Some(true),
|
))])),
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -431,26 +380,19 @@ impl ServerHandler for ConformanceServer {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => Ok(CallToolResult {
|
Ok(result) => Ok(CallToolResult::success(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!(
|
"User response: action={}, content={:?}",
|
||||||
"User response: action={}, content={:?}",
|
match result.action {
|
||||||
match result.action {
|
ElicitationAction::Accept => "accept",
|
||||||
ElicitationAction::Accept => "accept",
|
ElicitationAction::Decline => "decline",
|
||||||
ElicitationAction::Decline => "decline",
|
ElicitationAction::Cancel => "cancel",
|
||||||
ElicitationAction::Cancel => "cancel",
|
},
|
||||||
},
|
result.content
|
||||||
result.content
|
))])),
|
||||||
))],
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(format!(
|
||||||
structured_content: None,
|
"Elicitation error: {}",
|
||||||
is_error: None,
|
e
|
||||||
meta: None,
|
))])),
|
||||||
}),
|
|
||||||
Err(e) => Ok(CallToolResult {
|
|
||||||
content: vec![Content::text(format!("Elicitation error: {}", e))],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: Some(true),
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -498,26 +440,19 @@ impl ServerHandler for ConformanceServer {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => Ok(CallToolResult {
|
Ok(result) => Ok(CallToolResult::success(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!(
|
"Elicitation completed: action={}, content={:?}",
|
||||||
"Elicitation completed: action={}, content={:?}",
|
match result.action {
|
||||||
match result.action {
|
ElicitationAction::Accept => "accept",
|
||||||
ElicitationAction::Accept => "accept",
|
ElicitationAction::Decline => "decline",
|
||||||
ElicitationAction::Decline => "decline",
|
ElicitationAction::Cancel => "cancel",
|
||||||
ElicitationAction::Cancel => "cancel",
|
},
|
||||||
},
|
result.content
|
||||||
result.content
|
))])),
|
||||||
))],
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(format!(
|
||||||
structured_content: None,
|
"Elicitation error: {}",
|
||||||
is_error: None,
|
e
|
||||||
meta: None,
|
))])),
|
||||||
}),
|
|
||||||
Err(e) => Ok(CallToolResult {
|
|
||||||
content: vec![Content::text(format!("Elicitation error: {}", e))],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: Some(true),
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -573,46 +508,34 @@ impl ServerHandler for ConformanceServer {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
Ok(result) => Ok(CallToolResult {
|
Ok(result) => Ok(CallToolResult::success(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!(
|
"Enum elicitation completed: action={}",
|
||||||
"Enum elicitation completed: action={}",
|
match result.action {
|
||||||
match result.action {
|
ElicitationAction::Accept => "accept",
|
||||||
ElicitationAction::Accept => "accept",
|
ElicitationAction::Decline => "decline",
|
||||||
ElicitationAction::Decline => "decline",
|
ElicitationAction::Cancel => "cancel",
|
||||||
ElicitationAction::Cancel => "cancel",
|
}
|
||||||
}
|
))])),
|
||||||
))],
|
Err(e) => Ok(CallToolResult::error(vec![Content::text(format!(
|
||||||
structured_content: None,
|
"Elicitation error: {}",
|
||||||
is_error: None,
|
e
|
||||||
meta: None,
|
))])),
|
||||||
}),
|
|
||||||
Err(e) => Ok(CallToolResult {
|
|
||||||
content: vec![Content::text(format!("Elicitation error: {}", e))],
|
|
||||||
structured_content: None,
|
|
||||||
is_error: Some(true),
|
|
||||||
meta: None,
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
"json_schema_2020_12_tool" => {
|
"json_schema_2020_12_tool" => {
|
||||||
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("world");
|
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("world");
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![Content::text(format!(
|
||||||
content: vec![Content::text(format!("Hello, {}!", name))],
|
"Hello, {}!",
|
||||||
structured_content: None,
|
name
|
||||||
is_error: None,
|
))]))
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
"test_reconnection" => {
|
"test_reconnection" => {
|
||||||
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||||
Ok(CallToolResult {
|
Ok(CallToolResult::success(vec![Content::text(
|
||||||
content: vec![Content::text("Reconnection test completed")],
|
"Reconnection test completed",
|
||||||
structured_content: None,
|
)]))
|
||||||
is_error: None,
|
|
||||||
meta: None,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_ => Err(ErrorData::invalid_params(
|
_ => Err(ErrorData::invalid_params(
|
||||||
|
|
@ -668,22 +591,22 @@ impl ServerHandler for ConformanceServer {
|
||||||
async move {
|
async move {
|
||||||
let uri = request.uri.as_str();
|
let uri = request.uri.as_str();
|
||||||
match uri {
|
match uri {
|
||||||
"test://static-text" => Ok(ReadResourceResult {
|
"test://static-text" => Ok(ReadResourceResult::new(vec![
|
||||||
contents: vec![ResourceContents::TextResourceContents {
|
ResourceContents::TextResourceContents {
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
mime_type: Some("text/plain".into()),
|
mime_type: Some("text/plain".into()),
|
||||||
text: "This is the content of the static text resource.".into(),
|
text: "This is the content of the static text resource.".into(),
|
||||||
meta: None,
|
meta: None,
|
||||||
}],
|
},
|
||||||
}),
|
])),
|
||||||
"test://static-binary" => Ok(ReadResourceResult {
|
"test://static-binary" => Ok(ReadResourceResult::new(vec![
|
||||||
contents: vec![ResourceContents::BlobResourceContents {
|
ResourceContents::BlobResourceContents {
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
mime_type: Some("image/png".into()),
|
mime_type: Some("image/png".into()),
|
||||||
blob: TEST_IMAGE_DATA.into(),
|
blob: TEST_IMAGE_DATA.into(),
|
||||||
meta: None,
|
meta: None,
|
||||||
}],
|
},
|
||||||
}),
|
])),
|
||||||
_ => {
|
_ => {
|
||||||
// Check if it matches template: test://template/{id}/data
|
// Check if it matches template: test://template/{id}/data
|
||||||
if uri.starts_with("test://template/") && uri.ends_with("/data") {
|
if uri.starts_with("test://template/") && uri.ends_with("/data") {
|
||||||
|
|
@ -691,8 +614,8 @@ impl ServerHandler for ConformanceServer {
|
||||||
.strip_prefix("test://template/")
|
.strip_prefix("test://template/")
|
||||||
.and_then(|s| s.strip_suffix("/data"))
|
.and_then(|s| s.strip_suffix("/data"))
|
||||||
.unwrap_or("unknown");
|
.unwrap_or("unknown");
|
||||||
Ok(ReadResourceResult {
|
Ok(ReadResourceResult::new(vec![
|
||||||
contents: vec![ResourceContents::TextResourceContents {
|
ResourceContents::TextResourceContents {
|
||||||
uri: uri.into(),
|
uri: uri.into(),
|
||||||
mime_type: Some("application/json".into()),
|
mime_type: Some("application/json".into()),
|
||||||
text: format!(
|
text: format!(
|
||||||
|
|
@ -700,8 +623,8 @@ impl ServerHandler for ConformanceServer {
|
||||||
id, id
|
id, id
|
||||||
),
|
),
|
||||||
meta: None,
|
meta: None,
|
||||||
}],
|
},
|
||||||
})
|
]))
|
||||||
} else {
|
} else {
|
||||||
Err(ErrorData::resource_not_found(
|
Err(ErrorData::resource_not_found(
|
||||||
format!("Resource not found: {}", uri),
|
format!("Resource not found: {}", uri),
|
||||||
|
|
@ -779,18 +702,12 @@ impl ServerHandler for ConformanceServer {
|
||||||
"test_prompt_with_arguments",
|
"test_prompt_with_arguments",
|
||||||
Some("A test prompt that accepts arguments"),
|
Some("A test prompt that accepts arguments"),
|
||||||
Some(vec![
|
Some(vec![
|
||||||
PromptArgument {
|
PromptArgument::new("name")
|
||||||
name: "name".into(),
|
.with_description("The name to greet")
|
||||||
title: None,
|
.with_required(true),
|
||||||
description: Some("The name to greet".into()),
|
PromptArgument::new("style")
|
||||||
required: Some(true),
|
.with_description("The greeting style")
|
||||||
},
|
.with_required(false),
|
||||||
PromptArgument {
|
|
||||||
name: "style".into(),
|
|
||||||
title: None,
|
|
||||||
description: Some("The greeting style".into()),
|
|
||||||
required: Some(false),
|
|
||||||
},
|
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
Prompt::new(
|
Prompt::new(
|
||||||
|
|
@ -816,13 +733,11 @@ impl ServerHandler for ConformanceServer {
|
||||||
) -> impl Future<Output = Result<GetPromptResult, ErrorData>> + Send + '_ {
|
) -> impl Future<Output = Result<GetPromptResult, ErrorData>> + Send + '_ {
|
||||||
async move {
|
async move {
|
||||||
match request.name.as_str() {
|
match request.name.as_str() {
|
||||||
"test_simple_prompt" => Ok(GetPromptResult {
|
"test_simple_prompt" => Ok(GetPromptResult::new(vec![PromptMessage::new_text(
|
||||||
description: Some("A simple test prompt".into()),
|
PromptMessageRole::User,
|
||||||
messages: vec![PromptMessage::new_text(
|
"This is a simple test prompt.",
|
||||||
PromptMessageRole::User,
|
)])
|
||||||
"This is a simple test prompt.",
|
.with_description("A simple test prompt")),
|
||||||
)],
|
|
||||||
}),
|
|
||||||
"test_prompt_with_arguments" => {
|
"test_prompt_with_arguments" => {
|
||||||
let args = request.arguments.unwrap_or_default();
|
let args = request.arguments.unwrap_or_default();
|
||||||
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
|
let name = args.get("name").and_then(|v| v.as_str()).unwrap_or("World");
|
||||||
|
|
@ -830,47 +745,41 @@ impl ServerHandler for ConformanceServer {
|
||||||
.get("style")
|
.get("style")
|
||||||
.and_then(|v| v.as_str())
|
.and_then(|v| v.as_str())
|
||||||
.unwrap_or("friendly");
|
.unwrap_or("friendly");
|
||||||
Ok(GetPromptResult {
|
Ok(GetPromptResult::new(vec![PromptMessage::new_text(
|
||||||
description: Some("A prompt with arguments".into()),
|
PromptMessageRole::User,
|
||||||
messages: vec![PromptMessage::new_text(
|
format!("Please greet {} in a {} style.", name, style),
|
||||||
PromptMessageRole::User,
|
)])
|
||||||
format!("Please greet {} in a {} style.", name, style),
|
.with_description("A prompt with arguments"))
|
||||||
)],
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
"test_prompt_with_embedded_resource" => Ok(GetPromptResult {
|
"test_prompt_with_embedded_resource" => Ok(GetPromptResult::new(vec![
|
||||||
description: Some("A prompt with an embedded resource".into()),
|
PromptMessage::new_text(PromptMessageRole::User, "Here is a resource:"),
|
||||||
messages: vec![
|
PromptMessage::new_resource(
|
||||||
PromptMessage::new_text(PromptMessageRole::User, "Here is a resource:"),
|
PromptMessageRole::User,
|
||||||
PromptMessage::new_resource(
|
"test://static-text".into(),
|
||||||
PromptMessageRole::User,
|
Some("text/plain".into()),
|
||||||
"test://static-text".into(),
|
Some("Resource content for prompt".into()),
|
||||||
Some("text/plain".into()),
|
None,
|
||||||
Some("Resource content for prompt".into()),
|
None,
|
||||||
None,
|
None,
|
||||||
None,
|
),
|
||||||
None,
|
])
|
||||||
),
|
.with_description("A prompt with an embedded resource")),
|
||||||
],
|
|
||||||
}),
|
|
||||||
"test_prompt_with_image" => {
|
"test_prompt_with_image" => {
|
||||||
let image_content = RawImageContent {
|
let image_content = RawImageContent {
|
||||||
data: TEST_IMAGE_DATA.into(),
|
data: TEST_IMAGE_DATA.into(),
|
||||||
mime_type: "image/png".into(),
|
mime_type: "image/png".into(),
|
||||||
meta: None,
|
meta: None,
|
||||||
};
|
};
|
||||||
Ok(GetPromptResult {
|
Ok(GetPromptResult::new(vec![
|
||||||
description: Some("A prompt with an image".into()),
|
PromptMessage::new_text(PromptMessageRole::User, "Here is an image:"),
|
||||||
messages: vec![
|
PromptMessage::new(
|
||||||
PromptMessage::new_text(PromptMessageRole::User, "Here is an image:"),
|
PromptMessageRole::User,
|
||||||
PromptMessage {
|
PromptMessageContent::Image {
|
||||||
role: PromptMessageRole::User,
|
image: image_content.no_annotation(),
|
||||||
content: PromptMessageContent::Image {
|
|
||||||
image: image_content.no_annotation(),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
],
|
),
|
||||||
})
|
])
|
||||||
|
.with_description("A prompt with an image"))
|
||||||
}
|
}
|
||||||
_ => Err(ErrorData::invalid_params(
|
_ => Err(ErrorData::invalid_params(
|
||||||
format!("Unknown prompt: {}", request.name),
|
format!("Unknown prompt: {}", request.name),
|
||||||
|
|
@ -904,10 +813,9 @@ impl ServerHandler for ConformanceServer {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
Ok(CompleteResult {
|
Ok(CompleteResult::new(
|
||||||
completion: CompletionInfo::new(values)
|
CompletionInfo::new(values).map_err(|e| ErrorData::internal_error(e, None))?,
|
||||||
.map_err(|e| ErrorData::internal_error(e, None))?,
|
))
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue