Add tool registration
diff --git a/tools/registry.go b/tools/registry.go
new file mode 100644
index 0000000..9116f08
--- /dev/null
+++ b/tools/registry.go
@@ -0,0 +1,161 @@
+package tools
+
+import (
+	"context"
+	"fmt"
+	"sync"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/rs/zerolog/log"
+)
+
+// Tool represents an MCP tool that can be registered and executed
+type Tool interface {
+	// Name returns the unique name of the tool
+	Name() string
+
+	// Description returns a human-readable description of the tool
+	Description() string
+
+	// InputSchema returns the JSON schema for the tool's input parameters
+	InputSchema() map[string]interface{}
+
+	// Execute runs the tool with the given arguments and returns the result
+	Execute(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error)
+}
+
+// Registry manages the collection of available MCP tools
+type Registry struct {
+	mu    sync.RWMutex
+	tools map[string]Tool
+}
+
+// NewRegistry creates a new tools registry
+func NewRegistry() *Registry {
+	return &Registry{
+		tools: make(map[string]Tool),
+	}
+}
+
+// Register adds a tool to the registry
+func (r *Registry) Register(tool Tool) error {
+	if tool == nil {
+		return fmt.Errorf("cannot register nil tool")
+	}
+
+	name := tool.Name()
+	if name == "" {
+		return fmt.Errorf("tool name cannot be empty")
+	}
+
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if _, exists := r.tools[name]; exists {
+		return fmt.Errorf("tool with name %q already registered", name)
+	}
+
+	r.tools[name] = tool
+	log.Debug().Str("tool", name).Msg("Tool registered successfully")
+
+	return nil
+}
+
+// Unregister removes a tool from the registry
+func (r *Registry) Unregister(name string) error {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	if _, exists := r.tools[name]; !exists {
+		return fmt.Errorf("tool with name %q not found", name)
+	}
+
+	delete(r.tools, name)
+	log.Debug().Str("tool", name).Msg("Tool unregistered successfully")
+
+	return nil
+}
+
+// Get retrieves a tool by name
+func (r *Registry) Get(name string) (Tool, bool) {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	tool, exists := r.tools[name]
+	return tool, exists
+}
+
+// List returns all registered tools as MCP tool definitions
+func (r *Registry) List() []mcp.Tool {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	mcpTools := make([]mcp.Tool, 0, len(r.tools))
+
+	for _, tool := range r.tools {
+		mcpTool := mcp.NewTool(tool.Name(), mcp.WithDescription(tool.Description()))
+		mcpTools = append(mcpTools, mcpTool)
+	}
+
+	return mcpTools
+}
+
+// Names returns a slice of all registered tool names
+func (r *Registry) Names() []string {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	names := make([]string, 0, len(r.tools))
+	for name := range r.tools {
+		names = append(names, name)
+	}
+
+	return names
+}
+
+// Count returns the number of registered tools
+func (r *Registry) Count() int {
+	r.mu.RLock()
+	defer r.mu.RUnlock()
+
+	return len(r.tools)
+}
+
+// Execute runs a tool with the given arguments
+func (r *Registry) Execute(ctx context.Context, name string, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	tool, exists := r.Get(name)
+	if !exists {
+		return nil, fmt.Errorf("tool %q not found", name)
+	}
+
+	log.Debug().
+		Str("tool", name).
+		Interface("request", request).
+		Msg("Executing tool")
+
+	result, err := tool.Execute(ctx, request)
+	if err != nil {
+		log.Error().
+			Err(err).
+			Str("tool", name).
+			Msg("Tool execution failed")
+		return nil, fmt.Errorf("tool %q execution failed: %w", name, err)
+	}
+
+	log.Debug().
+		Str("tool", name).
+		Msg("Tool execution completed successfully")
+
+	return result, nil
+}
+
+// Clear removes all tools from the registry
+func (r *Registry) Clear() {
+	r.mu.Lock()
+	defer r.mu.Unlock()
+
+	count := len(r.tools)
+	r.tools = make(map[string]Tool)
+
+	log.Debug().Int("count", count).Msg("Registry cleared")
+}
diff --git a/tools/registry_test.go b/tools/registry_test.go
new file mode 100644
index 0000000..4e70978
--- /dev/null
+++ b/tools/registry_test.go
@@ -0,0 +1,230 @@
+package tools
+
+import (
+	"context"
+	"testing"
+
+	"github.com/mark3labs/mcp-go/mcp"
+	"github.com/stretchr/testify/assert"
+)
+
+// mockTool implements the Tool interface for testing
+type mockTool struct {
+	name        string
+	description string
+	schema      map[string]interface{}
+}
+
+func (t *mockTool) Name() string {
+	return t.name
+}
+
+func (t *mockTool) Description() string {
+	return t.description
+}
+
+func (t *mockTool) InputSchema() map[string]interface{} {
+	return t.schema
+}
+
+func (t *mockTool) Execute(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
+	return mcp.NewToolResultText("mock result"), nil
+}
+
+func TestNewRegistry(t *testing.T) {
+	registry := NewRegistry()
+	assert.NotNil(t, registry)
+	assert.Equal(t, 0, registry.Count())
+}
+
+func TestRegister(t *testing.T) {
+	registry := NewRegistry()
+
+	// Test registering a valid tool
+	tool := &mockTool{
+		name:        "test-tool",
+		description: "A test tool",
+		schema: map[string]interface{}{
+			"type": "object",
+		},
+	}
+
+	err := registry.Register(tool)
+	assert.NoError(t, err)
+	assert.Equal(t, 1, registry.Count())
+
+	// Test registering nil tool
+	err = registry.Register(nil)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "cannot register nil tool")
+
+	// Test registering tool with empty name
+	emptyNameTool := &mockTool{
+		name:        "",
+		description: "A tool with no name",
+	}
+	err = registry.Register(emptyNameTool)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "tool name cannot be empty")
+
+	// Test registering duplicate tool
+	err = registry.Register(tool)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "already registered")
+}
+
+func TestUnregister(t *testing.T) {
+	registry := NewRegistry()
+
+	tool := &mockTool{
+		name:        "test-tool",
+		description: "A test tool",
+	}
+
+	// Register the tool first
+	err := registry.Register(tool)
+	assert.NoError(t, err)
+
+	// Test unregistering existing tool
+	err = registry.Unregister("test-tool")
+	assert.NoError(t, err)
+	assert.Equal(t, 0, registry.Count())
+
+	// Test unregistering non-existent tool
+	err = registry.Unregister("non-existent")
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "not found")
+}
+
+func TestGet(t *testing.T) {
+	registry := NewRegistry()
+
+	tool := &mockTool{
+		name:        "test-tool",
+		description: "A test tool",
+	}
+
+	// Register the tool first
+	err := registry.Register(tool)
+	assert.NoError(t, err)
+
+	// Test getting existing tool
+	gotTool, exists := registry.Get("test-tool")
+	assert.True(t, exists)
+	assert.Equal(t, tool, gotTool)
+
+	// Test getting non-existent tool
+	_, exists = registry.Get("non-existent")
+	assert.False(t, exists)
+}
+
+func TestList(t *testing.T) {
+	registry := NewRegistry()
+
+	tool1 := &mockTool{
+		name:        "tool-1",
+		description: "First tool",
+	}
+	tool2 := &mockTool{
+		name:        "tool-2",
+		description: "Second tool",
+	}
+
+	// Register both tools
+	err := registry.Register(tool1)
+	assert.NoError(t, err)
+	err = registry.Register(tool2)
+	assert.NoError(t, err)
+
+	// Test listing tools
+	tools := registry.List()
+	assert.Len(t, tools, 2)
+
+	// Verify tool names and descriptions
+	names := make(map[string]string)
+	for _, tool := range tools {
+		names[tool.Name] = tool.Description
+	}
+
+	assert.Equal(t, "First tool", names["tool-1"])
+	assert.Equal(t, "Second tool", names["tool-2"])
+}
+
+func TestNames(t *testing.T) {
+	registry := NewRegistry()
+
+	tool1 := &mockTool{
+		name:        "tool-1",
+		description: "First tool",
+	}
+	tool2 := &mockTool{
+		name:        "tool-2",
+		description: "Second tool",
+	}
+
+	// Register both tools
+	err := registry.Register(tool1)
+	assert.NoError(t, err)
+	err = registry.Register(tool2)
+	assert.NoError(t, err)
+
+	// Test getting tool names
+	names := registry.Names()
+	assert.Len(t, names, 2)
+	assert.Contains(t, names, "tool-1")
+	assert.Contains(t, names, "tool-2")
+}
+
+func TestExecute(t *testing.T) {
+	registry := NewRegistry()
+
+	tool := &mockTool{
+		name:        "test-tool",
+		description: "A test tool",
+	}
+
+	// Register the tool first
+	err := registry.Register(tool)
+	assert.NoError(t, err)
+
+	// Test executing existing tool
+	request := mcp.CallToolRequest{}
+
+	result, err := registry.Execute(context.Background(), "test-tool", request)
+	assert.NoError(t, err)
+	assert.NotNil(t, result)
+
+	// Test executing non-existent tool
+	_, err = registry.Execute(context.Background(), "non-existent", request)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "not found")
+}
+
+func TestClear(t *testing.T) {
+	registry := NewRegistry()
+
+	tool1 := &mockTool{
+		name:        "tool-1",
+		description: "First tool",
+	}
+	tool2 := &mockTool{
+		name:        "tool-2",
+		description: "Second tool",
+	}
+
+	// Register both tools
+	err := registry.Register(tool1)
+	assert.NoError(t, err)
+	err = registry.Register(tool2)
+	assert.NoError(t, err)
+
+	// Test clearing registry
+	registry.Clear()
+	assert.Equal(t, 0, registry.Count())
+
+	// Verify tools are gone
+	_, exists := registry.Get("tool-1")
+	assert.False(t, exists)
+	_, exists = registry.Get("tool-2")
+	assert.False(t, exists)
+}