Add integration tests

Change-Id: If46e6ddd7713fe53d5e83bb8a3c4ed01eb3b13fb
diff --git a/mcp/integration_test.go b/mcp/integration_test.go
new file mode 100644
index 0000000..bc04434
--- /dev/null
+++ b/mcp/integration_test.go
@@ -0,0 +1,276 @@
+package mcp_test
+
+import (
+	"context"
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"net/http/httptest"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/korap/korap-mcp/config"
+	"github.com/korap/korap-mcp/logger"
+	"github.com/korap/korap-mcp/mcp"
+	"github.com/korap/korap-mcp/service"
+	"github.com/korap/korap-mcp/tools"
+	"github.com/korap/korap-mcp/validation"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+)
+
+// MockKorAPServer provides mock KorAP responses for integration testing
+type MockKorAPServer struct {
+	server   *httptest.Server
+	requests []string
+}
+
+func NewMockKorAPServer() *MockKorAPServer {
+	mock := &MockKorAPServer{
+		requests: make([]string, 0),
+	}
+
+	mock.server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		// Record the request
+		mock.requests = append(mock.requests, fmt.Sprintf("%s %s", r.Method, r.URL.Path))
+
+		// Handle different endpoints
+		switch {
+		case r.URL.Path == "/oauth2/token":
+			w.Header().Set("Content-Type", "application/json")
+			response := map[string]interface{}{
+				"access_token": "mock_access_token",
+				"token_type":   "Bearer",
+				"expires_in":   3600,
+			}
+			json.NewEncoder(w).Encode(response)
+
+		case r.URL.Path == "/search":
+			w.Header().Set("Content-Type", "application/json")
+			response := map[string]interface{}{
+				"meta": map[string]interface{}{
+					"totalResults": 42,
+					"startIndex":   0,
+					"itemsPerPage": 25,
+				},
+				"matches": []map[string]interface{}{
+					{
+						"textSigle":   "GOE/AGI/00000",
+						"corpusSigle": "GOE",
+						"title":       "Test Document",
+						"snippet":     "Test <mark>query</mark> result",
+					},
+				},
+			}
+			json.NewEncoder(w).Encode(response)
+
+		case r.URL.Path == "/corpus":
+			w.Header().Set("Content-Type", "application/json")
+			response := map[string]interface{}{
+				"meta":  map[string]interface{}{"totalResults": 2},
+				"@type": "koral:docGroup",
+				"operands": []map[string]interface{}{
+					{"key": "corpusSigle", "value": "GOE", "title": "Goethe-Korpus"},
+					{"key": "corpusSigle", "value": "WPD", "title": "Wikipedia-Korpus"},
+				},
+			}
+			json.NewEncoder(w).Encode(response)
+
+		case strings.HasPrefix(r.URL.Path, "/corpus/") && strings.HasSuffix(r.URL.Path, "/statistics"):
+			w.Header().Set("Content-Type", "application/json")
+			response := map[string]interface{}{
+				"key":       "corpusSigle",
+				"value":     "test",
+				"tokens":    11823,
+				"sentences": 2156,
+			}
+			json.NewEncoder(w).Encode(response)
+
+		default:
+			http.NotFound(w, r)
+		}
+	}))
+
+	return mock
+}
+
+func (m *MockKorAPServer) URL() string {
+	return m.server.URL
+}
+
+func (m *MockKorAPServer) Close() {
+	m.server.Close()
+}
+
+func (m *MockKorAPServer) GetRequests() []string {
+	return m.requests
+}
+
+func (m *MockKorAPServer) Reset() {
+	m.requests = make([]string, 0)
+}
+
+func TestIntegration_ComponentSetup(t *testing.T) {
+	// Test that all main components can be created properly
+	logConfig := &config.LoggingConfig{
+		Level:  "info",
+		Format: "json",
+	}
+	testLogger := logger.GetLogger(logConfig)
+
+	// Test registry
+	registry := tools.NewRegistry()
+	assert.NotNil(t, registry)
+	assert.Equal(t, 0, registry.Count())
+
+	// Test validator
+	validator := validation.New(testLogger)
+	assert.NotNil(t, validator)
+
+	// Test MCP server
+	mcpServer := mcp.NewServer("korap-mcp-server", "1.0.0")
+	assert.NotNil(t, mcpServer)
+
+	name, version := mcpServer.GetServerInfo()
+	assert.Equal(t, "korap-mcp-server", name)
+	assert.Equal(t, "1.0.0", version)
+}
+
+func TestIntegration_ServiceClientWithMockServer(t *testing.T) {
+	// Create mock server
+	mockServer := NewMockKorAPServer()
+	defer mockServer.Close()
+
+	// Create OAuth config
+	oauthConfig := &config.OAuthConfig{
+		Enabled:      true,
+		ClientID:     "test_client",
+		ClientSecret: "test_secret",
+		TokenURL:     mockServer.URL() + "/oauth2/token",
+		Scopes:       []string{"read"},
+	}
+
+	// Create service client
+	client, err := service.NewClient(service.ClientOptions{
+		BaseURL:     mockServer.URL(),
+		Timeout:     10 * time.Second,
+		OAuthConfig: oauthConfig,
+	})
+	require.NoError(t, err)
+	assert.NotNil(t, client)
+
+	// Test authentication
+	err = client.AuthenticateWithClientCredentials(context.Background())
+	assert.NoError(t, err)
+	assert.True(t, client.IsAuthenticated())
+
+	// Verify OAuth request was made
+	requests := mockServer.GetRequests()
+	assert.Contains(t, requests, "POST /oauth2/token")
+}
+
+func TestIntegration_ToolsWithMockServer(t *testing.T) {
+	// Create mock server
+	mockServer := NewMockKorAPServer()
+	defer mockServer.Close()
+
+	// Create service client
+	oauthConfig := &config.OAuthConfig{
+		Enabled:      true,
+		ClientID:     "test_client",
+		ClientSecret: "test_secret",
+		TokenURL:     mockServer.URL() + "/oauth2/token",
+		Scopes:       []string{"read"},
+	}
+
+	client, err := service.NewClient(service.ClientOptions{
+		BaseURL:     mockServer.URL(),
+		Timeout:     10 * time.Second,
+		OAuthConfig: oauthConfig,
+	})
+	require.NoError(t, err)
+
+	// Create registry and tools
+	registry := tools.NewRegistry()
+	searchTool := tools.NewSearchTool(client)
+	metadataTool := tools.NewMetadataTool(client)
+
+	err = registry.Register(searchTool)
+	assert.NoError(t, err)
+
+	err = registry.Register(metadataTool)
+	assert.NoError(t, err)
+
+	// Verify tools were registered
+	assert.Equal(t, 2, registry.Count())
+	toolNames := registry.Names()
+	assert.Contains(t, toolNames, "korap_search")
+	assert.Contains(t, toolNames, "korap_metadata")
+
+	// Test tool retrieval
+	tool, exists := registry.Get("korap_search")
+	assert.True(t, exists)
+	assert.NotNil(t, tool)
+	assert.Equal(t, "korap_search", tool.Name())
+	assert.Contains(t, tool.Description(), "KorAP")
+	assert.NotNil(t, tool.InputSchema())
+
+	// Test MCP tool listing
+	mcpTools := registry.List()
+	assert.Len(t, mcpTools, 2)
+}
+
+func TestIntegration_ConcurrentAccess(t *testing.T) {
+	// Create mock server
+	mockServer := NewMockKorAPServer()
+	defer mockServer.Close()
+
+	// Create service client
+	oauthConfig := &config.OAuthConfig{
+		Enabled:      true,
+		ClientID:     "test_client",
+		ClientSecret: "test_secret",
+		TokenURL:     mockServer.URL() + "/oauth2/token",
+		Scopes:       []string{"read"},
+	}
+
+	client, err := service.NewClient(service.ClientOptions{
+		BaseURL:     mockServer.URL(),
+		Timeout:     10 * time.Second,
+		OAuthConfig: oauthConfig,
+	})
+	require.NoError(t, err)
+
+	// Create registry with tools
+	registry := tools.NewRegistry()
+	searchTool := tools.NewSearchTool(client)
+
+	err = registry.Register(searchTool)
+	require.NoError(t, err)
+
+	// Test concurrent access to registry
+	const numGoroutines = 10
+	done := make(chan bool, numGoroutines)
+
+	for i := 0; i < numGoroutines; i++ {
+		go func(index int) {
+			defer func() { done <- true }()
+
+			// Test registry operations
+			assert.Equal(t, 1, registry.Count())
+
+			toolNames := registry.Names()
+			assert.Contains(t, toolNames, "korap_search")
+
+			tool, exists := registry.Get("korap_search")
+			assert.True(t, exists)
+			assert.NotNil(t, tool)
+		}(i)
+	}
+
+	// Wait for all goroutines to complete
+	for range numGoroutines {
+		<-done
+	}
+}