blob: fc3bb7c5880e17cd76290727d2702c384646dbe7 [file] [log] [blame]
Akrone33db5c2025-06-12 15:31:10 +02001package tools
2
3import (
4 "context"
5 "fmt"
6 "sync"
7
8 "github.com/mark3labs/mcp-go/mcp"
9 "github.com/rs/zerolog/log"
10)
11
12// Tool represents an MCP tool that can be registered and executed
13type Tool interface {
14 // Name returns the unique name of the tool
15 Name() string
16
17 // Description returns a human-readable description of the tool
18 Description() string
19
20 // InputSchema returns the JSON schema for the tool's input parameters
Akron708f3912025-06-17 12:26:02 +020021 InputSchema() map[string]any
Akrone33db5c2025-06-12 15:31:10 +020022
23 // Execute runs the tool with the given arguments and returns the result
24 Execute(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
25}
26
27// Registry manages the collection of available MCP tools
28type Registry struct {
29 mu sync.RWMutex
30 tools map[string]Tool
31}
32
33// NewRegistry creates a new tools registry
34func NewRegistry() *Registry {
35 return &Registry{
36 tools: make(map[string]Tool),
37 }
38}
39
40// Register adds a tool to the registry
41func (r *Registry) Register(tool Tool) error {
42 if tool == nil {
43 return fmt.Errorf("cannot register nil tool")
44 }
45
46 name := tool.Name()
47 if name == "" {
48 return fmt.Errorf("tool name cannot be empty")
49 }
50
51 r.mu.Lock()
52 defer r.mu.Unlock()
53
54 if _, exists := r.tools[name]; exists {
55 return fmt.Errorf("tool with name %q already registered", name)
56 }
57
58 r.tools[name] = tool
59 log.Debug().Str("tool", name).Msg("Tool registered successfully")
60
61 return nil
62}
63
64// Unregister removes a tool from the registry
65func (r *Registry) Unregister(name string) error {
66 r.mu.Lock()
67 defer r.mu.Unlock()
68
69 if _, exists := r.tools[name]; !exists {
70 return fmt.Errorf("tool with name %q not found", name)
71 }
72
73 delete(r.tools, name)
74 log.Debug().Str("tool", name).Msg("Tool unregistered successfully")
75
76 return nil
77}
78
79// Get retrieves a tool by name
80func (r *Registry) Get(name string) (Tool, bool) {
81 r.mu.RLock()
82 defer r.mu.RUnlock()
83
84 tool, exists := r.tools[name]
85 return tool, exists
86}
87
88// List returns all registered tools as MCP tool definitions
89func (r *Registry) List() []mcp.Tool {
90 r.mu.RLock()
91 defer r.mu.RUnlock()
92
93 mcpTools := make([]mcp.Tool, 0, len(r.tools))
94
95 for _, tool := range r.tools {
96 mcpTool := mcp.NewTool(tool.Name(), mcp.WithDescription(tool.Description()))
97 mcpTools = append(mcpTools, mcpTool)
98 }
99
100 return mcpTools
101}
102
103// Names returns a slice of all registered tool names
104func (r *Registry) Names() []string {
105 r.mu.RLock()
106 defer r.mu.RUnlock()
107
108 names := make([]string, 0, len(r.tools))
109 for name := range r.tools {
110 names = append(names, name)
111 }
112
113 return names
114}
115
116// Count returns the number of registered tools
117func (r *Registry) Count() int {
118 r.mu.RLock()
119 defer r.mu.RUnlock()
120
121 return len(r.tools)
122}
123
124// Execute runs a tool with the given arguments
125func (r *Registry) Execute(ctx context.Context, name string, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
126 tool, exists := r.Get(name)
127 if !exists {
128 return nil, fmt.Errorf("tool %q not found", name)
129 }
130
131 log.Debug().
132 Str("tool", name).
133 Interface("request", request).
134 Msg("Executing tool")
135
136 result, err := tool.Execute(ctx, request)
137 if err != nil {
138 log.Error().
139 Err(err).
140 Str("tool", name).
141 Msg("Tool execution failed")
142 return nil, fmt.Errorf("tool %q execution failed: %w", name, err)
143 }
144
145 log.Debug().
146 Str("tool", name).
147 Msg("Tool execution completed successfully")
148
149 return result, nil
150}
151
152// Clear removes all tools from the registry
153func (r *Registry) Clear() {
154 r.mu.Lock()
155 defer r.mu.Unlock()
156
157 count := len(r.tools)
158 r.tools = make(map[string]Tool)
159
160 log.Debug().Int("count", count).Msg("Registry cleared")
161}