rust-sdk/crates/rmcp/tests/test_prompt_routers.rs
Michael Assaf 9349f5cb26
feat: Add prompt support (#351)
* feat: add prompt support with typed argument handling

- Implement #[prompt], #[prompt_router], and #[prompt_handler] macros
- Add automatic JSON schema generation from Rust types for arguments
- Support flexible async handler signatures with automatic adaptation
- Create PromptRouter for efficient prompt dispatch
- Include comprehensive tests and example implementation

This enables MCP servers to provide reusable prompt templates that
LLMs can discover and invoke with strongly-typed parameters, similar
to the existing tool system but optimized for prompt use cases.

🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: unify parameter handling between tools and prompts

- Replace Arguments<T> with Parameters<T> for consistent API
- Create shared common module for tool/prompt utilities
- Modernize async handling with futures::future::BoxFuture
- Move cached_schema_for_type to common module for reuse
- Update error types from rmcp::Error to rmcp::ErrorData
- Add comprehensive trait implementations for parameter extraction

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>

* refactor: extract Parameters wrapper to shared module and unify trait usage

- Move Parameters type from common.rs to dedicated wrapper/parameters.rs module
- Unify extractor traits by using FromContextPart for both tools and prompts
- Remove duplicate FromToolCallContextPart and FromPromptContextPart traits
- Add structured output support to Json wrapper with IntoCallToolResult impl
- Improve error messages with more descriptive panic for schema serialization
- Update all imports across codebase to use new module path
- Clean up trailing whitespace and formatting inconsistencies

This consolidates parameter extraction logic and reduces code duplication
between tool and prompt handlers while maintaining backward compatibility.

* chore: remove committed .egg-info directory and update gitignore

* docs: fix documentation formatting
2025-08-21 11:22:19 +08:00

105 lines
3.1 KiB
Rust

use std::collections::HashMap;
use futures::future::BoxFuture;
use rmcp::{
ServerHandler,
handler::server::wrapper::Parameters,
model::{GetPromptResult, PromptMessage, PromptMessageRole},
};
#[derive(Debug, Default)]
pub struct TestHandler<T: 'static = ()> {
pub _marker: std::marker::PhantomData<fn(*const T)>,
}
impl<T: 'static> ServerHandler for TestHandler<T> {}
#[derive(Debug, schemars::JsonSchema, serde::Deserialize, serde::Serialize)]
pub struct Request {
pub fields: HashMap<String, String>,
}
#[derive(Debug, schemars::JsonSchema, serde::Deserialize, serde::Serialize)]
pub struct Sum {
pub a: i32,
pub b: i32,
}
#[rmcp::prompt_router(router = "test_router")]
impl<T> TestHandler<T> {
#[rmcp::prompt]
async fn async_method(
&self,
Parameters(Request { fields }): Parameters<Request>,
) -> Vec<PromptMessage> {
drop(fields);
vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Async method response",
)]
}
#[rmcp::prompt]
fn sync_method(
&self,
Parameters(Request { fields }): Parameters<Request>,
) -> Vec<PromptMessage> {
drop(fields);
vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Sync method response",
)]
}
}
#[rmcp::prompt]
async fn async_function(Parameters(Request { fields }): Parameters<Request>) -> Vec<PromptMessage> {
drop(fields);
vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Async function response",
)]
}
#[rmcp::prompt]
fn async_function2<T>(_callee: &TestHandler<T>) -> BoxFuture<'_, GetPromptResult> {
Box::pin(async move {
GetPromptResult {
description: Some("Async function 2".to_string()),
messages: vec![PromptMessage::new_text(
PromptMessageRole::Assistant,
"Async function 2 response",
)],
}
})
}
#[test]
fn test_prompt_router() {
let test_prompt_router = TestHandler::<()>::test_router()
.with_route(rmcp::handler::server::router::prompt::PromptRoute::new_dyn(
async_function_prompt_attr(),
|mut context| {
Box::pin(async move {
use rmcp::handler::server::{
common::FromContextPart, prompt::IntoGetPromptResult,
};
let params = Parameters::<Request>::from_context_part(&mut context)?;
let result = async_function(params).await;
result.into_get_prompt_result()
})
},
))
.with_route(rmcp::handler::server::router::prompt::PromptRoute::new_dyn(
async_function2_prompt_attr(),
|context| {
Box::pin(async move {
use rmcp::handler::server::prompt::IntoGetPromptResult;
let result = async_function2(context.server).await;
result.into_get_prompt_result()
})
},
));
let prompts = test_prompt_router.list_all();
assert_eq!(prompts.len(), 4);
}