turbomcp_client/client/operations/prompts.rs
1//! Prompt operations for MCP client
2//!
3//! This module provides prompt-related functionality including listing prompts,
4//! retrieving prompt templates, and supporting parameter substitution.
5
6use std::sync::atomic::Ordering;
7
8use turbomcp_protocol::types::{
9 Cursor, GetPromptRequest, GetPromptResult, ListPromptsRequest, ListPromptsResult, Prompt,
10 PromptInput,
11};
12use turbomcp_protocol::{Error, Result};
13
14/// Maximum number of pagination pages to prevent infinite loops from misbehaving servers.
15const MAX_PAGINATION_PAGES: usize = 1000;
16
17impl<T: turbomcp_transport::Transport + 'static> super::super::core::Client<T> {
18 /// List available prompt templates from the server
19 ///
20 /// Retrieves the complete list of prompt templates that the server provides,
21 /// including all metadata: title, description, and argument schemas. This is
22 /// the MCP-compliant implementation that provides everything needed for UI generation
23 /// and dynamic form creation.
24 ///
25 /// # Returns
26 ///
27 /// Returns a vector of `Prompt` objects containing:
28 /// - `name`: Programmatic identifier
29 /// - `title`: Human-readable display name (optional)
30 /// - `description`: Description of what the prompt does (optional)
31 /// - `arguments`: Array of argument schemas with validation info (optional)
32 ///
33 /// # Errors
34 ///
35 /// Returns an error if:
36 /// - The client is not initialized
37 /// - The server doesn't support prompts
38 /// - The request fails
39 ///
40 /// # Examples
41 ///
42 /// ```rust,no_run
43 /// # use turbomcp_client::Client;
44 /// # use turbomcp_transport::stdio::StdioTransport;
45 /// # async fn example() -> turbomcp_protocol::Result<()> {
46 /// let mut client = Client::new(StdioTransport::new());
47 /// client.initialize().await?;
48 ///
49 /// let prompts = client.list_prompts().await?;
50 /// for prompt in prompts {
51 /// println!("Prompt: {} ({})", prompt.name, prompt.title.unwrap_or("No title".to_string()));
52 /// if let Some(args) = prompt.arguments {
53 /// println!(" Arguments: {:?}", args);
54 /// for arg in args {
55 /// let required = arg.required.unwrap_or(false);
56 /// println!(" - {}: {} (required: {})", arg.name,
57 /// arg.description.unwrap_or("No description".to_string()), required);
58 /// }
59 /// }
60 /// }
61 /// # Ok(())
62 /// # }
63 /// ```
64 pub async fn list_prompts(&self) -> Result<Vec<Prompt>> {
65 if !self.inner.initialized.load(Ordering::Relaxed) {
66 return Err(Error::invalid_request("Client not initialized"));
67 }
68
69 let mut all_prompts = Vec::new();
70 let mut cursor = None;
71 for _ in 0..MAX_PAGINATION_PAGES {
72 let result = self.list_prompts_paginated(cursor).await?;
73 let page_empty = result.prompts.is_empty();
74 all_prompts.extend(result.prompts);
75 match result.next_cursor {
76 Some(c) if !page_empty => cursor = Some(c),
77 _ => break,
78 }
79 }
80 Ok(all_prompts)
81 }
82
83 /// List prompts with pagination support
84 ///
85 /// Returns the full `ListPromptsResult` including `next_cursor` for manual
86 /// pagination control. Use `list_prompts()` for automatic pagination.
87 ///
88 /// # Arguments
89 ///
90 /// * `cursor` - Optional cursor from a previous `ListPromptsResult::next_cursor`
91 pub async fn list_prompts_paginated(
92 &self,
93 cursor: Option<Cursor>,
94 ) -> Result<ListPromptsResult> {
95 if !self.inner.initialized.load(Ordering::Relaxed) {
96 return Err(Error::invalid_request("Client not initialized"));
97 }
98
99 let request = ListPromptsRequest {
100 cursor,
101 _meta: None,
102 };
103 let params = if request.cursor.is_some() {
104 Some(serde_json::to_value(&request)?)
105 } else {
106 None
107 };
108 self.inner.protocol.request("prompts/list", params).await
109 }
110
111 /// Get a specific prompt template with argument support
112 ///
113 /// Retrieves a specific prompt template from the server with support for
114 /// parameter substitution. When arguments are provided, the server will
115 /// substitute them into the prompt template using {parameter} syntax.
116 ///
117 /// This is the MCP-compliant implementation that supports the full protocol specification.
118 ///
119 /// # Arguments
120 ///
121 /// * `name` - The name of the prompt to retrieve
122 /// * `arguments` - Optional parameters for template substitution
123 ///
124 /// # Returns
125 ///
126 /// Returns `GetPromptResult` containing the prompt template with parameters substituted.
127 ///
128 /// # Errors
129 ///
130 /// Returns an error if:
131 /// - The client is not initialized
132 /// - The prompt name is empty
133 /// - The prompt doesn't exist
134 /// - Required arguments are missing
135 /// - Argument types don't match schema
136 /// - The request fails
137 ///
138 /// # Examples
139 ///
140 /// ```rust,no_run
141 /// # use turbomcp_client::Client;
142 /// # use turbomcp_transport::stdio::StdioTransport;
143 /// # use turbomcp_protocol::types::PromptInput;
144 /// # use std::collections::HashMap;
145 /// # async fn example() -> turbomcp_protocol::Result<()> {
146 /// let mut client = Client::new(StdioTransport::new());
147 /// client.initialize().await?;
148 ///
149 /// // Get prompt without arguments (template form)
150 /// let template = client.get_prompt("greeting", None).await?;
151 /// println!("Template has {} messages", template.messages.len());
152 ///
153 /// // Get prompt with arguments (substituted form)
154 /// let mut args = HashMap::new();
155 /// args.insert("name".to_string(), serde_json::Value::String("Alice".to_string()));
156 /// args.insert("greeting".to_string(), serde_json::Value::String("Hello".to_string()));
157 ///
158 /// let result = client.get_prompt("greeting", Some(args)).await?;
159 /// println!("Generated prompt with {} messages", result.messages.len());
160 /// # Ok(())
161 /// # }
162 /// ```
163 pub async fn get_prompt(
164 &self,
165 name: &str,
166 arguments: Option<PromptInput>,
167 ) -> Result<GetPromptResult> {
168 if !self.inner.initialized.load(Ordering::Relaxed) {
169 return Err(Error::invalid_request("Client not initialized"));
170 }
171
172 if name.is_empty() {
173 return Err(Error::invalid_request("Prompt name cannot be empty"));
174 }
175
176 // Send prompts/get request with full argument support
177 let request = GetPromptRequest {
178 name: name.to_string(),
179 arguments, // Support for parameter substitution
180 _meta: None,
181 };
182
183 self.inner
184 .protocol
185 .request(
186 "prompts/get",
187 Some(serde_json::to_value(request).map_err(|e| {
188 Error::internal(format!("Failed to serialize prompt request: {}", e))
189 })?),
190 )
191 .await
192 }
193}