manager.vala 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. * Copyright (C) 2025 Mcp-Vala Project
  3. *
  4. * This library is free software; you can redistribute it and/or
  5. * modify it under the terms of the GNU Lesser General Public
  6. * License as published by the Free Software Foundation; either
  7. * version 2.1 of the License, or (at your option) any later version.
  8. *
  9. * This library is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  12. * Lesser General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU Lesser General Public
  15. * License along with this library; if not, write to the Free Software
  16. * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  17. *
  18. * Author: Mcp-Vala Project
  19. */
  20. /**
  21. * Prompt manager for MCP protocol.
  22. *
  23. * This class manages prompt templates and handles prompt-related
  24. * JSON-RPC method calls.
  25. */
  26. namespace Mcp.Prompts {
  27. /**
  28. * Prompt manager implementation.
  29. */
  30. public class Manager : GLib.Object {
  31. private HashTable<string, Template> templates;
  32. /**
  33. * Signal emitted when the prompt list changes.
  34. */
  35. public signal void list_changed ();
  36. /**
  37. * Creates a new Manager.
  38. */
  39. public Manager () {
  40. templates = new HashTable<string, Template> (str_hash, str_equal);
  41. }
  42. /**
  43. * Registers a prompt template.
  44. *
  45. * @param name The prompt name
  46. * @param template The template implementation
  47. * @throws Error If registration fails
  48. */
  49. public void register_template (string name, Template template) throws Error {
  50. if (name == null || name.strip () == "") {
  51. throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt name cannot be empty or null. Please provide a valid name for the prompt template.");
  52. }
  53. if (templates.contains (name)) {
  54. throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt '%s' is already registered. Use a different name or unregister the existing prompt first.".printf (name));
  55. }
  56. templates.insert (name, template);
  57. list_changed ();
  58. }
  59. /**
  60. * Unregisters a prompt template.
  61. *
  62. * @param name The prompt name to unregister
  63. * @throws Error If unregistration fails
  64. */
  65. public void unregister_template (string name) throws Error {
  66. if (!templates.contains (name)) {
  67. throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt '%s' not found. Cannot unregister a prompt that doesn't exist. Check the name and try again.".printf (name));
  68. }
  69. templates.remove (name);
  70. list_changed ();
  71. }
  72. /**
  73. * Gets a registered prompt template.
  74. *
  75. * @param name The prompt name
  76. * @return The template or null if not found
  77. */
  78. public Template? get_template (string name) {
  79. return templates.lookup (name);
  80. }
  81. /**
  82. * Gets all registered prompt templates.
  83. *
  84. * @return A hash table of all templates
  85. */
  86. public HashTable<string, Template> get_templates () {
  87. return templates;
  88. }
  89. /**
  90. * Lists all registered prompt names.
  91. *
  92. * @return A list of prompt names
  93. */
  94. public Gee.ArrayList<string> list_prompt_names () {
  95. var names = new Gee.ArrayList<string> ();
  96. foreach (var name in templates.get_keys ()) {
  97. names.add (name);
  98. }
  99. return names;
  100. }
  101. /**
  102. * Checks if a prompt is registered.
  103. *
  104. * @param name The prompt name
  105. * @return True if the prompt is registered
  106. */
  107. public bool has_prompt (string name) {
  108. return templates.contains (name);
  109. }
  110. /**
  111. * Handles the prompts/list JSON-RPC method.
  112. *
  113. * @param params The method parameters
  114. * @return The response result
  115. * @throws Error If handling fails
  116. */
  117. public async Variant handle_list (Variant @params) throws Error {
  118. // Extract cursor parameter if present
  119. string? cursor = null;
  120. if (@params.lookup_value ("cursor", null) != null) {
  121. cursor = @params.lookup_value ("cursor", VariantType.STRING).get_string ();
  122. }
  123. // Get all prompt definitions from all templates
  124. var all_prompts = new Gee.ArrayList<Mcp.Prompts.Types.PromptDefinition> ();
  125. foreach (var name in templates.get_keys ()) {
  126. var template = templates.lookup (name);
  127. try {
  128. var definition = template.get_definition ();
  129. all_prompts.add (definition);
  130. } catch (Error e) {
  131. // Continue with other templates
  132. }
  133. }
  134. // Sort prompts by name for consistent pagination
  135. all_prompts.sort ((a, b) => {
  136. return strcmp (a.name, b.name);
  137. });
  138. // Pagination settings
  139. const int PAGE_SIZE = 10;
  140. int start_index = 0;
  141. // Parse cursor to determine starting position
  142. if (cursor != null) {
  143. // Simple cursor implementation: cursor is the page number as a string
  144. // Default to 0 if parsing fails
  145. start_index = int.parse (cursor) * PAGE_SIZE;
  146. if (start_index < 0) {
  147. start_index = 0;
  148. }
  149. }
  150. // Calculate the end index for this page
  151. int end_index = int.min (start_index + PAGE_SIZE, all_prompts.size);
  152. // Get the prompts for this page
  153. var page_prompts = new Gee.ArrayList<Mcp.Prompts.Types.PromptDefinition> ();
  154. for (int i = start_index; i < end_index; i++) {
  155. page_prompts.add (all_prompts[i]);
  156. }
  157. // Build response as Variant using utility functions
  158. var result_builder = Mcp.Types.VariantUtils.new_dict_builder ();
  159. // Serialize prompts array
  160. var prompts_builder = Mcp.Types.VariantUtils.new_dict_array_builder ();
  161. foreach (var prompt in page_prompts) {
  162. prompts_builder.add_value (prompt.to_variant ());
  163. }
  164. result_builder.add ("{sv}", "prompts", prompts_builder.end ());
  165. // Add nextCursor if there are more prompts
  166. if (end_index < all_prompts.size) {
  167. // Next cursor is the next page number
  168. int next_page = start_index / PAGE_SIZE + 1;
  169. result_builder.add ("{sv}", "nextCursor", new Variant.string (next_page.to_string ()));
  170. }
  171. return result_builder.end ();
  172. }
  173. /**
  174. * Handles the prompts/get JSON-RPC method.
  175. *
  176. * @param params The method parameters
  177. * @return The response result
  178. * @throws Error If handling fails
  179. */
  180. public async Variant handle_get (Variant @params) throws Error {
  181. if (@params.lookup_value ("name", null) == null) {
  182. throw new Mcp.Core.McpError.INVALID_PARAMS ("Missing required 'name' parameter. The prompts/get method requires a prompt name to retrieve.");
  183. }
  184. string name = @params.lookup_value ("name", VariantType.STRING).get_string ();
  185. if (name.strip () == "") {
  186. throw new Mcp.Core.McpError.INVALID_PARAMS ("The 'name' parameter cannot be empty. Please provide a valid prompt name.");
  187. }
  188. Variant? arguments = null;
  189. if (@params.lookup_value ("arguments", null) != null) {
  190. arguments = @params.lookup_value ("arguments", VariantType.VARDICT);
  191. }
  192. Template? template = templates.lookup (name);
  193. if (template == null) {
  194. throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt '%s' not found. Verify the prompt name is correct and that it has been registered.".printf (name));
  195. }
  196. try {
  197. var result = yield template.get_prompt (arguments);
  198. return result.to_variant ();
  199. } catch (Error e) {
  200. throw new Mcp.Core.McpError.INTERNAL_ERROR ("Failed to generate prompt '%s': %s".printf (name, e.message));
  201. }
  202. }
  203. /**
  204. * Sends a notification that the prompt list has changed.
  205. *
  206. * This method is called automatically when templates are registered
  207. * or unregistered, but can also be called manually.
  208. */
  209. public void notify_list_changed () {
  210. list_changed ();
  211. }
  212. /**
  213. * Validates a prompt template before registration.
  214. *
  215. * @param template The template to validate
  216. * @throws Error If validation fails
  217. */
  218. private void validate_template (Template template) throws Error {
  219. if (template == null) {
  220. throw new Mcp.Core.McpError.INVALID_PARAMS ("Template cannot be null. Please provide a valid Template instance.");
  221. }
  222. try {
  223. var definition = template.get_definition ();
  224. if (definition == null) {
  225. throw new Mcp.Core.McpError.INVALID_PARAMS ("Template definition cannot be null. The template must return a valid PromptDefinition.");
  226. }
  227. if (definition.name == null || definition.name.strip () == "") {
  228. throw new Mcp.Core.McpError.INVALID_PARAMS ("Template name cannot be empty or null. Please provide a valid name for the prompt template.");
  229. }
  230. // Validate arguments
  231. if (definition.arguments != null) {
  232. var argument_names = new Gee.HashSet<string> ();
  233. foreach (var arg in definition.arguments) {
  234. if (arg.name == null || arg.name.strip () == "") {
  235. throw new Mcp.Core.McpError.INVALID_PARAMS ("Argument name cannot be empty or null. All arguments must have valid names.");
  236. }
  237. if (argument_names.contains (arg.name)) {
  238. throw new Mcp.Core.McpError.INVALID_PARAMS ("Duplicate argument name '%s'. Each argument must have a unique name.".printf (arg.name));
  239. }
  240. argument_names.add (arg.name);
  241. }
  242. }
  243. } catch (Error e) {
  244. throw new Mcp.Core.McpError.INVALID_PARAMS ("Template validation failed: %s. Please check the template implementation and fix any issues.".printf (e.message));
  245. }
  246. }
  247. }
  248. }