Skip to main content

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}