diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-16 03:59:15 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-16 03:59:15 +0200 |
| commit | c578f367d81ce035c1f7b9c55e38101753e99de5 (patch) | |
| tree | 12ba8091731eabf6af1ee4d1f2f35a67c9481b4c /internal/mcp/handlers_tool.go | |
| parent | 52938e05c1ab250cae1c19c29eaa050351559b3b (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.go | 335 |
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, ¶ms); 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"}, + }, + } +} |
