summaryrefslogtreecommitdiff
path: root/internal/mcp/handlers_tool.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-16 03:59:15 +0200
committerPaul Buetow <paul@buetow.org>2026-03-16 03:59:15 +0200
commitc578f367d81ce035c1f7b9c55e38101753e99de5 (patch)
tree12ba8091731eabf6af1ee4d1f2f35a67c9481b4c /internal/mcp/handlers_tool.go
parent52938e05c1ab250cae1c19c29eaa050351559b3b (diff)
Split mcp/server.go (962L) into server, handlers_prompt, handlers_tool
server.go (223L): core lifecycle and routing handlers_prompt.go (420L): prompt CRUD handlers handlers_tool.go (335L): tool handlers and schema builders Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/mcp/handlers_tool.go')
-rw-r--r--internal/mcp/handlers_tool.go335
1 files changed, 335 insertions, 0 deletions
diff --git a/internal/mcp/handlers_tool.go b/internal/mcp/handlers_tool.go
new file mode 100644
index 0000000..759de9e
--- /dev/null
+++ b/internal/mcp/handlers_tool.go
@@ -0,0 +1,335 @@
+// Summary: MCP tool-related handler methods for listing tools, calling tools,
+// and JSON Schema definitions for tool inputs.
+package mcp
+
+import (
+ "encoding/json"
+ "fmt"
+)
+
+// handleToolsList processes the tools/list request.
+// Returns available tools for prompt management operations.
+func (s *Server) handleToolsList(req Request) {
+ s.mu.RLock()
+ if !s.initialized {
+ s.mu.RUnlock()
+ s.sendError(req.ID, ErrCodeInvalidRequest, "Server not initialized")
+ return
+ }
+ s.mu.RUnlock()
+
+ // Define 3 tools for prompt management
+ tools := []Tool{
+ s.makeCreatePromptTool(),
+ s.makeUpdatePromptTool(),
+ s.makeDeletePromptTool(),
+ }
+
+ result := ListToolsResult{
+ Tools: tools,
+ NextCursor: "", // No pagination needed for 3 tools
+ }
+
+ s.sendResponse(req.ID, result)
+}
+
+// makeCreatePromptTool returns the JSON Schema definition for create_prompt tool.
+func (s *Server) makeCreatePromptTool() Tool {
+ return Tool{
+ Name: "create_prompt",
+ Description: "Creates a new custom prompt template that can be invoked via the MCP prompts capability. The prompt is saved to user.jsonl and becomes immediately available for use.",
+ InputSchema: s.buildCreatePromptSchema(),
+ }
+}
+
+// buildCreatePromptSchema constructs the JSON Schema for create_prompt tool inputs.
+func (s *Server) buildCreatePromptSchema() map[string]interface{} {
+ return map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "name": map[string]interface{}{
+ "type": "string",
+ "description": "Unique identifier for the prompt (lowercase, underscores allowed)",
+ },
+ "title": map[string]interface{}{
+ "type": "string",
+ "description": "Human-readable display name for the prompt",
+ },
+ "description": map[string]interface{}{
+ "type": "string",
+ "description": "Detailed explanation of what the prompt does",
+ },
+ "arguments": buildArgumentArraySchema(),
+ "messages": buildMessageArraySchema(),
+ "tags": map[string]interface{}{
+ "type": "array",
+ "description": "Optional tags for categorizing the prompt",
+ "items": map[string]interface{}{"type": "string"},
+ },
+ },
+ "required": []string{"name", "title", "messages"},
+ }
+}
+
+// makeUpdatePromptTool returns the JSON Schema definition for update_prompt tool.
+func (s *Server) makeUpdatePromptTool() Tool {
+ return Tool{
+ Name: "update_prompt",
+ Description: "Updates an existing custom prompt template. Only custom prompts (stored in user.jsonl) can be updated; built-in prompts cannot be modified.",
+ InputSchema: s.buildUpdatePromptSchema(),
+ }
+}
+
+// buildUpdatePromptSchema constructs the JSON Schema for update_prompt tool inputs.
+func (s *Server) buildUpdatePromptSchema() map[string]interface{} {
+ return map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "name": map[string]interface{}{
+ "type": "string",
+ "description": "Name of the existing prompt to update",
+ },
+ "title": map[string]interface{}{
+ "type": "string",
+ "description": "New display name (optional, only if changing)",
+ },
+ "description": map[string]interface{}{
+ "type": "string",
+ "description": "New description (optional, only if changing)",
+ },
+ "arguments": buildArgumentArraySchema(),
+ "messages": buildMessageArraySchema(),
+ "tags": map[string]interface{}{
+ "type": "array",
+ "description": "New tags array (optional, replaces existing if provided)",
+ "items": map[string]interface{}{"type": "string"},
+ },
+ },
+ "required": []string{"name"},
+ }
+}
+
+// makeDeletePromptTool returns the JSON Schema definition for delete_prompt tool.
+func (s *Server) makeDeletePromptTool() Tool {
+ return Tool{
+ Name: "delete_prompt",
+ Description: "Deletes a custom prompt template. Only custom prompts (stored in user.jsonl) can be deleted; built-in prompts cannot be removed.",
+ InputSchema: map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "name": map[string]interface{}{
+ "type": "string",
+ "description": "Name of the prompt to delete",
+ },
+ },
+ "required": []string{"name"},
+ },
+ }
+}
+
+// handleToolsCall processes the tools/call request.
+// Dispatches to appropriate tool wrapper based on tool name.
+func (s *Server) handleToolsCall(req Request) {
+ s.mu.RLock()
+ if !s.initialized {
+ s.mu.RUnlock()
+ s.sendError(req.ID, ErrCodeInvalidRequest, "Server not initialized")
+ return
+ }
+ s.mu.RUnlock()
+
+ var params CallToolRequest
+ if err := json.Unmarshal(req.Params, &params); err != nil {
+ s.sendError(req.ID, ErrCodeInvalidParams, "Invalid tools/call params")
+ return
+ }
+
+ // Dispatch to appropriate tool wrapper
+ switch params.Name {
+ case "create_prompt":
+ s.callCreatePromptTool(req.ID, params.Arguments)
+ case "update_prompt":
+ s.callUpdatePromptTool(req.ID, params.Arguments)
+ case "delete_prompt":
+ s.callDeletePromptTool(req.ID, params.Arguments)
+ default:
+ s.sendToolError(req.ID, fmt.Sprintf("Unknown tool: %s", params.Name))
+ }
+}
+
+// callCreatePromptTool executes the create_prompt tool.
+// Converts map arguments to CreatePromptRequest and delegates to store.Create.
+func (s *Server) callCreatePromptTool(id any, args map[string]interface{}) {
+ // Convert map to CreatePromptRequest
+ params, err := convertToCreatePromptRequest(args)
+ if err != nil {
+ s.sendToolError(id, fmt.Sprintf("Invalid arguments: %v", err))
+ return
+ }
+
+ // Validate required fields
+ if err := validateCreateParams(params); err != nil {
+ s.sendToolError(id, err.Error())
+ return
+ }
+
+ // Build prompt and create
+ prompt := buildPromptFromCreateParams(params)
+ if err := s.store.Create(prompt); err != nil {
+ s.logger.Printf("create prompt error: %v", err)
+ s.sendToolError(id, fmt.Sprintf("Failed to create prompt: %v", err))
+ return
+ }
+
+ s.logger.Printf("created prompt via tool: %s", params.Name)
+
+ // Sync to slash commands if enabled
+ if s.syncer != nil {
+ if err := s.syncer.SyncCreate(prompt); err != nil {
+ s.logger.Printf("slash command sync failed: %v", err)
+ }
+ }
+
+ s.sendToolSuccess(id, fmt.Sprintf("Successfully created prompt: %s", params.Name))
+
+ // Notify clients that the prompt list has changed
+ s.sendPromptsListChangedNotification()
+}
+
+// callUpdatePromptTool executes the update_prompt tool.
+// Converts map arguments to UpdatePromptRequest and delegates to store.Update.
+func (s *Server) callUpdatePromptTool(id any, args map[string]interface{}) {
+ // Convert map to UpdatePromptRequest
+ params, err := convertToUpdatePromptRequest(args)
+ if err != nil {
+ s.sendToolError(id, fmt.Sprintf("Invalid arguments: %v", err))
+ return
+ }
+
+ if params.Name == "" {
+ s.sendToolError(id, "Prompt name is required")
+ return
+ }
+
+ // Get existing prompt
+ existing, err := s.store.Get(params.Name)
+ if err != nil {
+ s.logger.Printf("get prompt error: %v", err)
+ s.sendToolError(id, fmt.Sprintf("Prompt not found: %s", params.Name))
+ return
+ }
+
+ // Apply updates
+ applyPromptUpdates(existing, params)
+
+ if err := s.store.Update(existing); err != nil {
+ s.logger.Printf("update prompt error: %v", err)
+ s.sendToolError(id, fmt.Sprintf("Failed to update prompt: %v", err))
+ return
+ }
+
+ s.logger.Printf("updated prompt via tool: %s", params.Name)
+
+ // Sync to slash commands if enabled
+ if s.syncer != nil {
+ if err := s.syncer.SyncUpdate(existing); err != nil {
+ s.logger.Printf("slash command sync failed: %v", err)
+ }
+ }
+
+ s.sendToolSuccess(id, fmt.Sprintf("Successfully updated prompt: %s", params.Name))
+
+ // Notify clients that the prompt list has changed
+ s.sendPromptsListChangedNotification()
+}
+
+// callDeletePromptTool executes the delete_prompt tool.
+// Extracts name from arguments and delegates to store.Delete.
+func (s *Server) callDeletePromptTool(id any, args map[string]interface{}) {
+ // Extract name from arguments
+ name, ok := args["name"].(string)
+ if !ok || name == "" {
+ s.sendToolError(id, "Prompt name is required")
+ return
+ }
+
+ if err := s.store.Delete(name); err != nil {
+ s.logger.Printf("delete prompt error: %v", err)
+ s.sendToolError(id, fmt.Sprintf("Failed to delete prompt: %v", err))
+ return
+ }
+
+ s.logger.Printf("deleted prompt via tool: %s", name)
+
+ // Delete slash command file if enabled
+ if s.syncer != nil {
+ if err := s.syncer.Delete(name); err != nil {
+ s.logger.Printf("slash command sync delete failed: %v", err)
+ }
+ }
+
+ s.sendToolSuccess(id, fmt.Sprintf("Successfully deleted prompt: %s", name))
+
+ // Notify clients that the prompt list has changed
+ s.sendPromptsListChangedNotification()
+}
+
+// buildArgumentArraySchema creates the JSON Schema for prompt arguments array.
+func buildArgumentArraySchema() map[string]interface{} {
+ return map[string]interface{}{
+ "type": "array",
+ "description": "Template variables that can be substituted when invoking the prompt",
+ "items": map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "name": map[string]interface{}{
+ "type": "string",
+ "description": "Argument name (used in {{name}} placeholders)",
+ },
+ "description": map[string]interface{}{
+ "type": "string",
+ "description": "Description of the argument's purpose",
+ },
+ "required": map[string]interface{}{
+ "type": "boolean",
+ "description": "Whether this argument must be provided",
+ },
+ },
+ "required": []string{"name"},
+ },
+ }
+}
+
+// buildMessageArraySchema creates the JSON Schema for prompt messages array.
+func buildMessageArraySchema() map[string]interface{} {
+ return map[string]interface{}{
+ "type": "array",
+ "description": "Array of message objects (role + content) that form the prompt",
+ "items": map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "role": map[string]interface{}{
+ "type": "string",
+ "description": "Message role: 'user' or 'assistant'",
+ "enum": []string{"user", "assistant"},
+ },
+ "content": map[string]interface{}{
+ "type": "object",
+ "properties": map[string]interface{}{
+ "type": map[string]interface{}{
+ "type": "string",
+ "description": "Content type (currently only 'text' is supported)",
+ "enum": []string{"text"},
+ },
+ "text": map[string]interface{}{
+ "type": "string",
+ "description": "The message text (can include {{arg}} placeholders)",
+ },
+ },
+ "required": []string{"type", "text"},
+ },
+ },
+ "required": []string{"role", "content"},
+ },
+ }
+}