Support metadata retrieval
diff --git a/tools/metadata_test.go b/tools/metadata_test.go
new file mode 100644
index 0000000..88c7e6d
--- /dev/null
+++ b/tools/metadata_test.go
@@ -0,0 +1,285 @@
+package tools
+
+import (
+ "context"
+ "testing"
+
+ "github.com/korap/korap-mcp/service"
+ "github.com/mark3labs/mcp-go/mcp"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestMetadataTool_Name(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ assert.Equal(t, "korap_metadata", tool.Name())
+}
+
+func TestMetadataTool_Description(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ expected := "Retrieve metadata and statistics for KorAP corpora"
+ assert.Equal(t, expected, tool.Description())
+}
+
+func TestMetadataTool_InputSchema(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ schema := tool.InputSchema()
+
+ // Verify it's an object type
+ assert.Equal(t, "object", schema["type"])
+
+ // Verify properties exist
+ properties, ok := schema["properties"].(map[string]interface{})
+ assert.True(t, ok)
+ assert.Contains(t, properties, "action")
+ assert.Contains(t, properties, "corpus")
+
+ // Verify action property details
+ action, ok := properties["action"].(map[string]interface{})
+ assert.True(t, ok)
+ assert.Equal(t, "string", action["type"])
+
+ enum, ok := action["enum"].([]string)
+ assert.True(t, ok)
+ assert.Contains(t, enum, "list")
+ assert.Contains(t, enum, "statistics")
+ assert.Equal(t, "list", action["default"])
+
+ // Verify required fields
+ required, ok := schema["required"].([]string)
+ assert.True(t, ok)
+ assert.Contains(t, required, "action")
+}
+
+func TestNewMetadataTool(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ assert.NotNil(t, tool)
+ assert.Equal(t, client, tool.client)
+}
+
+func TestMetadataTool_Execute_MissingAction(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ // Create request without action 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(), "action parameter is required")
+}
+
+func TestMetadataTool_Execute_NilClient(t *testing.T) {
+ tool := NewMetadataTool(nil)
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Arguments: map[string]interface{}{
+ "action": "list",
+ },
+ },
+ }
+
+ _, err := tool.Execute(context.Background(), request)
+ assert.Error(t, err)
+ assert.Contains(t, err.Error(), "KorAP client not configured")
+}
+
+func TestMetadataTool_Execute_UnknownAction(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Arguments: map[string]interface{}{
+ "action": "unknown",
+ },
+ },
+ }
+
+ _, err := tool.Execute(context.Background(), request)
+ assert.Error(t, err)
+ // The unknown action error should come before authentication
+ assert.Contains(t, err.Error(), "unknown action: unknown")
+}
+
+func TestMetadataTool_Execute_StatisticsWithoutCorpus(t *testing.T) {
+ tool := NewMetadataTool(nil)
+
+ request := mcp.CallToolRequest{
+ Params: mcp.CallToolParams{
+ Arguments: map[string]interface{}{
+ "action": "statistics",
+ },
+ },
+ }
+
+ _, err := tool.Execute(context.Background(), request)
+ assert.Error(t, err)
+ // Should fail at client check since corpus is now optional
+ assert.Contains(t, err.Error(), "KorAP client not configured")
+}
+
+func TestMetadataTool_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 := NewMetadataTool(client)
+
+ tests := []struct {
+ name string
+ arguments map[string]interface{}
+ expectErr bool
+ }{
+ {
+ name: "list_action",
+ arguments: map[string]interface{}{
+ "action": "list",
+ },
+ expectErr: true, // Will fail at authentication
+ },
+ {
+ name: "statistics_action",
+ arguments: map[string]interface{}{
+ "action": "statistics",
+ "corpus": "test-corpus",
+ },
+ expectErr: true, // Will fail at authentication
+ },
+ {
+ name: "statistics_with_empty_corpus",
+ arguments: map[string]interface{}{
+ "action": "statistics",
+ "corpus": "",
+ },
+ expectErr: true, // Will fail at authentication (corpus is optional)
+ },
+ }
+
+ 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 TestMetadataTool_formatCorpusList(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ // Test empty response
+ emptyResponse := &service.CorpusListResponse{
+ Corpora: []service.CorpusInfo{},
+ }
+
+ result := tool.formatCorpusList(emptyResponse)
+ assert.Contains(t, result, "KorAP Available Corpora")
+ assert.Contains(t, result, "No corpora available")
+
+ // Test response with corpora
+ responseWithCorpora := &service.CorpusListResponse{
+ Corpora: []service.CorpusInfo{
+ {
+ ID: "corpus1",
+ Name: "Test Corpus 1",
+ Description: "A test corpus",
+ Documents: 100,
+ Tokens: 50000,
+ Sentences: 2500,
+ Paragraphs: 500,
+ },
+ {
+ ID: "corpus2",
+ Name: "Test Corpus 2",
+ Documents: 200,
+ Tokens: 75000,
+ },
+ },
+ }
+
+ result = tool.formatCorpusList(responseWithCorpora)
+ assert.Contains(t, result, "KorAP Available Corpora")
+ assert.Contains(t, result, "Total Corpora: 2")
+ assert.Contains(t, result, "1. Test Corpus 1")
+ assert.Contains(t, result, "ID: corpus1")
+ assert.Contains(t, result, "Description: A test corpus")
+ assert.Contains(t, result, "Documents: 100")
+ assert.Contains(t, result, "Tokens: 50000")
+ assert.Contains(t, result, "Sentences: 2500")
+ assert.Contains(t, result, "Paragraphs: 500")
+ assert.Contains(t, result, "2. Test Corpus 2")
+ assert.Contains(t, result, "ID: corpus2")
+ assert.Contains(t, result, "Documents: 200")
+ assert.Contains(t, result, "Tokens: 75000")
+}
+
+func TestMetadataTool_formatCorpusStatistics(t *testing.T) {
+ client := &service.Client{}
+ tool := NewMetadataTool(client)
+
+ // Test minimal statistics
+ minimalStats := &service.StatisticsResponse{
+ Documents: 100,
+ Tokens: 50000,
+ }
+
+ result := tool.formatCorpusStatistics("test-corpus", minimalStats)
+ assert.Contains(t, result, "KorAP Corpus Statistics")
+ assert.Contains(t, result, "Corpus Query: test-corpus")
+ assert.Contains(t, result, "Documents: 100")
+ assert.Contains(t, result, "Tokens: 50000")
+
+ // Test complete statistics with additional fields
+ completeStats := &service.StatisticsResponse{
+ Documents: 200,
+ Tokens: 100000,
+ Sentences: 5000,
+ Paragraphs: 1000,
+ Fields: map[string]interface{}{
+ "genre": "literature",
+ "language": "German",
+ "year": 2023,
+ },
+ }
+
+ result = tool.formatCorpusStatistics("complete-corpus", completeStats)
+ assert.Contains(t, result, "KorAP Corpus Statistics")
+ assert.Contains(t, result, "Corpus Query: complete-corpus")
+ assert.Contains(t, result, "Documents: 200")
+ assert.Contains(t, result, "Tokens: 100000")
+ assert.Contains(t, result, "Sentences: 5000")
+ assert.Contains(t, result, "Paragraphs: 1000")
+ assert.Contains(t, result, "Additional Fields:")
+ assert.Contains(t, result, "genre: literature")
+ assert.Contains(t, result, "language: German")
+ assert.Contains(t, result, "year: 2023")
+
+ // Test empty corpus query (all available data)
+ result = tool.formatCorpusStatistics("", minimalStats)
+ assert.Contains(t, result, "KorAP Corpus Statistics")
+ assert.Contains(t, result, "Corpus Query: (all available data)")
+ assert.Contains(t, result, "Documents: 100")
+ assert.Contains(t, result, "Tokens: 50000")
+}