Improved parameter validation

Change-Id: If2e7ec1b063a6e114a6c5582463af784b75c37b8
diff --git a/tools/metadata.go b/tools/metadata.go
index c3da813..46eb221 100644
--- a/tools/metadata.go
+++ b/tools/metadata.go
@@ -42,16 +42,22 @@
 		"properties": map[string]interface{}{
 			"action": map[string]interface{}{
 				"type":        "string",
-				"description": "Type of metadata to retrieve: 'list' for corpus list, 'statistics' for corpus statistics",
+				"description": "Type of metadata operation to perform. 'list' retrieves all available corpora with their basic information, 'statistics' provides detailed corpus statistics.",
 				"enum":        []string{"list", "statistics"},
 				"default":     "list",
+				"examples":    []string{"list", "statistics"},
 			},
 			"corpus": map[string]interface{}{
 				"type":        "string",
-				"description": "Virtual corpus query to filter results (optional, when not provided refers to all data available to the user)",
+				"description": "Virtual corpus query to filter results based on metadata fields. For 'list' action, this parameter is ignored. For 'statistics' action, specifies which subset of data to analyze using metadata queries with boolean operations (& | !), comparison operators (= != < > in), and regular expressions (/pattern/). When not provided with 'statistics', returns statistics for all accessible data.",
+				"pattern":     "^[a-zA-Z0-9._\\-\\s&|!=<>()/*\"']+$",
+				"examples":    []string{"corpusSigle = \"GOE\"", "textClass = \"politics\" & pubDate in 2020", "textType = \"news\" | textType = \"blog\"", "availability = /CC.*/ & textClass != \"fiction\""},
 			},
 		},
-		"required": []string{"action"},
+		"required":             []string{"action"},
+		"additionalProperties": false,
+		"title":                "KorAP Metadata Parameters",
+		"description":          "Parameters for retrieving corpus metadata and statistics from KorAP, including corpus lists and detailed statistical information.",
 	}
 }
 
diff --git a/tools/metadata_test.go b/tools/metadata_test.go
index e5d60cd..04a067c 100644
--- a/tools/metadata_test.go
+++ b/tools/metadata_test.go
@@ -2,6 +2,7 @@
 
 import (
 	"context"
+	"strings"
 	"testing"
 
 	"github.com/korap/korap-mcp/service"
@@ -32,6 +33,9 @@
 
 	// Verify it's an object type
 	assert.Equal(t, "object", schema["type"])
+	assert.Equal(t, "KorAP Metadata Parameters", schema["title"])
+	assert.Contains(t, schema["description"], "Parameters for retrieving corpus metadata")
+	assert.Equal(t, false, schema["additionalProperties"])
 
 	// Verify properties exist
 	properties, ok := schema["properties"].(map[string]interface{})
@@ -43,6 +47,7 @@
 	action, ok := properties["action"].(map[string]interface{})
 	assert.True(t, ok)
 	assert.Equal(t, "string", action["type"])
+	assert.Contains(t, action["description"], "Type of metadata operation")
 
 	enum, ok := action["enum"].([]string)
 	assert.True(t, ok)
@@ -50,10 +55,23 @@
 	assert.Contains(t, enum, "statistics")
 	assert.Equal(t, "list", action["default"])
 
+	actionExamples, ok := action["examples"].([]string)
+	assert.True(t, ok)
+	assert.Contains(t, actionExamples, "list")
+	assert.Contains(t, actionExamples, "statistics")
+
+	// Verify corpus property details
+	corpus, ok := properties["corpus"].(map[string]interface{})
+	assert.True(t, ok)
+	assert.Equal(t, "string", corpus["type"])
+	assert.Contains(t, corpus["description"], "Virtual corpus query")
+	assert.Contains(t, corpus["examples"], "corpusSigle = \"GOE\"")
+
 	// Verify required fields
 	required, ok := schema["required"].([]string)
 	assert.True(t, ok)
 	assert.Contains(t, required, "action")
+	assert.Len(t, required, 1) // Only action should be required
 }
 
 func TestNewMetadataTool(t *testing.T) {
@@ -284,3 +302,126 @@
 	assert.Contains(t, result, "Documents: 100")
 	assert.Contains(t, result, "Tokens: 50000")
 }
+
+func TestMetadataTool_SchemaCompliance(t *testing.T) {
+	// Test various parameter combinations against the schema
+	client := &service.Client{}
+	tool := NewMetadataTool(client)
+
+	tests := []struct {
+		name        string
+		arguments   map[string]interface{}
+		expectValid bool
+		errorMsg    string
+	}{
+		{
+			name: "valid_list_minimal",
+			arguments: map[string]interface{}{
+				"action": "list",
+			},
+			expectValid: true,
+		},
+		{
+			name: "valid_statistics_minimal",
+			arguments: map[string]interface{}{
+				"action": "statistics",
+			},
+			expectValid: true,
+		},
+		{
+			name: "valid_statistics_with_corpus",
+			arguments: map[string]interface{}{
+				"action": "statistics",
+				"corpus": "test-corpus",
+			},
+			expectValid: true,
+		},
+		{
+			name: "valid_list_with_corpus_ignored",
+			arguments: map[string]interface{}{
+				"action": "list",
+				"corpus": "test-corpus", // Should be ignored for list action
+			},
+			expectValid: true,
+		},
+		{
+			name: "missing_required_action",
+			arguments: map[string]interface{}{
+				"corpus": "test-corpus",
+			},
+			expectValid: false,
+			errorMsg:    "action parameter is required",
+		},
+		{
+			name: "invalid_action",
+			arguments: map[string]interface{}{
+				"action": "invalid",
+			},
+			expectValid: false,
+			errorMsg:    "invalid action",
+		},
+		{
+			name: "empty_action",
+			arguments: map[string]interface{}{
+				"action": "",
+			},
+			expectValid: false,
+			errorMsg:    "action is required and cannot be empty",
+		},
+		{
+			name: "invalid_corpus_format",
+			arguments: map[string]interface{}{
+				"action": "statistics",
+				"corpus": "invalid@corpus#format",
+			},
+			expectValid: false,
+			errorMsg:    "collection query contains invalid characters",
+		},
+		{
+			name: "valid_corpus_with_boolean",
+			arguments: map[string]interface{}{
+				"action": "statistics",
+				"corpus": "corpus1 & corpus2",
+			},
+			expectValid: true,
+		},
+		{
+			name: "valid_collection_query",
+			arguments: map[string]interface{}{
+				"action": "statistics",
+				"corpus": "textClass = \"politics\" & pubDate in 2020",
+			},
+			expectValid: true,
+		},
+	}
+
+	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.expectValid {
+				// For valid requests, we expect authentication/client errors, not validation errors
+				if err != nil {
+					assert.NotContains(t, err.Error(), "validation")
+					// Should fail at authentication or client configuration
+					assert.True(t,
+						strings.Contains(err.Error(), "authentication") ||
+							strings.Contains(err.Error(), "KorAP client not configured"),
+						"Expected authentication or client error, got: %s", err.Error())
+				}
+			} else {
+				// For invalid requests, we expect validation errors
+				assert.Error(t, err)
+				if tt.errorMsg != "" {
+					assert.Contains(t, err.Error(), tt.errorMsg)
+				}
+			}
+		})
+	}
+}
diff --git a/tools/schema_test.go b/tools/schema_test.go
new file mode 100644
index 0000000..4365537
--- /dev/null
+++ b/tools/schema_test.go
@@ -0,0 +1,362 @@
+package tools
+
+import (
+	"encoding/json"
+	"fmt"
+	"reflect"
+	"testing"
+
+	"github.com/korap/korap-mcp/service"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// TestAllToolsSchemaCompliance ensures all tools have properly structured schemas
+func TestAllToolsSchemaCompliance(t *testing.T) {
+	client := &service.Client{}
+
+	tools := []Tool{
+		NewSearchTool(client),
+		NewMetadataTool(client),
+	}
+
+	for _, tool := range tools {
+		t.Run(tool.Name(), func(t *testing.T) {
+			schema := tool.InputSchema()
+
+			// Verify basic schema structure
+			validateBasicSchemaStructure(t, schema, tool.Name())
+
+			// Verify schema is valid JSON
+			validateSchemaIsValidJSON(t, schema, tool.Name())
+
+			// Verify schema documentation completeness
+			validateSchemaDocumentation(t, schema, tool.Name())
+		})
+	}
+}
+
+// validateBasicSchemaStructure checks that the schema has the required top-level properties
+func validateBasicSchemaStructure(t *testing.T, schema map[string]interface{}, toolName string) {
+	// Must be object type
+	assert.Equal(t, "object", schema["type"], "Tool %s schema must be object type", toolName)
+
+	// Must have properties
+	properties, exists := schema["properties"]
+	assert.True(t, exists, "Tool %s schema must have properties", toolName)
+	assert.IsType(t, map[string]interface{}{}, properties, "Tool %s properties must be an object", toolName)
+
+	// Must have required array
+	required, exists := schema["required"]
+	assert.True(t, exists, "Tool %s schema must have required array", toolName)
+	assert.IsType(t, []string{}, required, "Tool %s required must be string array", toolName)
+
+	// Should have title and description
+	assert.Contains(t, schema, "title", "Tool %s schema should have title", toolName)
+	assert.Contains(t, schema, "description", "Tool %s schema should have description", toolName)
+
+	// Should prevent additional properties for strict validation
+	assert.Contains(t, schema, "additionalProperties", "Tool %s schema should specify additionalProperties", toolName)
+	assert.Equal(t, false, schema["additionalProperties"], "Tool %s should not allow additional properties", toolName)
+}
+
+// validateSchemaIsValidJSON ensures the schema can be properly serialized to JSON
+func validateSchemaIsValidJSON(t *testing.T, schema map[string]interface{}, toolName string) {
+	jsonBytes, err := json.Marshal(schema)
+	require.NoError(t, err, "Tool %s schema must be serializable to JSON", toolName)
+
+	var unmarshalled map[string]interface{}
+	err = json.Unmarshal(jsonBytes, &unmarshalled)
+	require.NoError(t, err, "Tool %s schema JSON must be valid", toolName)
+
+	// Verify the unmarshalled schema has the same structure
+	assert.Equal(t, schema["type"], unmarshalled["type"], "Tool %s schema type must survive JSON round-trip", toolName)
+}
+
+// validateSchemaDocumentation checks that all properties have proper documentation
+func validateSchemaDocumentation(t *testing.T, schema map[string]interface{}, toolName string) {
+	properties, ok := schema["properties"].(map[string]interface{})
+	require.True(t, ok, "Tool %s properties must be accessible", toolName)
+
+	for propName, propSchema := range properties {
+		propMap, ok := propSchema.(map[string]interface{})
+		require.True(t, ok, "Tool %s property %s must be an object", toolName, propName)
+
+		// Must have type
+		assert.Contains(t, propMap, "type", "Tool %s property %s must have type", toolName, propName)
+
+		// Must have description
+		assert.Contains(t, propMap, "description", "Tool %s property %s must have description", toolName, propName)
+
+		description, ok := propMap["description"].(string)
+		assert.True(t, ok, "Tool %s property %s description must be string", toolName, propName)
+		assert.NotEmpty(t, description, "Tool %s property %s description must not be empty", toolName, propName)
+		assert.Greater(t, len(description), 10, "Tool %s property %s description should be descriptive", toolName, propName)
+
+		// String properties should have examples
+		if propType, ok := propMap["type"].(string); ok && propType == "string" {
+			if enumValues, hasEnum := propMap["enum"]; hasEnum {
+				// Enum properties should have examples matching enum values
+				assert.Contains(t, propMap, "examples", "Tool %s property %s with enum should have examples", toolName, propName)
+				examples, ok := propMap["examples"].([]string)
+				assert.True(t, ok, "Tool %s property %s examples must be string array", toolName, propName)
+
+				enumArray, ok := enumValues.([]string)
+				assert.True(t, ok, "Tool %s property %s enum must be string array", toolName, propName)
+
+				// All examples should be valid enum values
+				for _, example := range examples {
+					assert.Contains(t, enumArray, example, "Tool %s property %s example '%s' must be valid enum value", toolName, propName, example)
+				}
+			} else {
+				// Regular string properties should have examples
+				assert.Contains(t, propMap, "examples", "Tool %s property %s should have examples", toolName, propName)
+			}
+		}
+
+		// Integer properties should have examples and constraints
+		if propType, ok := propMap["type"].(string); ok && propType == "integer" {
+			assert.Contains(t, propMap, "examples", "Tool %s property %s should have examples", toolName, propName)
+
+			// Should have minimum/maximum constraints
+			assert.Contains(t, propMap, "minimum", "Tool %s property %s should have minimum", toolName, propName)
+			assert.Contains(t, propMap, "maximum", "Tool %s property %s should have maximum", toolName, propName)
+		}
+	}
+}
+
+// TestSchemaExamples verifies that schema examples are realistic and valid
+func TestSchemaExamples(t *testing.T) {
+	client := &service.Client{}
+
+	t.Run("SearchTool", func(t *testing.T) {
+		tool := NewSearchTool(client)
+		schema := tool.InputSchema()
+
+		properties := schema["properties"].(map[string]interface{})
+
+		// Test query examples
+		queryProp := properties["query"].(map[string]interface{})
+		examples := queryProp["examples"].([]string)
+
+		for _, example := range examples {
+			assert.NotEmpty(t, example, "Query example should not be empty")
+			assert.LessOrEqual(t, len(example), 100, "Query example should be reasonably short")
+		}
+
+		// Test corpus examples
+		corpusProp := properties["corpus"].(map[string]interface{})
+		corpusExamples := corpusProp["examples"].([]string)
+
+		for _, example := range corpusExamples {
+			assert.NotEmpty(t, example, "Corpus example should not be empty")
+			// Should match the pattern defined in the schema
+			pattern := corpusProp["pattern"].(string)
+			assert.NotEmpty(t, pattern, "Corpus should have validation pattern")
+		}
+	})
+
+	t.Run("MetadataTool", func(t *testing.T) {
+		tool := NewMetadataTool(client)
+		schema := tool.InputSchema()
+
+		properties := schema["properties"].(map[string]interface{})
+
+		// Test action examples
+		actionProp := properties["action"].(map[string]interface{})
+		examples := actionProp["examples"].([]string)
+		enumValues := actionProp["enum"].([]string)
+
+		for _, example := range examples {
+			assert.Contains(t, enumValues, example, "Action example should be valid enum value")
+		}
+	})
+}
+
+// TestSchemaConstraints verifies that schema constraints are reasonable
+func TestSchemaConstraints(t *testing.T) {
+	client := &service.Client{}
+
+	t.Run("SearchTool", func(t *testing.T) {
+		tool := NewSearchTool(client)
+		schema := tool.InputSchema()
+		properties := schema["properties"].(map[string]interface{})
+
+		// Test query constraints
+		queryProp := properties["query"].(map[string]interface{})
+		minLength := queryProp["minLength"].(int)
+		maxLength := queryProp["maxLength"].(int)
+
+		assert.Greater(t, minLength, 0, "Query minimum length should be positive")
+		assert.Greater(t, maxLength, minLength, "Query maximum length should be greater than minimum")
+		assert.LessOrEqual(t, maxLength, 10000, "Query maximum length should be reasonable")
+
+		// Test count constraints
+		countProp := properties["count"].(map[string]interface{})
+		minimum := countProp["minimum"].(int)
+		maximum := countProp["maximum"].(int)
+		defaultValue := countProp["default"].(int)
+
+		assert.GreaterOrEqual(t, minimum, 0, "Count minimum should be non-negative")
+		assert.Greater(t, maximum, minimum, "Count maximum should be greater than minimum")
+		assert.GreaterOrEqual(t, defaultValue, minimum, "Count default should be >= minimum")
+		assert.LessOrEqual(t, defaultValue, maximum, "Count default should be <= maximum")
+	})
+}
+
+// TestSchemaVersioning ensures schema structure is backwards compatible
+func TestSchemaVersioning(t *testing.T) {
+	client := &service.Client{}
+
+	// This test would be extended when we add new versions
+	// For now, we ensure the basic structure is stable
+
+	tools := []Tool{
+		NewSearchTool(client),
+		NewMetadataTool(client),
+	}
+
+	for _, tool := range tools {
+		t.Run(tool.Name(), func(t *testing.T) {
+			schema := tool.InputSchema()
+
+			// Core schema structure must remain stable
+			assert.Equal(t, "object", schema["type"])
+			assert.Contains(t, schema, "properties")
+			assert.Contains(t, schema, "required")
+
+			// Tool-specific stability checks
+			switch tool.Name() {
+			case "korap_search":
+				properties := schema["properties"].(map[string]interface{})
+				assert.Contains(t, properties, "query", "Search tool must always have query parameter")
+
+				required := schema["required"].([]string)
+				assert.Contains(t, required, "query", "Search tool must always require query parameter")
+
+			case "korap_metadata":
+				properties := schema["properties"].(map[string]interface{})
+				assert.Contains(t, properties, "action", "Metadata tool must always have action parameter")
+
+				required := schema["required"].([]string)
+				assert.Contains(t, required, "action", "Metadata tool must always require action parameter")
+			}
+		})
+	}
+}
+
+// BenchmarkSchemaGeneration measures schema generation performance
+func BenchmarkSchemaGeneration(b *testing.B) {
+	client := &service.Client{}
+
+	tools := []Tool{
+		NewSearchTool(client),
+		NewMetadataTool(client),
+	}
+
+	for _, tool := range tools {
+		b.Run(tool.Name(), func(b *testing.B) {
+			for i := 0; i < b.N; i++ {
+				schema := tool.InputSchema()
+				_ = schema // Prevent optimization
+			}
+		})
+	}
+}
+
+// TestSchemaDeepCopy ensures schemas are independent instances
+func TestSchemaDeepCopy(t *testing.T) {
+	client := &service.Client{}
+	tool := NewSearchTool(client)
+
+	// Get two schema instances
+	schema1 := tool.InputSchema()
+	schema2 := tool.InputSchema()
+
+	// They should be equal in content but different instances
+	assert.True(t, reflect.DeepEqual(schema1, schema2), "Schemas should have equal content")
+
+	// Modifying one should not affect the other
+	schema1["modified"] = true
+	assert.NotContains(t, schema2, "modified", "Schema instances should be independent")
+}
+
+// TestSchemaHelpGeneration verifies schema can be used to generate help text
+func TestSchemaHelpGeneration(t *testing.T) {
+	client := &service.Client{}
+
+	tools := []Tool{
+		NewSearchTool(client),
+		NewMetadataTool(client),
+	}
+
+	for _, tool := range tools {
+		t.Run(tool.Name(), func(t *testing.T) {
+			schema := tool.InputSchema()
+
+			// Generate help text from schema
+			helpText := generateHelpText(schema, tool.Name())
+
+			// Verify help text contains essential information
+			assert.Contains(t, helpText, tool.Name(), "Help text should contain tool name")
+			assert.Contains(t, helpText, "Parameters:", "Help text should list parameters")
+
+			// Should contain information about required parameters
+			required := schema["required"].([]string)
+			for _, param := range required {
+				assert.Contains(t, helpText, param, "Help text should mention required parameter %s", param)
+				assert.Contains(t, helpText, "(required)", "Help text should mark required parameters")
+			}
+		})
+	}
+}
+
+// generateHelpText creates human-readable help text from a schema
+func generateHelpText(schema map[string]interface{}, toolName string) string {
+	help := fmt.Sprintf("Tool: %s\n", toolName)
+
+	if desc, ok := schema["description"].(string); ok {
+		help += fmt.Sprintf("Description: %s\n\n", desc)
+	}
+
+	properties, ok := schema["properties"].(map[string]interface{})
+	if !ok {
+		return help
+	}
+
+	required, _ := schema["required"].([]string)
+	requiredMap := make(map[string]bool)
+	for _, req := range required {
+		requiredMap[req] = true
+	}
+
+	help += "Parameters:\n"
+	for paramName, paramSchema := range properties {
+		paramMap, ok := paramSchema.(map[string]interface{})
+		if !ok {
+			continue
+		}
+
+		requiredText := ""
+		if requiredMap[paramName] {
+			requiredText = " (required)"
+		}
+
+		paramType, _ := paramMap["type"].(string)
+		description, _ := paramMap["description"].(string)
+
+		help += fmt.Sprintf("  %s (%s)%s: %s\n", paramName, paramType, requiredText, description)
+
+		// Add enum values if present
+		if enum, ok := paramMap["enum"].([]string); ok {
+			help += fmt.Sprintf("    Allowed values: %v\n", enum)
+		}
+
+		// Add examples if present
+		if examples, ok := paramMap["examples"]; ok {
+			help += fmt.Sprintf("    Examples: %v\n", examples)
+		}
+	}
+
+	return help
+}
diff --git a/tools/search.go b/tools/search.go
index 2a78f8e..002dc86 100644
--- a/tools/search.go
+++ b/tools/search.go
@@ -42,27 +42,37 @@
 		"properties": map[string]interface{}{
 			"query": map[string]interface{}{
 				"type":        "string",
-				"description": "The search query (word, phrase, or pattern)",
+				"description": "The search query. Supports different query languages like Poliqarp, CosmasII, or Annis depending on the selected query_language parameter.",
+				"minLength":   1,
+				"maxLength":   1000,
+				"examples":    []string{"Haus", "[pos=NN]", "der /w1:5 Mann"},
 			},
 			"query_language": map[string]interface{}{
 				"type":        "string",
-				"description": "Query language: 'poliqarp' (default), 'cosmas2', or 'annis'",
-				"enum":        []string{"poliqarp", "cosmas2", "annis"},
+				"description": "Query language to use for parsing the search query. Supported languages: 'poliqarp' (default; extended Poliqarp QL), 'cosmas2' (corpus query syntax of COSMAS II), 'annis' (multi-layer annotation queries), 'cql' (corpus query language), 'cqp' (Corpus Query Processor syntax), 'fcsql' (Federated Content Search queries).",
+				"enum":        []string{"poliqarp", "cosmas2", "annis", "cql", "cqp", "fcsql"},
 				"default":     "poliqarp",
+				"examples":    []string{"poliqarp", "cosmas2", "annis", "cql", "cqp", "fcsql"},
 			},
 			"corpus": map[string]interface{}{
 				"type":        "string",
-				"description": "Virtual corpus query to filter search results (optional, when not provided searches all available data)",
+				"description": "Virtual corpus query to filter search results based on metadata fields. Supports boolean operations (& | !), comparison operators (= != < > in), and regular expressions (/pattern/). Use metadata fields like corpusSigle, textClass, pubDate, textType, availability, etc. When not provided, searches all available data accessible to the user.",
+				"pattern":     "^[a-zA-Z0-9._\\-\\s&|!=<>()/*\"']+$",
+				"examples":    []string{"corpusSigle = \"GOE\"", "textClass = \"politics\" & pubDate in 2020", "textType = \"news\" | textType = \"blog\"", "availability = /CC.*/ & textClass != \"fiction\""},
 			},
 			"count": map[string]interface{}{
 				"type":        "integer",
-				"description": "Number of results to return (max 100)",
-				"minimum":     1,
-				"maximum":     100,
+				"description": "Maximum number of search results to return. Higher values may increase response time. Use smaller values for faster responses when doing exploratory searches.",
+				"minimum":     0,
+				"maximum":     10000,
 				"default":     25,
+				"examples":    []interface{}{10, 25, 50, 100},
 			},
 		},
-		"required": []string{"query"},
+		"required":             []string{"query"},
+		"additionalProperties": false,
+		"title":                "KorAP Search Parameters",
+		"description":          "Parameters for searching text corpora using KorAP's powerful query languages and filtering capabilities.",
 	}
 }
 
diff --git a/tools/search_test.go b/tools/search_test.go
index 5966877..b80915a 100644
--- a/tools/search_test.go
+++ b/tools/search_test.go
@@ -32,6 +32,9 @@
 
 	// Verify it's an object type
 	assert.Equal(t, "object", schema["type"])
+	assert.Equal(t, "KorAP Search Parameters", schema["title"])
+	assert.Contains(t, schema["description"], "Parameters for searching text corpora")
+	assert.Equal(t, false, schema["additionalProperties"])
 
 	// Verify properties exist
 	properties, ok := schema["properties"].(map[string]interface{})
@@ -40,12 +43,168 @@
 	assert.Contains(t, properties, "query_language")
 	assert.Contains(t, properties, "corpus")
 	assert.Contains(t, properties, "count")
-	// Note: offset and context will be added in future iterations
+
+	// Verify query property details
+	query, ok := properties["query"].(map[string]interface{})
+	assert.True(t, ok)
+	assert.Equal(t, "string", query["type"])
+	assert.Contains(t, query["description"], "search query")
+	assert.Equal(t, 1, query["minLength"])
+	assert.Equal(t, 1000, query["maxLength"])
+	examples, ok := query["examples"].([]string)
+	assert.True(t, ok)
+	assert.Contains(t, examples, "Haus")
+
+	// Verify query_language property details
+	queryLang, ok := properties["query_language"].(map[string]interface{})
+	assert.True(t, ok)
+	assert.Equal(t, "string", queryLang["type"])
+	assert.Contains(t, queryLang["description"], "Query language to use for parsing")
+	enum, ok := queryLang["enum"].([]string)
+	assert.True(t, ok)
+	assert.Contains(t, enum, "poliqarp")
+	assert.Contains(t, enum, "cosmas2")
+	assert.Contains(t, enum, "annis")
+	assert.Contains(t, enum, "cql")
+	assert.Contains(t, enum, "cqp")
+	assert.Contains(t, enum, "fcsql")
+	assert.Equal(t, "poliqarp", queryLang["default"])
+
+	// Verify corpus property details
+	corpus, ok := properties["corpus"].(map[string]interface{})
+	assert.True(t, ok)
+	assert.Equal(t, "string", corpus["type"])
+	assert.Contains(t, corpus["description"], "Virtual corpus query")
+	assert.NotEmpty(t, corpus["pattern"])
+	corpusExamples, ok := corpus["examples"].([]string)
+	assert.True(t, ok)
+	assert.Contains(t, corpusExamples, "corpusSigle = \"GOE\"")
+
+	// Verify count property details
+	count, ok := properties["count"].(map[string]interface{})
+	assert.True(t, ok)
+	assert.Equal(t, "integer", count["type"])
+	assert.Contains(t, count["description"], "Maximum number")
+	assert.Equal(t, 0, count["minimum"])
+	assert.Equal(t, 10000, count["maximum"])
+	assert.Equal(t, 25, count["default"])
+	countExamples, ok := count["examples"].([]interface{})
+	assert.True(t, ok)
+	assert.Contains(t, countExamples, 25)
 
 	// Verify required fields
 	required, ok := schema["required"].([]string)
 	assert.True(t, ok)
 	assert.Contains(t, required, "query")
+	assert.Len(t, required, 1) // Only query should be required
+}
+
+func TestSearchTool_SchemaCompliance(t *testing.T) {
+	// Test various parameter combinations against the schema
+	client := &service.Client{}
+	tool := NewSearchTool(client)
+
+	tests := []struct {
+		name        string
+		arguments   map[string]interface{}
+		expectValid bool
+		errorMsg    string
+	}{
+		{
+			name: "valid_minimal",
+			arguments: map[string]interface{}{
+				"query": "test",
+			},
+			expectValid: true,
+		},
+		{
+			name: "valid_full",
+			arguments: map[string]interface{}{
+				"query":          "word",
+				"query_language": "cosmas2",
+				"corpus":         "test-corpus",
+				"count":          10,
+			},
+			expectValid: true,
+		},
+		{
+			name: "missing_required_query",
+			arguments: map[string]interface{}{
+				"query_language": "poliqarp",
+			},
+			expectValid: false,
+			errorMsg:    "query parameter is required",
+		},
+		{
+			name: "invalid_query_language",
+			arguments: map[string]interface{}{
+				"query":          "test",
+				"query_language": "invalid",
+			},
+			expectValid: false,
+			errorMsg:    "invalid query language",
+		},
+		{
+			name: "invalid_count_negative",
+			arguments: map[string]interface{}{
+				"query": "test",
+				"count": -1,
+			},
+			expectValid: false,
+			errorMsg:    "count must be",
+		},
+		{
+			name: "invalid_count_too_large",
+			arguments: map[string]interface{}{
+				"query": "test",
+				"count": 20000,
+			},
+			expectValid: false,
+			errorMsg:    "count must be",
+		},
+		{
+			name: "empty_query",
+			arguments: map[string]interface{}{
+				"query": "",
+			},
+			expectValid: false,
+			errorMsg:    "query is required and cannot be empty",
+		},
+		{
+			name: "count_zero_valid",
+			arguments: map[string]interface{}{
+				"query": "test",
+				"count": 0,
+			},
+			expectValid: true, // Zero count should be valid (uses default behavior)
+		},
+	}
+
+	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.expectValid {
+				// For valid requests, we expect authentication/client errors, not validation errors
+				if err != nil {
+					assert.NotContains(t, err.Error(), "validation")
+					assert.Contains(t, err.Error(), "authentication")
+				}
+			} else {
+				// For invalid requests, we expect validation errors
+				assert.Error(t, err)
+				if tt.errorMsg != "" {
+					assert.Contains(t, err.Error(), tt.errorMsg)
+				}
+			}
+		})
+	}
 }
 
 func TestNewSearchTool(t *testing.T) {
diff --git a/validation/validator.go b/validation/validator.go
index 66b77c8..343e820 100644
--- a/validation/validator.go
+++ b/validation/validator.go
@@ -65,15 +65,11 @@
 
 // Regular expressions for validation
 var (
-	// Query language validation - KorAP supports poliqarp, cosmas2, annis
-	validQueryLanguages = map[string]bool{
-		"poliqarp": true,
-		"cosmas2":  true,
-		"annis":    true,
-	}
+	// Query language validation
+	validQueryLanguages = []string{"poliqarp", "poliqarpplus", "cosmas2", "annis", "cql", "cqp", "fcsql"}
 
-	// Corpus ID validation - alphanumeric with dots, hyphens, underscores
-	corpusIDRegex = regexp.MustCompile(`^[a-zA-Z0-9._-]+$`)
+	// Corpus ID validation - KorAP collection queries with metadata fields, operators, and regex
+	corpusIDRegex = regexp.MustCompile(`^[a-zA-Z0-9._\-\s&|!=<>()/*"']+$`)
 
 	// Action validation for metadata requests
 	validMetadataActions = map[string]bool{
@@ -105,15 +101,11 @@
 	}
 
 	// Validate query language if provided
-	if req.QueryLanguage != "" && !validQueryLanguages[req.QueryLanguage] {
-		var validLangs []string
-		for lang := range validQueryLanguages {
-			validLangs = append(validLangs, lang)
-		}
+	if req.QueryLanguage != "" && !contains(validQueryLanguages, req.QueryLanguage) {
 		errors = append(errors, ValidationError{
 			Field:   "query_language",
 			Value:   req.QueryLanguage,
-			Message: fmt.Sprintf("invalid query language, must be one of: %s", strings.Join(validLangs, ", ")),
+			Message: fmt.Sprintf("invalid query language, must be one of: %s", strings.Join(validQueryLanguages, ", ")),
 		})
 	}
 
@@ -404,7 +396,9 @@
 	return nil
 }
 
-// validateCorpusID validates a corpus identifier
+// validateCorpusID validates a corpus identifier or collection query
+// This supports both simple corpus sigles (e.g., "DeReKo-2023-I") and complex
+// collection queries with metadata fields (e.g., "textClass = \"politics\" & pubDate in 2020")
 func (v *Validator) validateCorpusID(corpusID string) error {
 	if len(corpusID) == 0 {
 		return fmt.Errorf("corpus ID cannot be empty")
@@ -415,7 +409,7 @@
 	}
 
 	if !corpusIDRegex.MatchString(corpusID) {
-		return fmt.Errorf("corpus ID contains invalid characters (only alphanumeric, dots, hyphens, underscores allowed)")
+		return fmt.Errorf("collection query contains invalid characters (supports alphanumeric, dots, hyphens, underscores, spaces, quotes, operators & | ! = < > in, parentheses, and regex /pattern/)")
 	}
 
 	return nil
@@ -458,3 +452,13 @@
 
 	return sanitized
 }
+
+// contains checks if a string slice contains a specific value
+func contains(slice []string, item string) bool {
+	for _, s := range slice {
+		if s == item {
+			return true
+		}
+	}
+	return false
+}
diff --git a/validation/validator_test.go b/validation/validator_test.go
index 43254a7..88d01a2 100644
--- a/validation/validator_test.go
+++ b/validation/validator_test.go
@@ -96,22 +96,78 @@
 			errorMsg:  "query is required and cannot be empty",
 		},
 		{
+			name: "valid_poliqarp_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "poliqarp",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_poliqarpplus_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "poliqarpplus",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_cosmas2_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "cosmas2",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_annis_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "annis",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_cql_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "cql",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_cqp_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "cqp",
+			},
+			expectErr: false,
+		},
+		{
+			name: "valid_fcsql_language",
+			request: SearchRequest{
+				Query:         "test query",
+				QueryLanguage: "fcsql",
+			},
+			expectErr: false,
+		},
+		{
 			name: "invalid_query_language",
 			request: SearchRequest{
 				Query:         "test query",
 				QueryLanguage: "invalid",
 			},
 			expectErr: true,
-			errorMsg:  "invalid query language",
+			errorMsg:  "invalid query language, must be one of: poliqarp, poliqarpplus, cosmas2, annis, cql, cqp, fcsql",
 		},
 		{
 			name: "invalid_corpus_id",
 			request: SearchRequest{
 				Query:  "test query",
-				Corpus: "invalid corpus!",
+				Corpus: "invalid@corpus#format",
 			},
 			expectErr: true,
-			errorMsg:  "corpus ID contains invalid characters",
+			errorMsg:  "collection query contains invalid characters",
 		},
 		{
 			name: "count_negative",
@@ -231,10 +287,10 @@
 			name: "invalid_corpus_id",
 			request: MetadataRequest{
 				Action: "statistics",
-				Corpus: "invalid corpus!",
+				Corpus: "invalid@corpus#format",
 			},
 			expectErr: true,
-			errorMsg:  "corpus ID contains invalid characters",
+			errorMsg:  "collection query contains invalid characters",
 		},
 	}
 
@@ -465,7 +521,7 @@
 			response: &service.CorpusListResponse{
 				Corpora: []service.CorpusInfo{
 					{
-						ID:        "invalid id!",
+						ID:        "invalid@corpus#format",
 						Name:      "Test Corpus",
 						Documents: 100,
 						Tokens:    50000,
@@ -473,7 +529,7 @@
 				},
 			},
 			expectErr: true,
-			errorMsg:  "corpus ID contains invalid characters",
+			errorMsg:  "collection query contains invalid characters",
 		},
 		{
 			name: "corpus_missing_name",
@@ -708,15 +764,34 @@
 		},
 		{
 			name:      "corpus_id_invalid_chars",
-			corpusID:  "invalid corpus!",
+			corpusID:  "invalid@corpus#format",
 			expectErr: true,
-			errorMsg:  "corpus ID contains invalid characters",
+			errorMsg:  "collection query contains invalid characters",
 		},
 		{
 			name:      "corpus_id_with_space",
 			corpusID:  "corpus with space",
-			expectErr: true,
-			errorMsg:  "corpus ID contains invalid characters",
+			expectErr: false, // Now allowed with updated regex
+		},
+		{
+			name:      "corpus_id_with_boolean_operators",
+			corpusID:  "corpus1 & corpus2",
+			expectErr: false, // Now allowed with updated regex
+		},
+		{
+			name:      "collection_query_with_metadata",
+			corpusID:  "textClass = \"politics\" & pubDate in 2020",
+			expectErr: false, // Collection query syntax
+		},
+		{
+			name:      "collection_query_with_regex",
+			corpusID:  "corpusSigle = \"DeReKo/WPD*\" & availability = /CC.*/",
+			expectErr: false, // Collection query with regex
+		},
+		{
+			name:      "collection_query_complex",
+			corpusID:  "(textType = \"news\" | textType = \"blog\") & textClass != \"fiction\"",
+			expectErr: false, // Complex collection query
 		},
 	}