| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- /*
- * Copyright (C) 2025 Mcp-Vala Project
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- *
- * Author: Mcp-Vala Project
- */
- /**
- * Prompt manager for MCP protocol.
- *
- * This class manages prompt templates and handles prompt-related
- * JSON-RPC method calls.
- */
- namespace Mcp.Prompts {
- /**
- * Prompt manager implementation.
- */
- public class Manager : GLib.Object {
- private HashTable<string, Template> templates;
-
- /**
- * Signal emitted when the prompt list changes.
- */
- public signal void list_changed ();
-
- /**
- * Creates a new Manager.
- */
- public Manager () {
- templates = new HashTable<string, Template> (str_hash, str_equal);
- }
-
- /**
- * Registers a prompt template.
- *
- * @param name The prompt name
- * @param template The template implementation
- * @throws Error If registration fails
- */
- public void register_template (string name, Template template) throws Error {
- if (name == null || name.strip () == "") {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt name cannot be empty or null. Please provide a valid name for the prompt template.");
- }
-
- if (templates.contains (name)) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Prompt '%s' is already registered. Use a different name or unregister the existing prompt first.".printf (name));
- }
-
- templates.insert (name, template);
- list_changed ();
- }
-
- /**
- * Unregisters a prompt template.
- *
- * @param name The prompt name to unregister
- * @throws Error If unregistration fails
- */
- public void unregister_template (string name) throws Error {
- if (!templates.contains (name)) {
- 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));
- }
-
- templates.remove (name);
- list_changed ();
- }
-
- /**
- * Gets a registered prompt template.
- *
- * @param name The prompt name
- * @return The template or null if not found
- */
- public Template? get_template (string name) {
- return templates.lookup (name);
- }
-
- /**
- * Gets all registered prompt templates.
- *
- * @return A hash table of all templates
- */
- public HashTable<string, Template> get_templates () {
- return templates;
- }
-
- /**
- * Lists all registered prompt names.
- *
- * @return A list of prompt names
- */
- public Gee.ArrayList<string> list_prompt_names () {
- var names = new Gee.ArrayList<string> ();
- foreach (var name in templates.get_keys ()) {
- names.add (name);
- }
- return names;
- }
-
- /**
- * Checks if a prompt is registered.
- *
- * @param name The prompt name
- * @return True if the prompt is registered
- */
- public bool has_prompt (string name) {
- return templates.contains (name);
- }
-
- /**
- * Handles the prompts/list JSON-RPC method.
- *
- * @param params The method parameters
- * @return The response result
- * @throws Error If handling fails
- */
- public async Variant handle_list (Variant @params) throws Error {
- // Extract cursor parameter if present
- string? cursor = null;
- if (@params.lookup_value ("cursor", null) != null) {
- cursor = @params.lookup_value ("cursor", VariantType.STRING).get_string ();
- }
-
- // Get all prompt definitions from all templates
- var all_prompts = new Gee.ArrayList<Mcp.Prompts.Types.PromptDefinition> ();
- foreach (var name in templates.get_keys ()) {
- var template = templates.lookup (name);
-
- try {
- var definition = template.get_definition ();
- all_prompts.add (definition);
- } catch (Error e) {
- // Continue with other templates
- }
- }
-
- // Sort prompts by name for consistent pagination
- all_prompts.sort ((a, b) => {
- return strcmp (a.name, b.name);
- });
-
- // Pagination settings
- const int PAGE_SIZE = 10;
- int start_index = 0;
-
- // Parse cursor to determine starting position
- if (cursor != null) {
- // Simple cursor implementation: cursor is the page number as a string
- // Default to 0 if parsing fails
- start_index = int.parse (cursor) * PAGE_SIZE;
- if (start_index < 0) {
- start_index = 0;
- }
- }
-
- // Calculate the end index for this page
- int end_index = int.min (start_index + PAGE_SIZE, all_prompts.size);
-
- // Get the prompts for this page
- var page_prompts = new Gee.ArrayList<Mcp.Prompts.Types.PromptDefinition> ();
- for (int i = start_index; i < end_index; i++) {
- page_prompts.add (all_prompts[i]);
- }
-
- // Build response as Variant using utility functions
- var result_builder = Mcp.Types.VariantUtils.new_dict_builder ();
-
- // Serialize prompts array
- var prompts_builder = Mcp.Types.VariantUtils.new_dict_array_builder ();
- foreach (var prompt in page_prompts) {
- prompts_builder.add_value (prompt.to_variant ());
- }
- result_builder.add ("{sv}", "prompts", prompts_builder.end ());
-
- // Add nextCursor if there are more prompts
- if (end_index < all_prompts.size) {
- // Next cursor is the next page number
- int next_page = start_index / PAGE_SIZE + 1;
- result_builder.add ("{sv}", "nextCursor", new Variant.string (next_page.to_string ()));
- }
-
- return result_builder.end ();
- }
-
- /**
- * Handles the prompts/get JSON-RPC method.
- *
- * @param params The method parameters
- * @return The response result
- * @throws Error If handling fails
- */
- public async Variant handle_get (Variant @params) throws Error {
- if (@params.lookup_value ("name", null) == null) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Missing required 'name' parameter. The prompts/get method requires a prompt name to retrieve.");
- }
-
- string name = @params.lookup_value ("name", VariantType.STRING).get_string ();
-
- if (name.strip () == "") {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("The 'name' parameter cannot be empty. Please provide a valid prompt name.");
- }
-
- Variant? arguments = null;
- if (@params.lookup_value ("arguments", null) != null) {
- arguments = @params.lookup_value ("arguments", VariantType.VARDICT);
- }
-
- Template? template = templates.lookup (name);
- if (template == null) {
- 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));
- }
-
- try {
- var result = yield template.get_prompt (arguments);
- return result.to_variant ();
- } catch (Error e) {
- throw new Mcp.Core.McpError.INTERNAL_ERROR ("Failed to generate prompt '%s': %s".printf (name, e.message));
- }
- }
-
- /**
- * Sends a notification that the prompt list has changed.
- *
- * This method is called automatically when templates are registered
- * or unregistered, but can also be called manually.
- */
- public void notify_list_changed () {
- list_changed ();
- }
-
- /**
- * Validates a prompt template before registration.
- *
- * @param template The template to validate
- * @throws Error If validation fails
- */
- private void validate_template (Template template) throws Error {
- if (template == null) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Template cannot be null. Please provide a valid Template instance.");
- }
-
- try {
- var definition = template.get_definition ();
- if (definition == null) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Template definition cannot be null. The template must return a valid PromptDefinition.");
- }
-
- if (definition.name == null || definition.name.strip () == "") {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Template name cannot be empty or null. Please provide a valid name for the prompt template.");
- }
-
- // Validate arguments
- if (definition.arguments != null) {
- var argument_names = new Gee.HashSet<string> ();
- foreach (var arg in definition.arguments) {
- if (arg.name == null || arg.name.strip () == "") {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Argument name cannot be empty or null. All arguments must have valid names.");
- }
-
- if (argument_names.contains (arg.name)) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Duplicate argument name '%s'. Each argument must have a unique name.".printf (arg.name));
- }
-
- argument_names.add (arg.name);
- }
- }
- } catch (Error e) {
- throw new Mcp.Core.McpError.INVALID_PARAMS ("Template validation failed: %s. Please check the template implementation and fix any issues.".printf (e.message));
- }
- }
- }
- }
|