1use std::collections::HashMap;
46use std::convert::Infallible;
47use std::fmt;
48use std::future::Future;
49use std::pin::Pin;
50use std::sync::Arc;
51use std::task::{Context, Poll};
52
53use pin_project_lite::pin_project;
54
55use tokio::sync::Mutex;
56use tower::util::BoxCloneService;
57use tower::{Layer, ServiceExt};
58use tower_service::Service;
59
60use crate::context::RequestContext;
61use crate::error::{Error, Result};
62use crate::protocol::{
63 Content, GetPromptResult, PromptArgument, PromptDefinition, PromptMessage, PromptRole,
64 RequestId, RequestOutcome, ToolIcon,
65};
66
67pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
69
70#[derive(Debug, Clone)]
79pub struct PromptRequest {
80 pub context: RequestContext,
82 pub arguments: HashMap<String, String>,
84}
85
86impl PromptRequest {
87 pub fn new(context: RequestContext, arguments: HashMap<String, String>) -> Self {
89 Self { context, arguments }
90 }
91
92 pub fn with_arguments(arguments: HashMap<String, String>) -> Self {
94 Self {
95 context: RequestContext::new(RequestId::Number(0)),
96 arguments,
97 }
98 }
99}
100
101pub type BoxPromptService = BoxCloneService<PromptRequest, GetPromptResult, Infallible>;
107
108#[cfg(feature = "stateless")]
109type BoxMrtrPromptService =
110 BoxCloneService<PromptRequest, RequestOutcome<GetPromptResult>, Infallible>;
111
112#[doc(hidden)]
120pub struct PromptCatchError<S> {
121 inner: S,
122}
123
124impl<S> PromptCatchError<S> {
125 pub fn new(inner: S) -> Self {
127 Self { inner }
128 }
129}
130
131impl<S: Clone> Clone for PromptCatchError<S> {
132 fn clone(&self) -> Self {
133 Self {
134 inner: self.inner.clone(),
135 }
136 }
137}
138
139impl<S: fmt::Debug> fmt::Debug for PromptCatchError<S> {
140 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
141 f.debug_struct("PromptCatchError")
142 .field("inner", &self.inner)
143 .finish()
144 }
145}
146
147pin_project! {
148 #[doc(hidden)]
150 pub struct PromptCatchErrorFuture<F> {
151 #[pin]
152 inner: F,
153 }
154}
155
156impl<F, E> Future for PromptCatchErrorFuture<F>
157where
158 F: Future<Output = std::result::Result<GetPromptResult, E>>,
159 E: fmt::Display,
160{
161 type Output = std::result::Result<GetPromptResult, Infallible>;
162
163 fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
164 match self.project().inner.poll(cx) {
165 Poll::Pending => Poll::Pending,
166 Poll::Ready(Ok(response)) => Poll::Ready(Ok(response)),
167 Poll::Ready(Err(err)) => Poll::Ready(Ok(GetPromptResult {
168 description: Some(format!("Prompt error: {}", err)),
169 messages: vec![PromptMessage {
170 role: PromptRole::Assistant,
171 content: Content::Text {
172 text: format!("Error generating prompt: {}", err),
173 annotations: None,
174 meta: None,
175 },
176 meta: None,
177 }],
178 meta: None,
179 })),
180 }
181 }
182}
183
184impl<S> Service<PromptRequest> for PromptCatchError<S>
185where
186 S: Service<PromptRequest, Response = GetPromptResult> + Clone + Send + 'static,
187 S::Error: fmt::Display + Send,
188 S::Future: Send,
189{
190 type Response = GetPromptResult;
191 type Error = Infallible;
192 type Future = PromptCatchErrorFuture<S::Future>;
193
194 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
195 self.inner.poll_ready(cx).map_err(|_| unreachable!())
196 }
197
198 fn call(&mut self, req: PromptRequest) -> Self::Future {
199 PromptCatchErrorFuture {
200 inner: self.inner.call(req),
201 }
202 }
203}
204
205#[cfg(feature = "stateless")]
206#[derive(Clone)]
207struct MrtrPromptCatchError<S> {
208 inner: S,
209}
210
211#[cfg(feature = "stateless")]
212impl<S> MrtrPromptCatchError<S> {
213 fn new(inner: S) -> Self {
214 Self { inner }
215 }
216}
217
218#[cfg(feature = "stateless")]
219impl<S> Service<PromptRequest> for MrtrPromptCatchError<S>
220where
221 S: Service<PromptRequest, Response = RequestOutcome<GetPromptResult>> + Clone + Send + 'static,
222 S::Error: fmt::Display + Send + 'static,
223 S::Future: Send + 'static,
224{
225 type Response = RequestOutcome<GetPromptResult>;
226 type Error = Infallible;
227 type Future =
228 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
229
230 fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
231 match self.inner.poll_ready(cx) {
232 Poll::Ready(Ok(())) | Poll::Ready(Err(_)) => Poll::Ready(Ok(())),
233 Poll::Pending => Poll::Pending,
234 }
235 }
236
237 fn call(&mut self, req: PromptRequest) -> Self::Future {
238 let future = self.inner.call(req);
239 Box::pin(async move {
240 Ok(match future.await {
241 Ok(outcome) => outcome,
242 Err(error) => RequestOutcome::Complete(GetPromptResult {
243 description: Some(format!("Prompt error: {error}")),
244 messages: vec![PromptMessage {
245 role: PromptRole::Assistant,
246 content: Content::Text {
247 text: format!("Error generating prompt: {error}"),
248 annotations: None,
249 meta: None,
250 },
251 meta: None,
252 }],
253 meta: None,
254 }),
255 })
256 })
257 }
258}
259
260#[doc(hidden)]
265pub struct PromptHandlerService<F> {
266 handler: F,
267}
268
269impl<F> Clone for PromptHandlerService<F>
270where
271 F: Clone,
272{
273 fn clone(&self) -> Self {
274 Self {
275 handler: self.handler.clone(),
276 }
277 }
278}
279
280impl<F, Fut> Service<PromptRequest> for PromptHandlerService<F>
281where
282 F: Fn(HashMap<String, String>) -> Fut + Clone + Send + Sync + 'static,
283 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
284{
285 type Response = GetPromptResult;
286 type Error = Error;
287 type Future = Pin<Box<dyn Future<Output = std::result::Result<GetPromptResult, Error>> + Send>>;
288
289 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
290 Poll::Ready(Ok(()))
291 }
292
293 fn call(&mut self, req: PromptRequest) -> Self::Future {
294 let handler = self.handler.clone();
295 Box::pin(async move { handler(req.arguments).await })
296 }
297}
298
299#[doc(hidden)]
303pub struct PromptContextHandlerService<F> {
304 handler: F,
305}
306
307#[cfg(feature = "stateless")]
308#[doc(hidden)]
309pub struct MrtrPromptHandlerService<F> {
310 handler: F,
311}
312
313#[cfg(feature = "stateless")]
314impl<F: Clone> Clone for MrtrPromptHandlerService<F> {
315 fn clone(&self) -> Self {
316 Self {
317 handler: self.handler.clone(),
318 }
319 }
320}
321
322#[cfg(feature = "stateless")]
323impl<F, Fut> Service<PromptRequest> for MrtrPromptHandlerService<F>
324where
325 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Clone + Send + Sync + 'static,
326 Fut: Future<Output = Result<RequestOutcome<GetPromptResult>>> + Send + 'static,
327{
328 type Response = RequestOutcome<GetPromptResult>;
329 type Error = Error;
330 type Future =
331 Pin<Box<dyn Future<Output = std::result::Result<Self::Response, Self::Error>> + Send>>;
332
333 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
334 Poll::Ready(Ok(()))
335 }
336
337 fn call(&mut self, req: PromptRequest) -> Self::Future {
338 let handler = self.handler.clone();
339 Box::pin(async move { handler(req.context, req.arguments).await })
340 }
341}
342
343impl<F> Clone for PromptContextHandlerService<F>
344where
345 F: Clone,
346{
347 fn clone(&self) -> Self {
348 Self {
349 handler: self.handler.clone(),
350 }
351 }
352}
353
354impl<F, Fut> Service<PromptRequest> for PromptContextHandlerService<F>
355where
356 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Clone + Send + Sync + 'static,
357 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
358{
359 type Response = GetPromptResult;
360 type Error = Error;
361 type Future = Pin<Box<dyn Future<Output = std::result::Result<GetPromptResult, Error>> + Send>>;
362
363 fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<std::result::Result<(), Self::Error>> {
364 Poll::Ready(Ok(()))
365 }
366
367 fn call(&mut self, req: PromptRequest) -> Self::Future {
368 let handler = self.handler.clone();
369 Box::pin(async move { handler(req.context, req.arguments).await })
370 }
371}
372
373pub trait PromptHandler: Send + Sync {
375 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>>;
377
378 fn get_with_context(
383 &self,
384 _ctx: RequestContext,
385 arguments: HashMap<String, String>,
386 ) -> BoxFuture<'_, Result<GetPromptResult>> {
387 self.get(arguments)
388 }
389
390 fn uses_context(&self) -> bool {
392 false
393 }
394}
395
396#[cfg(feature = "stateless")]
398pub trait MrtrPromptHandler: Send + Sync {
399 fn get(
402 &self,
403 ctx: RequestContext,
404 arguments: HashMap<String, String>,
405 ) -> BoxFuture<'_, Result<RequestOutcome<GetPromptResult>>>;
406}
407
408pub struct Prompt {
410 pub name: String,
412 pub title: Option<String>,
414 pub description: Option<String>,
416 pub icons: Option<Vec<ToolIcon>>,
418 pub arguments: Vec<PromptArgument>,
420 handler: Option<Arc<dyn PromptHandler>>,
421 #[cfg(feature = "stateless")]
422 mrtr_handler: Option<Arc<dyn MrtrPromptHandler>>,
423}
424
425impl Clone for Prompt {
426 fn clone(&self) -> Self {
427 Self {
428 name: self.name.clone(),
429 title: self.title.clone(),
430 description: self.description.clone(),
431 icons: self.icons.clone(),
432 arguments: self.arguments.clone(),
433 handler: self.handler.clone(),
434 #[cfg(feature = "stateless")]
435 mrtr_handler: self.mrtr_handler.clone(),
436 }
437 }
438}
439
440impl std::fmt::Debug for Prompt {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 f.debug_struct("Prompt")
443 .field("name", &self.name)
444 .field("title", &self.title)
445 .field("description", &self.description)
446 .field("icons", &self.icons)
447 .field("arguments", &self.arguments)
448 .finish_non_exhaustive()
449 }
450}
451
452impl Prompt {
453 pub fn builder(name: impl Into<String>) -> PromptBuilder {
455 PromptBuilder::new(name)
456 }
457
458 pub fn definition(&self) -> PromptDefinition {
460 PromptDefinition {
461 name: self.name.clone(),
462 title: self.title.clone(),
463 description: self.description.clone(),
464 icons: self.icons.clone(),
465 arguments: self.arguments.clone(),
466 meta: None,
467 }
468 }
469
470 pub fn get(
472 &self,
473 arguments: HashMap<String, String>,
474 ) -> BoxFuture<'_, Result<GetPromptResult>> {
475 match &self.handler {
476 Some(handler) => handler.get(arguments),
477 None => Box::pin(async {
478 Err(Error::invalid_params(
479 "MRTR prompt requires get_outcome_with_context",
480 ))
481 }),
482 }
483 }
484
485 pub fn get_with_context(
489 &self,
490 ctx: RequestContext,
491 arguments: HashMap<String, String>,
492 ) -> BoxFuture<'_, Result<GetPromptResult>> {
493 match &self.handler {
494 Some(handler) => handler.get_with_context(ctx, arguments),
495 None => Box::pin(async {
496 Err(Error::invalid_params(
497 "MRTR prompt requires get_outcome_with_context",
498 ))
499 }),
500 }
501 }
502
503 pub fn get_outcome_with_context(
505 &self,
506 ctx: RequestContext,
507 arguments: HashMap<String, String>,
508 ) -> BoxFuture<'_, Result<RequestOutcome<GetPromptResult>>> {
509 #[cfg(feature = "stateless")]
510 if let Some(handler) = &self.mrtr_handler {
511 return handler.get(ctx, arguments);
512 }
513 match &self.handler {
514 Some(handler) => Box::pin(async move {
515 handler
516 .get_with_context(ctx, arguments)
517 .await
518 .map(RequestOutcome::Complete)
519 }),
520 None => Box::pin(async {
521 Err(Error::invalid_params(
522 "prompt has neither a complete nor MRTR handler",
523 ))
524 }),
525 }
526 }
527
528 pub fn uses_context(&self) -> bool {
530 self.handler
531 .as_ref()
532 .is_none_or(|handler| handler.uses_context())
533 }
534}
535
536pub struct PromptBuilder {
572 name: String,
573 title: Option<String>,
574 description: Option<String>,
575 icons: Option<Vec<ToolIcon>>,
576 arguments: Vec<PromptArgument>,
577}
578
579impl PromptBuilder {
580 pub fn new(name: impl Into<String>) -> Self {
582 Self {
583 name: name.into(),
584 title: None,
585 description: None,
586 icons: None,
587 arguments: Vec::new(),
588 }
589 }
590
591 pub fn title(mut self, title: impl Into<String>) -> Self {
593 self.title = Some(title.into());
594 self
595 }
596
597 pub fn description(mut self, description: impl Into<String>) -> Self {
599 self.description = Some(description.into());
600 self
601 }
602
603 pub fn icon(mut self, src: impl Into<String>) -> Self {
605 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
606 src: src.into(),
607 mime_type: None,
608 sizes: None,
609 theme: None,
610 });
611 self
612 }
613
614 pub fn icon_with_meta(
616 mut self,
617 src: impl Into<String>,
618 mime_type: Option<String>,
619 sizes: Option<Vec<String>>,
620 ) -> Self {
621 self.icons.get_or_insert_with(Vec::new).push(ToolIcon {
622 src: src.into(),
623 mime_type,
624 sizes,
625 theme: None,
626 });
627 self
628 }
629
630 pub fn required_arg(mut self, name: impl Into<String>, description: impl Into<String>) -> Self {
632 self.arguments.push(PromptArgument {
633 name: name.into(),
634 description: Some(description.into()),
635 required: true,
636 });
637 self
638 }
639
640 pub fn optional_arg(mut self, name: impl Into<String>, description: impl Into<String>) -> Self {
642 self.arguments.push(PromptArgument {
643 name: name.into(),
644 description: Some(description.into()),
645 required: false,
646 });
647 self
648 }
649
650 pub fn argument(mut self, arg: PromptArgument) -> Self {
652 self.arguments.push(arg);
653 self
654 }
655
656 pub fn handler<F, Fut>(self, handler: F) -> PromptBuilderWithHandler<F>
704 where
705 F: Fn(HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
706 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
707 {
708 PromptBuilderWithHandler {
709 name: self.name,
710 title: self.title,
711 description: self.description,
712 icons: self.icons,
713 arguments: self.arguments,
714 handler,
715 }
716 }
717
718 pub fn handler_with_context<F, Fut>(self, handler: F) -> PromptBuilderWithContextHandler<F>
723 where
724 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
725 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
726 {
727 PromptBuilderWithContextHandler {
728 name: self.name,
729 title: self.title,
730 description: self.description,
731 icons: self.icons,
732 arguments: self.arguments,
733 handler,
734 }
735 }
736
737 #[cfg(feature = "stateless")]
739 pub fn mrtr_handler<F, Fut>(self, handler: F) -> PromptBuilderWithMrtrHandler<F>
740 where
741 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
742 Fut: Future<Output = Result<RequestOutcome<GetPromptResult>>> + Send + 'static,
743 {
744 PromptBuilderWithMrtrHandler {
745 name: self.name,
746 title: self.title,
747 description: self.description,
748 icons: self.icons,
749 arguments: self.arguments,
750 handler,
751 }
752 }
753
754 pub fn static_prompt(self, messages: Vec<PromptMessage>) -> Prompt {
756 let description = self.description.clone();
757 self.handler(move |_| {
758 let messages = messages.clone();
759 let description = description.clone();
760 async move {
761 Ok(GetPromptResult {
762 description,
763 messages,
764 meta: None,
765 })
766 }
767 })
768 .build()
769 }
770
771 pub fn user_message(self, text: impl Into<String>) -> Prompt {
773 let text = text.into();
774 self.static_prompt(vec![PromptMessage {
775 role: PromptRole::User,
776 content: Content::Text {
777 text,
778 annotations: None,
779 meta: None,
780 },
781 meta: None,
782 }])
783 }
784
785 pub fn build<F, Fut>(self, handler: F) -> Prompt
790 where
791 F: Fn(HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
792 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
793 {
794 self.handler(handler).build()
795 }
796}
797
798#[doc(hidden)]
803pub struct PromptBuilderWithHandler<F> {
804 name: String,
805 title: Option<String>,
806 description: Option<String>,
807 icons: Option<Vec<ToolIcon>>,
808 arguments: Vec<PromptArgument>,
809 handler: F,
810}
811
812#[cfg(feature = "stateless")]
813#[doc(hidden)]
814pub struct PromptBuilderWithMrtrHandler<F> {
815 name: String,
816 title: Option<String>,
817 description: Option<String>,
818 icons: Option<Vec<ToolIcon>>,
819 arguments: Vec<PromptArgument>,
820 handler: F,
821}
822
823impl<F, Fut> PromptBuilderWithHandler<F>
824where
825 F: Fn(HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
826 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
827{
828 pub fn build(self) -> Prompt {
830 Prompt {
831 name: self.name,
832 title: self.title,
833 description: self.description,
834 icons: self.icons,
835 arguments: self.arguments,
836 handler: Some(Arc::new(FnHandler {
837 handler: self.handler,
838 })),
839 #[cfg(feature = "stateless")]
840 mrtr_handler: None,
841 }
842 }
843
844 pub fn layer<L>(self, layer: L) -> Prompt
878 where
879 L: Layer<PromptHandlerService<F>> + Send + Sync + 'static,
880 L::Service: Service<PromptRequest, Response = GetPromptResult> + Clone + Send + 'static,
881 <L::Service as Service<PromptRequest>>::Error: fmt::Display + Send,
882 <L::Service as Service<PromptRequest>>::Future: Send,
883 {
884 let service = PromptHandlerService {
885 handler: self.handler,
886 };
887 let wrapped = layer.layer(service);
888 let boxed = BoxCloneService::new(PromptCatchError::new(wrapped));
889
890 Prompt {
891 name: self.name,
892 title: self.title,
893 description: self.description,
894 icons: self.icons,
895 arguments: self.arguments,
896 handler: Some(Arc::new(ServiceHandler {
897 service: Mutex::new(boxed),
898 })),
899 #[cfg(feature = "stateless")]
900 mrtr_handler: None,
901 }
902 }
903}
904
905#[cfg(feature = "stateless")]
906impl<F, Fut> PromptBuilderWithMrtrHandler<F>
907where
908 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
909 Fut: Future<Output = Result<RequestOutcome<GetPromptResult>>> + Send + 'static,
910{
911 pub fn build(self) -> Prompt {
913 Prompt {
914 name: self.name,
915 title: self.title,
916 description: self.description,
917 icons: self.icons,
918 arguments: self.arguments,
919 handler: None,
920 mrtr_handler: Some(Arc::new(MrtrContextHandler {
921 handler: self.handler,
922 })),
923 }
924 }
925
926 #[allow(private_bounds)]
932 pub fn layer<L>(self, layer: L) -> Prompt
933 where
934 L: Layer<MrtrPromptHandlerService<F>> + Send + Sync + 'static,
935 L::Service: Service<PromptRequest, Response = RequestOutcome<GetPromptResult>>
936 + Clone
937 + Send
938 + 'static,
939 <L::Service as Service<PromptRequest>>::Error: fmt::Display + Send + 'static,
940 <L::Service as Service<PromptRequest>>::Future: Send + 'static,
941 {
942 let service = MrtrPromptHandlerService {
943 handler: self.handler,
944 };
945 let service = layer.layer(service);
946 let service = BoxCloneService::new(MrtrPromptCatchError::new(service));
947
948 Prompt {
949 name: self.name,
950 title: self.title,
951 description: self.description,
952 icons: self.icons,
953 arguments: self.arguments,
954 handler: None,
955 mrtr_handler: Some(Arc::new(ServiceMrtrPromptHandler {
956 service: Mutex::new(service),
957 })),
958 }
959 }
960}
961
962#[doc(hidden)]
964pub struct PromptBuilderWithContextHandler<F> {
965 name: String,
966 title: Option<String>,
967 description: Option<String>,
968 icons: Option<Vec<ToolIcon>>,
969 arguments: Vec<PromptArgument>,
970 handler: F,
971}
972
973impl<F, Fut> PromptBuilderWithContextHandler<F>
974where
975 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + Clone + 'static,
976 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
977{
978 pub fn build(self) -> Prompt {
980 Prompt {
981 name: self.name,
982 title: self.title,
983 description: self.description,
984 icons: self.icons,
985 arguments: self.arguments,
986 handler: Some(Arc::new(ContextAwareHandler {
987 handler: self.handler,
988 })),
989 #[cfg(feature = "stateless")]
990 mrtr_handler: None,
991 }
992 }
993
994 pub fn layer<L>(self, layer: L) -> Prompt
996 where
997 L: Layer<PromptContextHandlerService<F>> + Send + Sync + 'static,
998 L::Service: Service<PromptRequest, Response = GetPromptResult> + Clone + Send + 'static,
999 <L::Service as Service<PromptRequest>>::Error: fmt::Display + Send,
1000 <L::Service as Service<PromptRequest>>::Future: Send,
1001 {
1002 let service = PromptContextHandlerService {
1003 handler: self.handler,
1004 };
1005 let wrapped = layer.layer(service);
1006 let boxed = BoxCloneService::new(PromptCatchError::new(wrapped));
1007
1008 Prompt {
1009 name: self.name,
1010 title: self.title,
1011 description: self.description,
1012 icons: self.icons,
1013 arguments: self.arguments,
1014 handler: Some(Arc::new(ServiceContextHandler {
1015 service: Mutex::new(boxed),
1016 })),
1017 #[cfg(feature = "stateless")]
1018 mrtr_handler: None,
1019 }
1020 }
1021}
1022
1023struct FnHandler<F> {
1029 handler: F,
1030}
1031
1032impl<F, Fut> PromptHandler for FnHandler<F>
1033where
1034 F: Fn(HashMap<String, String>) -> Fut + Send + Sync + 'static,
1035 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
1036{
1037 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>> {
1038 Box::pin((self.handler)(arguments))
1039 }
1040}
1041
1042struct ContextAwareHandler<F> {
1044 handler: F,
1045}
1046
1047#[cfg(feature = "stateless")]
1048struct MrtrContextHandler<F> {
1049 handler: F,
1050}
1051
1052#[cfg(feature = "stateless")]
1053struct ServiceMrtrPromptHandler {
1054 service: Mutex<BoxMrtrPromptService>,
1055}
1056
1057#[cfg(feature = "stateless")]
1058impl MrtrPromptHandler for ServiceMrtrPromptHandler {
1059 fn get(
1060 &self,
1061 ctx: RequestContext,
1062 arguments: HashMap<String, String>,
1063 ) -> BoxFuture<'_, Result<RequestOutcome<GetPromptResult>>> {
1064 Box::pin(async move {
1065 let request = PromptRequest::new(ctx, arguments);
1066 let mut service = self.service.lock().await.clone();
1067 let outcome = service
1068 .ready()
1069 .await
1070 .expect("MRTR prompt service is infallible")
1071 .call(request)
1072 .await
1073 .expect("MRTR prompt service is infallible");
1074 Ok(outcome)
1075 })
1076 }
1077}
1078
1079#[cfg(feature = "stateless")]
1080impl<F, Fut> MrtrPromptHandler for MrtrContextHandler<F>
1081where
1082 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1083 Fut: Future<Output = Result<RequestOutcome<GetPromptResult>>> + Send + 'static,
1084{
1085 fn get(
1086 &self,
1087 ctx: RequestContext,
1088 arguments: HashMap<String, String>,
1089 ) -> BoxFuture<'_, Result<RequestOutcome<GetPromptResult>>> {
1090 Box::pin((self.handler)(ctx, arguments))
1091 }
1092}
1093
1094impl<F, Fut> PromptHandler for ContextAwareHandler<F>
1095where
1096 F: Fn(RequestContext, HashMap<String, String>) -> Fut + Send + Sync + 'static,
1097 Fut: Future<Output = Result<GetPromptResult>> + Send + 'static,
1098{
1099 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>> {
1100 let ctx = RequestContext::new(RequestId::Number(0));
1102 self.get_with_context(ctx, arguments)
1103 }
1104
1105 fn get_with_context(
1106 &self,
1107 ctx: RequestContext,
1108 arguments: HashMap<String, String>,
1109 ) -> BoxFuture<'_, Result<GetPromptResult>> {
1110 Box::pin((self.handler)(ctx, arguments))
1111 }
1112
1113 fn uses_context(&self) -> bool {
1114 true
1115 }
1116}
1117
1118struct ServiceHandler {
1124 service: Mutex<BoxPromptService>,
1125}
1126
1127impl PromptHandler for ServiceHandler {
1128 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>> {
1129 Box::pin(async move {
1130 let req = PromptRequest::with_arguments(arguments);
1131 let mut service = self.service.lock().await.clone();
1132 match service.ready().await {
1133 Ok(svc) => svc.call(req).await.map_err(|e| match e {}),
1134 Err(e) => match e {},
1135 }
1136 })
1137 }
1138
1139 fn get_with_context(
1140 &self,
1141 ctx: RequestContext,
1142 arguments: HashMap<String, String>,
1143 ) -> BoxFuture<'_, Result<GetPromptResult>> {
1144 Box::pin(async move {
1145 let req = PromptRequest::new(ctx, arguments);
1146 let mut service = self.service.lock().await.clone();
1147 match service.ready().await {
1148 Ok(svc) => svc.call(req).await.map_err(|e| match e {}),
1149 Err(e) => match e {},
1150 }
1151 })
1152 }
1153}
1154
1155struct ServiceContextHandler {
1157 service: Mutex<BoxPromptService>,
1158}
1159
1160impl PromptHandler for ServiceContextHandler {
1161 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>> {
1162 let ctx = RequestContext::new(RequestId::Number(0));
1163 self.get_with_context(ctx, arguments)
1164 }
1165
1166 fn get_with_context(
1167 &self,
1168 ctx: RequestContext,
1169 arguments: HashMap<String, String>,
1170 ) -> BoxFuture<'_, Result<GetPromptResult>> {
1171 Box::pin(async move {
1172 let req = PromptRequest::new(ctx, arguments);
1173 let mut service = self.service.lock().await.clone();
1174 match service.ready().await {
1175 Ok(svc) => svc.call(req).await.map_err(|e| match e {}),
1176 Err(e) => match e {},
1177 }
1178 })
1179 }
1180
1181 fn uses_context(&self) -> bool {
1182 true
1183 }
1184}
1185
1186pub trait McpPrompt: Send + Sync + 'static {
1248 const NAME: &'static str;
1250 const DESCRIPTION: &'static str;
1252
1253 fn arguments(&self) -> Vec<PromptArgument> {
1255 Vec::new()
1256 }
1257
1258 fn get(
1260 &self,
1261 arguments: HashMap<String, String>,
1262 ) -> impl Future<Output = Result<GetPromptResult>> + Send;
1263
1264 fn into_prompt(self) -> Prompt
1266 where
1267 Self: Sized,
1268 {
1269 let arguments = self.arguments();
1270 let prompt = Arc::new(self);
1271 Prompt {
1272 name: Self::NAME.to_string(),
1273 title: None,
1274 description: Some(Self::DESCRIPTION.to_string()),
1275 icons: None,
1276 arguments,
1277 handler: Some(Arc::new(McpPromptHandler { prompt })),
1278 #[cfg(feature = "stateless")]
1279 mrtr_handler: None,
1280 }
1281 }
1282}
1283
1284struct McpPromptHandler<T: McpPrompt> {
1286 prompt: Arc<T>,
1287}
1288
1289impl<T: McpPrompt> PromptHandler for McpPromptHandler<T> {
1290 fn get(&self, arguments: HashMap<String, String>) -> BoxFuture<'_, Result<GetPromptResult>> {
1291 let prompt = self.prompt.clone();
1292 Box::pin(async move { prompt.get(arguments).await })
1293 }
1294}
1295
1296#[cfg(test)]
1297mod tests {
1298 use super::*;
1299
1300 #[tokio::test]
1301 async fn test_builder_prompt() {
1302 let prompt = PromptBuilder::new("greet")
1303 .description("A greeting prompt")
1304 .required_arg("name", "Name to greet")
1305 .handler(|args| async move {
1306 let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
1307 Ok(GetPromptResult {
1308 description: Some("Greeting".to_string()),
1309 messages: vec![PromptMessage {
1310 role: PromptRole::User,
1311 content: Content::Text {
1312 text: format!("Hello, {}!", name),
1313 annotations: None,
1314 meta: None,
1315 },
1316 meta: None,
1317 }],
1318 meta: None,
1319 })
1320 })
1321 .build();
1322
1323 assert_eq!(prompt.name, "greet");
1324 assert_eq!(prompt.description.as_deref(), Some("A greeting prompt"));
1325 assert_eq!(prompt.arguments.len(), 1);
1326 assert!(prompt.arguments[0].required);
1327
1328 let mut args = HashMap::new();
1329 args.insert("name".to_string(), "Alice".to_string());
1330 let result = prompt.get(args).await.unwrap();
1331
1332 assert_eq!(result.messages.len(), 1);
1333 match &result.messages[0].content {
1334 Content::Text { text, .. } => assert_eq!(text, "Hello, Alice!"),
1335 _ => panic!("Expected text content"),
1336 }
1337 }
1338
1339 #[tokio::test]
1340 async fn test_static_prompt() {
1341 let prompt = PromptBuilder::new("help")
1342 .description("Help prompt")
1343 .user_message("How can I help you today?");
1344
1345 let result = prompt.get(HashMap::new()).await.unwrap();
1346 assert_eq!(result.messages.len(), 1);
1347 match &result.messages[0].content {
1348 Content::Text { text, .. } => assert_eq!(text, "How can I help you today?"),
1349 _ => panic!("Expected text content"),
1350 }
1351 }
1352
1353 #[tokio::test]
1354 async fn test_trait_prompt() {
1355 struct TestPrompt;
1356
1357 impl McpPrompt for TestPrompt {
1358 const NAME: &'static str = "test";
1359 const DESCRIPTION: &'static str = "A test prompt";
1360
1361 fn arguments(&self) -> Vec<PromptArgument> {
1362 vec![PromptArgument {
1363 name: "input".to_string(),
1364 description: Some("Test input".to_string()),
1365 required: true,
1366 }]
1367 }
1368
1369 async fn get(&self, args: HashMap<String, String>) -> Result<GetPromptResult> {
1370 let input = args.get("input").map(|s| s.as_str()).unwrap_or("default");
1371 Ok(GetPromptResult {
1372 description: Some("Test".to_string()),
1373 messages: vec![PromptMessage {
1374 role: PromptRole::User,
1375 content: Content::Text {
1376 text: format!("Input: {}", input),
1377 annotations: None,
1378 meta: None,
1379 },
1380 meta: None,
1381 }],
1382 meta: None,
1383 })
1384 }
1385 }
1386
1387 let prompt = TestPrompt.into_prompt();
1388 assert_eq!(prompt.name, "test");
1389 assert_eq!(prompt.arguments.len(), 1);
1390
1391 let mut args = HashMap::new();
1392 args.insert("input".to_string(), "hello".to_string());
1393 let result = prompt.get(args).await.unwrap();
1394
1395 match &result.messages[0].content {
1396 Content::Text { text, .. } => assert_eq!(text, "Input: hello"),
1397 _ => panic!("Expected text content"),
1398 }
1399 }
1400
1401 #[test]
1402 fn test_prompt_definition() {
1403 let prompt = PromptBuilder::new("test")
1404 .description("Test description")
1405 .required_arg("arg1", "First arg")
1406 .optional_arg("arg2", "Second arg")
1407 .user_message("Test");
1408
1409 let def = prompt.definition();
1410 assert_eq!(def.name, "test");
1411 assert_eq!(def.description.as_deref(), Some("Test description"));
1412 assert_eq!(def.arguments.len(), 2);
1413 assert!(def.arguments[0].required);
1414 assert!(!def.arguments[1].required);
1415 }
1416
1417 #[tokio::test]
1418 async fn test_handler_with_context() {
1419 let prompt = PromptBuilder::new("context_prompt")
1420 .description("A prompt with context")
1421 .handler_with_context(|ctx: RequestContext, args| async move {
1422 let _ = ctx.is_cancelled();
1424 let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
1425 Ok(GetPromptResult {
1426 description: Some("Context prompt".to_string()),
1427 messages: vec![PromptMessage {
1428 role: PromptRole::User,
1429 content: Content::Text {
1430 text: format!("Hello, {}!", name),
1431 annotations: None,
1432 meta: None,
1433 },
1434 meta: None,
1435 }],
1436 meta: None,
1437 })
1438 })
1439 .build();
1440
1441 assert_eq!(prompt.name, "context_prompt");
1442 assert!(prompt.uses_context());
1443
1444 let ctx = RequestContext::new(RequestId::Number(1));
1445 let mut args = HashMap::new();
1446 args.insert("name".to_string(), "Alice".to_string());
1447 let result = prompt.get_with_context(ctx, args).await.unwrap();
1448
1449 match &result.messages[0].content {
1450 Content::Text { text, .. } => assert_eq!(text, "Hello, Alice!"),
1451 _ => panic!("Expected text content"),
1452 }
1453 }
1454
1455 #[tokio::test]
1456 async fn test_prompt_with_timeout_layer() {
1457 use std::time::Duration;
1458 use tower::timeout::TimeoutLayer;
1459
1460 let prompt = PromptBuilder::new("timeout_prompt")
1461 .description("A prompt with timeout")
1462 .handler(|args: HashMap<String, String>| async move {
1463 let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
1464 Ok(GetPromptResult {
1465 description: Some("Timeout prompt".to_string()),
1466 messages: vec![PromptMessage {
1467 role: PromptRole::User,
1468 content: Content::Text {
1469 text: format!("Hello, {}!", name),
1470 annotations: None,
1471 meta: None,
1472 },
1473 meta: None,
1474 }],
1475 meta: None,
1476 })
1477 })
1478 .layer(TimeoutLayer::new(Duration::from_secs(5)));
1479
1480 assert_eq!(prompt.name, "timeout_prompt");
1481
1482 let mut args = HashMap::new();
1483 args.insert("name".to_string(), "Alice".to_string());
1484 let result = prompt.get(args).await.unwrap();
1485
1486 match &result.messages[0].content {
1487 Content::Text { text, .. } => assert_eq!(text, "Hello, Alice!"),
1488 _ => panic!("Expected text content"),
1489 }
1490 }
1491
1492 #[tokio::test]
1493 async fn test_prompt_timeout_expires() {
1494 use std::time::Duration;
1495 use tower::timeout::TimeoutLayer;
1496
1497 let prompt = PromptBuilder::new("slow_prompt")
1498 .description("A slow prompt")
1499 .handler(|_args: HashMap<String, String>| async move {
1500 tokio::time::sleep(Duration::from_secs(1)).await;
1502 Ok(GetPromptResult {
1503 description: Some("Slow prompt".to_string()),
1504 messages: vec![PromptMessage {
1505 role: PromptRole::User,
1506 content: Content::Text {
1507 text: "This should not appear".to_string(),
1508 annotations: None,
1509 meta: None,
1510 },
1511 meta: None,
1512 }],
1513 meta: None,
1514 })
1515 })
1516 .layer(TimeoutLayer::new(Duration::from_millis(50)));
1517
1518 let result = prompt.get(HashMap::new()).await.unwrap();
1519
1520 assert!(result.description.as_ref().unwrap().contains("error"));
1522 match &result.messages[0].content {
1523 Content::Text { text, .. } => {
1524 assert!(text.contains("Error generating prompt"));
1525 }
1526 _ => panic!("Expected text content"),
1527 }
1528 }
1529
1530 #[tokio::test]
1531 async fn test_context_handler_with_layer() {
1532 use std::time::Duration;
1533 use tower::timeout::TimeoutLayer;
1534
1535 let prompt = PromptBuilder::new("context_timeout")
1536 .description("Context prompt with timeout")
1537 .handler_with_context(
1538 |_ctx: RequestContext, args: HashMap<String, String>| async move {
1539 let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
1540 Ok(GetPromptResult {
1541 description: Some("Context timeout".to_string()),
1542 messages: vec![PromptMessage {
1543 role: PromptRole::User,
1544 content: Content::Text {
1545 text: format!("Hello, {}!", name),
1546 annotations: None,
1547 meta: None,
1548 },
1549 meta: None,
1550 }],
1551 meta: None,
1552 })
1553 },
1554 )
1555 .layer(TimeoutLayer::new(Duration::from_secs(5)));
1556
1557 assert_eq!(prompt.name, "context_timeout");
1558 assert!(prompt.uses_context());
1559
1560 let ctx = RequestContext::new(RequestId::Number(1));
1561 let mut args = HashMap::new();
1562 args.insert("name".to_string(), "Bob".to_string());
1563 let result = prompt.get_with_context(ctx, args).await.unwrap();
1564
1565 match &result.messages[0].content {
1566 Content::Text { text, .. } => assert_eq!(text, "Hello, Bob!"),
1567 _ => panic!("Expected text content"),
1568 }
1569 }
1570
1571 #[test]
1572 fn test_prompt_request_construction() {
1573 let args: HashMap<String, String> = [("key".to_string(), "value".to_string())]
1574 .into_iter()
1575 .collect();
1576
1577 let req = PromptRequest::with_arguments(args.clone());
1578 assert_eq!(req.arguments.get("key"), Some(&"value".to_string()));
1579
1580 let ctx = RequestContext::new(RequestId::Number(42));
1581 let req2 = PromptRequest::new(ctx, args);
1582 assert_eq!(req2.arguments.get("key"), Some(&"value".to_string()));
1583 }
1584
1585 #[test]
1586 fn test_prompt_catch_error_clone() {
1587 let handler = PromptHandlerService {
1589 handler: |_args: HashMap<String, String>| async {
1590 Ok::<GetPromptResult, Error>(GetPromptResult {
1591 description: None,
1592 messages: vec![],
1593 meta: None,
1594 })
1595 },
1596 };
1597 let catch_error = PromptCatchError::new(handler);
1598 let _clone = catch_error.clone();
1599 }
1602
1603 #[tokio::test]
1604 async fn test_prompt_handler_with_arguments() {
1605 let prompt = PromptBuilder::new("greet")
1606 .description("Greeting prompt")
1607 .required_arg("name", "Person to greet")
1608 .optional_arg("style", "Greeting style")
1609 .handler(|args: HashMap<String, String>| async move {
1610 let name = args.get("name").map(|s| s.as_str()).unwrap_or("World");
1611 let style = args.get("style").map(|s| s.as_str()).unwrap_or("casual");
1612 let text = match style {
1613 "formal" => format!("Good day, {name}."),
1614 _ => format!("Hey {name}!"),
1615 };
1616 Ok(GetPromptResult::user_message(text))
1617 })
1618 .build();
1619
1620 let mut args = HashMap::new();
1622 args.insert("name".to_string(), "Alice".to_string());
1623 args.insert("style".to_string(), "formal".to_string());
1624 let result = prompt.get(args).await.unwrap();
1625 assert_eq!(result.messages.len(), 1);
1626
1627 let mut args = HashMap::new();
1629 args.insert("name".to_string(), "Bob".to_string());
1630 let result = prompt.get(args).await.unwrap();
1631 assert_eq!(result.messages.len(), 1);
1632 }
1633
1634 #[cfg(feature = "stateless")]
1635 #[tokio::test]
1636 async fn test_mrtr_builder_preserves_input_required_outcome() {
1637 let prompt = PromptBuilder::new("continue")
1638 .mrtr_handler(|_ctx, _args| async move {
1639 Ok(RequestOutcome::input_required(
1640 crate::protocol::InputRequiredResult::new().with_request_state("signed-state"),
1641 ))
1642 })
1643 .build();
1644
1645 let outcome = prompt
1646 .get_outcome_with_context(RequestContext::new(RequestId::Number(1)), HashMap::new())
1647 .await
1648 .unwrap();
1649 assert_eq!(
1650 outcome
1651 .as_input_required()
1652 .and_then(|result| result.request_state.as_deref()),
1653 Some("signed-state")
1654 );
1655 }
1656
1657 #[cfg(feature = "stateless")]
1658 #[tokio::test]
1659 async fn mrtr_prompt_composes_middleware() {
1660 use std::time::Duration;
1661 use tower::timeout::TimeoutLayer;
1662
1663 let prompt = PromptBuilder::new("layered_continue")
1664 .mrtr_handler(|_ctx, _args| async move {
1665 Ok(RequestOutcome::input_required(
1666 crate::protocol::InputRequiredResult::new().with_request_state("layered-state"),
1667 ))
1668 })
1669 .layer(TimeoutLayer::new(Duration::from_secs(1)));
1670
1671 let outcome = prompt
1672 .get_outcome_with_context(RequestContext::new(RequestId::Number(2)), HashMap::new())
1673 .await
1674 .unwrap();
1675 assert_eq!(
1676 outcome
1677 .as_input_required()
1678 .and_then(|result| result.request_state.as_deref()),
1679 Some("layered-state")
1680 );
1681 }
1682
1683 #[tokio::test]
1684 async fn test_prompt_definition_fields() {
1685 let prompt = PromptBuilder::new("test_prompt")
1686 .title("Test Prompt")
1687 .description("A test prompt")
1688 .required_arg("input", "The input")
1689 .optional_arg("format", "Output format")
1690 .handler(|_args: HashMap<String, String>| async move {
1691 Ok(GetPromptResult::user_message("test"))
1692 })
1693 .build();
1694
1695 let def = prompt.definition();
1696 assert_eq!(def.name, "test_prompt");
1697 assert_eq!(def.title.as_deref(), Some("Test Prompt"));
1698 assert_eq!(def.description.as_deref(), Some("A test prompt"));
1699 assert_eq!(def.arguments.len(), 2);
1700 assert!(def.arguments[0].required);
1701 assert!(!def.arguments[1].required);
1702 }
1703
1704 #[tokio::test]
1705 async fn test_prompt_with_context_handler() {
1706 let prompt = PromptBuilder::new("ctx_prompt")
1707 .description("Context-aware prompt")
1708 .handler_with_context(
1709 |ctx: RequestContext, args: HashMap<String, String>| async move {
1710 let _ = ctx;
1711 let name = args.get("name").map(|s| s.as_str()).unwrap_or("default");
1712 Ok(GetPromptResult::user_message(format!("ctx: {name}")))
1713 },
1714 )
1715 .build();
1716
1717 assert!(prompt.uses_context());
1718
1719 let mut args = HashMap::new();
1720 args.insert("name".to_string(), "test".to_string());
1721 let ctx = RequestContext::new(RequestId::Number(1));
1722 let result: std::result::Result<GetPromptResult, Error> =
1723 prompt.get_with_context(ctx, args).await;
1724 assert!(result.is_ok());
1725 assert_eq!(result.unwrap().messages.len(), 1);
1726 }
1727
1728 #[tokio::test]
1729 async fn test_prompt_with_layer_catches_timeout() {
1730 use std::time::Duration;
1731 use tower::timeout::TimeoutLayer;
1732
1733 let prompt = PromptBuilder::new("slow_prompt")
1734 .description("Will timeout")
1735 .handler(|_args: HashMap<String, String>| async move {
1736 tokio::time::sleep(Duration::from_secs(10)).await;
1737 Ok(GetPromptResult::user_message("too late"))
1738 })
1739 .layer(TimeoutLayer::new(Duration::from_millis(10)));
1740
1741 let result = prompt.get(HashMap::new()).await;
1745 match result {
1748 Ok(r) => {
1749 assert!(
1751 !r.messages.is_empty(),
1752 "Expected error message in prompt result"
1753 );
1754 }
1755 Err(_) => {
1756 }
1758 }
1759 }
1760
1761 #[tokio::test]
1762 async fn test_prompt_clone() {
1763 let prompt = PromptBuilder::new("cloneable")
1764 .description("Can be cloned")
1765 .handler(|_args: HashMap<String, String>| async move {
1766 Ok(GetPromptResult::user_message("original"))
1767 })
1768 .build();
1769
1770 let cloned = prompt.clone();
1771 assert_eq!(cloned.name, "cloneable");
1772
1773 let result = cloned.get(HashMap::new()).await.unwrap();
1774 assert_eq!(result.messages.len(), 1);
1775 }
1776}