Enhance KorAP search
diff --git a/service/types.go b/service/types.go
index d5d4036..a6151b6 100644
--- a/service/types.go
+++ b/service/types.go
@@ -27,7 +27,7 @@
 type SearchRequest struct {
 	Query      string `json:"q"`
 	QueryLang  string `json:"ql,omitempty"`         // Query language (e.g., "poliqarp", "cosmas2", "annis")
-	Collection string `json:"collection,omitempty"` // Collection filter
+	Collection string `json:"collection,omitempty"` // Corpus filter
 	Count      int    `json:"count,omitempty"`      // Number of results to return
 	Offset     int    `json:"offset,omitempty"`     // Offset for pagination
 	Context    string `json:"context,omitempty"`    // Context specification
diff --git a/tools/search.go b/tools/search.go
index c11caa9..7a0422d 100644
--- a/tools/search.go
+++ b/tools/search.go
@@ -3,6 +3,7 @@
 import (
 	"context"
 	"fmt"
+	"strings"
 
 	"github.com/korap/korap-mcp/service"
 	"github.com/mark3labs/mcp-go/mcp"
@@ -68,26 +69,125 @@
 		Str("tool", s.Name()).
 		Msg("Executing search tool")
 
-	// Extract required query parameter using the same pattern as main.go
+	// Extract required query parameter
 	query, err := request.RequireString("query")
 	if err != nil {
 		return nil, fmt.Errorf("query parameter is required: %w", err)
 	}
 
+	// Extract optional parameters with defaults
+	queryLang := request.GetString("query_language", "poliqarp")
+	corpus := request.GetString("corpus", "")
+	count := request.GetInt("count", 25)
+
 	log.Debug().
 		Str("query", query).
+		Str("query_language", queryLang).
+		Str("corpus", corpus).
+		Int("count", count).
 		Msg("Parsed search parameters")
 
-	// For now, return a simple response to verify the tool works
-	// TODO: Implement actual KorAP search functionality
-	result := "KorAP Search Results\n"
-	result += "====================\n\n"
-	result += fmt.Sprintf("Query: %s\n", query)
-	result += "Status: Tool is working, KorAP integration to be implemented\n"
+	// Check if client is available and authenticated
+	if s.client == nil {
+		return nil, fmt.Errorf("KorAP client not configured")
+	}
+
+	if !s.client.IsAuthenticated() {
+		log.Warn().Msg("Client not authenticated, attempting authentication")
+		if err := s.client.AuthenticateWithClientCredentials(ctx); err != nil {
+			return nil, fmt.Errorf("authentication failed: %w", err)
+		}
+	}
+
+	// Prepare search request
+	searchReq := service.SearchRequest{
+		Query:      query,
+		QueryLang:  queryLang,
+		Collection: corpus,
+		Count:      count,
+	}
+
+	// Perform the search
+	var searchResp service.SearchResponse
+	err = s.client.PostJSON(ctx, "search", searchReq, &searchResp)
+	if err != nil {
+		log.Error().
+			Err(err).
+			Str("query", query).
+			Msg("Search request failed")
+		return nil, fmt.Errorf("search failed: %w", err)
+	}
 
 	log.Info().
 		Str("query", query).
-		Msg("Search tool executed successfully")
+		Int("total_results", searchResp.Meta.TotalResults).
+		Int("returned_matches", len(searchResp.Matches)).
+		Float64("search_time", searchResp.Meta.SearchTime).
+		Msg("Search completed successfully")
+
+	// Format the response
+	result := s.formatSearchResults(&searchResp)
 
 	return mcp.NewToolResultText(result), nil
 }
+
+// formatSearchResults formats the search response into a readable text format
+func (s *SearchTool) formatSearchResults(response *service.SearchResponse) string {
+	var result strings.Builder
+
+	result.WriteString("KorAP Search Results\n")
+	result.WriteString("====================\n\n")
+
+	// Query information
+	result.WriteString(fmt.Sprintf("Query: %s\n", response.Query.Query))
+	if response.Query.QueryLang != "" {
+		result.WriteString(fmt.Sprintf("Query Language: %s\n", response.Query.QueryLang))
+	}
+	if response.Query.Collection != "" {
+		result.WriteString(fmt.Sprintf("Corpus: %s\n", response.Query.Collection))
+	}
+	result.WriteString("\n")
+
+	// Result statistics
+	result.WriteString("Results Summary:\n")
+	result.WriteString(fmt.Sprintf("  Total Results: %d\n", response.Meta.TotalResults))
+	result.WriteString(fmt.Sprintf("  Shown: %d-%d\n",
+		response.Meta.StartIndex+1,
+		response.Meta.StartIndex+len(response.Matches)))
+	if response.Meta.SearchTime > 0 {
+		result.WriteString(fmt.Sprintf("  Search Time: %.3f seconds\n", response.Meta.SearchTime))
+	}
+	result.WriteString("\n")
+
+	// Individual matches
+	if len(response.Matches) > 0 {
+		result.WriteString("Matches:\n")
+		result.WriteString("--------\n")
+
+		for i, match := range response.Matches {
+			result.WriteString(fmt.Sprintf("\n%d. Text: %s\n", i+1, match.TextSigle))
+			if match.Snippet != "" {
+				result.WriteString(fmt.Sprintf("   Snippet: %s\n", match.Snippet))
+			}
+			if match.PubPlace != "" {
+				result.WriteString(fmt.Sprintf("   Publication: %s\n", match.PubPlace))
+			}
+			if match.MatchID != "" {
+				result.WriteString(fmt.Sprintf("   Match ID: %s\n", match.MatchID))
+			}
+			result.WriteString(fmt.Sprintf("   Position: %d\n", match.Position))
+		}
+	} else {
+		result.WriteString("No matches found.\n")
+	}
+
+	// Additional information
+	if response.Query.CutOff {
+		result.WriteString("\nNote: Results were cut off due to limits.\n")
+	}
+	if response.Query.TimeExceeded {
+		result.WriteString("\nNote: Search time limit was exceeded.\n")
+	}
+
+	return result.String()
+}
diff --git a/tools/search_test.go b/tools/search_test.go
index abd6bef..5966877 100644
--- a/tools/search_test.go
+++ b/tools/search_test.go
@@ -1,9 +1,11 @@
 package tools
 
 import (
+	"context"
 	"testing"
 
 	"github.com/korap/korap-mcp/service"
+	"github.com/mark3labs/mcp-go/mcp"
 	"github.com/stretchr/testify/assert"
 )
 
@@ -54,10 +56,173 @@
 	assert.Equal(t, client, tool.client)
 }
 
-// TestSearchTool_Execute will be implemented once the Execute method is fixed
-func TestSearchTool_Execute_WillBeImplemented(t *testing.T) {
-	t.Skip("Execute method needs to be fixed first")
+func TestSearchTool_Execute_MissingQuery(t *testing.T) {
+	client := &service.Client{}
+	tool := NewSearchTool(client)
 
-	// This test will be implemented once we fix the linter errors
-	// in the Execute method
+	// Create request without query parameter
+	request := mcp.CallToolRequest{
+		Params: mcp.CallToolParams{
+			Arguments: map[string]interface{}{},
+		},
+	}
+
+	_, err := tool.Execute(context.Background(), request)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "query parameter is required")
+}
+
+func TestSearchTool_Execute_NilClient(t *testing.T) {
+	tool := NewSearchTool(nil)
+
+	request := mcp.CallToolRequest{
+		Params: mcp.CallToolParams{
+			Arguments: map[string]interface{}{
+				"query": "test",
+			},
+		},
+	}
+
+	_, err := tool.Execute(context.Background(), request)
+	assert.Error(t, err)
+	assert.Contains(t, err.Error(), "KorAP client not configured")
+}
+
+func TestSearchTool_Execute_ParameterExtraction(t *testing.T) {
+	// This test verifies that parameters are extracted correctly
+	// It should fail with authentication error since we don't have a mock server
+	// but we can verify the parameters were parsed correctly by checking the log messages
+
+	client := &service.Client{}
+	tool := NewSearchTool(client)
+
+	tests := []struct {
+		name      string
+		arguments map[string]interface{}
+		expectErr bool
+	}{
+		{
+			name: "minimal_query",
+			arguments: map[string]interface{}{
+				"query": "test",
+			},
+			expectErr: true, // Will fail at authentication
+		},
+		{
+			name: "full_parameters",
+			arguments: map[string]interface{}{
+				"query":          "word",
+				"query_language": "cosmas2",
+				"corpus":         "test-corpus",
+				"count":          10,
+			},
+			expectErr: true, // Will fail at authentication
+		},
+		{
+			name: "invalid_count_type",
+			arguments: map[string]interface{}{
+				"query": "test",
+				"count": "invalid", // Should use default
+			},
+			expectErr: true, // Will fail at authentication
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			request := mcp.CallToolRequest{
+				Params: mcp.CallToolParams{
+					Arguments: tt.arguments,
+				},
+			}
+
+			_, err := tool.Execute(context.Background(), request)
+			if tt.expectErr {
+				assert.Error(t, err)
+			}
+		})
+	}
+}
+
+func TestSearchTool_formatSearchResults(t *testing.T) {
+	client := &service.Client{}
+	tool := NewSearchTool(client)
+
+	// Test empty response
+	emptyResponse := &service.SearchResponse{
+		Query: service.SearchQuery{
+			Query:     "test",
+			QueryLang: "poliqarp",
+		},
+		Meta: service.SearchMeta{
+			TotalResults: 0,
+			Count:        0,
+			StartIndex:   0,
+		},
+		Matches: []service.SearchMatch{},
+	}
+
+	result := tool.formatSearchResults(emptyResponse)
+	assert.Contains(t, result, "KorAP Search Results")
+	assert.Contains(t, result, "Query: test")
+	assert.Contains(t, result, "Query Language: poliqarp")
+	assert.Contains(t, result, "Total Results: 0")
+	assert.Contains(t, result, "No matches found")
+
+	// Test response with matches
+	responseWithMatches := &service.SearchResponse{
+		Query: service.SearchQuery{
+			Query:      "word",
+			QueryLang:  "poliqarp",
+			Collection: "test-corpus",
+		},
+		Meta: service.SearchMeta{
+			TotalResults: 5,
+			Count:        2,
+			StartIndex:   0,
+			SearchTime:   0.123,
+		},
+		Matches: []service.SearchMatch{
+			{
+				TextSigle: "text1",
+				Snippet:   "This is a test snippet",
+				PubPlace:  "Berlin",
+				MatchID:   "match1",
+				Position:  10,
+			},
+			{
+				TextSigle: "text2",
+				Snippet:   "Another test snippet",
+				Position:  25,
+			},
+		},
+	}
+
+	result = tool.formatSearchResults(responseWithMatches)
+	assert.Contains(t, result, "Query: word")
+	assert.Contains(t, result, "Query Language: poliqarp")
+	assert.Contains(t, result, "Corpus: test-corpus")
+	assert.Contains(t, result, "Total Results: 5")
+	assert.Contains(t, result, "Shown: 1-2")
+	assert.Contains(t, result, "Search Time: 0.123 seconds")
+	assert.Contains(t, result, "1. Text: text1")
+	assert.Contains(t, result, "Snippet: This is a test snippet")
+	assert.Contains(t, result, "Publication: Berlin")
+	assert.Contains(t, result, "2. Text: text2")
+	assert.Contains(t, result, "Position: 25")
+
+	// Test response with warnings
+	responseWithWarnings := &service.SearchResponse{
+		Query: service.SearchQuery{
+			Query:        "test",
+			CutOff:       true,
+			TimeExceeded: true,
+		},
+		Meta:    service.SearchMeta{},
+		Matches: []service.SearchMatch{},
+	}
+
+	result = tool.formatSearchResults(responseWithWarnings)
+	assert.Contains(t, result, "Results were cut off due to limits")
+	assert.Contains(t, result, "Search time limit was exceeded")
 }