Skip to main content
Glama
mcp-server.test.ts27 kB
/** * CognitiveMCPServer Tests * * Consolidated tests for MCP server initialization, tool registration, connection handling, * error recovery, and essential tool execution (memory and metacognitive tools). * * Requirements: 2.1, 2.4, 3.1, 7.1, 11.1, 11.2, 11.3, 11.4, 11.5 */ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { Memory, MemoryMetadata, MemorySectorType } from "../../../memory/types.js"; import { CognitiveMCPServer } from "../../../server/mcp-server.js"; import type { MCPTool } from "../../../server/types.js"; // Mock all dependencies - use simple mocks like memory-tools.test.ts vi.mock("../../../memory/memory-repository.js"); vi.mock("../../../reasoning/orchestrator.js"); vi.mock("../../../framework/framework-selector.js"); vi.mock("../../../confidence/multi-dimensional-assessor.js"); vi.mock("../../../bias/bias-pattern-recognizer.js"); vi.mock("../../../emotion/circumplex-analyzer.js"); vi.mock("../../../metacognitive/performance-monitoring-system.js"); vi.mock("../../../database/connection-manager.js"); vi.mock("../../../embeddings/embedding-engine.js"); vi.mock("../../../utils/logger.js", () => ({ Logger: { info: vi.fn(), error: vi.fn(), warn: vi.fn(), debug: vi.fn(), }, })); /** * Creates a mock memory for testing */ function createMockMemory( id: string, userId: string, strength: number, content: string = "Test content" ): Memory { const now = new Date(); const metadata: MemoryMetadata = { keywords: ["test"], tags: ["unit-test"], category: "test", importance: 0.5, }; return { id, content, createdAt: now, lastAccessed: now, accessCount: 1, salience: 0.5, decayRate: 0.01, strength, userId, sessionId: "test-session", primarySector: "semantic" as MemorySectorType, metadata, }; } describe("CognitiveMCPServer", () => { let server: CognitiveMCPServer; beforeEach(() => { vi.clearAllMocks(); server = new CognitiveMCPServer(); }); afterEach(async () => { if (server?.isInitialized) { try { await server.shutdown(); } catch { // Ignore errors during cleanup } } vi.clearAllMocks(); }); describe("Server Startup and Initialization", () => { it("should start successfully with all components", async () => { vi.spyOn(server as any, "initializeComponents").mockResolvedValue(undefined); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); expect(server.isInitialized).toBe(true); }); it("should initialize all cognitive components", async () => { vi.spyOn(server as any, "initializeComponents").mockImplementation(async () => { server.memoryRepository = {} as any; server.reasoningOrchestrator = {} as any; server.frameworkSelector = {} as any; server.confidenceAssessor = {} as any; server.biasDetector = {} as any; server.emotionAnalyzer = {} as any; server.performanceMonitor = {} as any; }); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); expect(server.memoryRepository).toBeDefined(); expect(server.reasoningOrchestrator).toBeDefined(); expect(server.frameworkSelector).toBeDefined(); }); it("should handle initialization timeout", async () => { vi.spyOn(server as any, "initializeComponents").mockImplementation( () => new Promise((resolve) => setTimeout(resolve, 10000)) ); await expect(server.initialize()).rejects.toThrow("Initialization timeout"); }); it("should not allow double initialization", async () => { vi.spyOn(server as any, "initializeComponents").mockResolvedValue(undefined); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); await expect(server.initialize()).rejects.toThrow("Server already initialized"); }); it("should rollback on initialization failure", async () => { vi.spyOn(server as any, "initializeMemoryRepository").mockRejectedValue( new Error("Database connection failed") ); vi.spyOn(server as any, "rollbackInitialization").mockResolvedValue(undefined); await expect(server.initialize()).rejects.toThrow(); expect(server.isInitialized).toBe(false); }); }); describe("Server Shutdown and Cleanup", () => { beforeEach(async () => { vi.spyOn(server as any, "initializeComponents").mockResolvedValue(undefined); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); }); it("should shutdown gracefully", async () => { vi.spyOn(server as any, "shutdownComponents").mockResolvedValue(undefined); await server.shutdown(); expect(server.isInitialized).toBe(false); }); it("should handle shutdown errors gracefully", async () => { vi.spyOn(server as any, "shutdownComponents").mockRejectedValue( new Error("Component shutdown failed") ); await expect(server.shutdown()).resolves.not.toThrow(); }); it("should not allow operations after shutdown", async () => { vi.spyOn(server as any, "shutdownComponents").mockResolvedValue(undefined); await server.shutdown(); const result = await server.executeTool("test_tool", {}); expect(result.success).toBe(false); expect(result.error).toContain("Server not initialized"); }); }); describe("Tool Registration and Discovery", () => { beforeEach(async () => { vi.spyOn(server as any, "initializeComponents").mockResolvedValue(undefined); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); }); it("should register and retrieve tools", async () => { server.toolRegistry.registerTool({ name: "test_tool", description: "Test tool", inputSchema: { type: "object", properties: {} }, handler: async () => ({ success: true }), }); const tools = server.getTools(); expect(tools.length).toBeGreaterThan(0); expect(tools.find((t) => t.name === "test_tool")).toBeDefined(); }); it("should prevent duplicate tool registration", async () => { const tool: MCPTool = { name: "test_tool", description: "Test tool", inputSchema: { type: "object", properties: {} }, handler: async () => ({ success: true }), }; server.toolRegistry.registerTool(tool); expect(() => server.toolRegistry.registerTool(tool)).toThrow("Tool already registered"); }); it("should validate tool schemas on registration", async () => { const invalidTool: any = { name: "invalid_tool", }; expect(() => server.toolRegistry.registerTool(invalidTool)).toThrow("Invalid tool schema"); }); }); describe("Health Check and Status", () => { beforeEach(async () => { vi.spyOn(server as any, "initializeComponents").mockImplementation(async () => { server.memoryRepository = {} as any; server.reasoningOrchestrator = {} as any; server.frameworkSelector = {} as any; server.confidenceAssessor = {} as any; server.evidenceExtractor = {} as any; server.biasDetector = {} as any; server.emotionAnalyzer = {} as any; server.performanceMonitor = {} as any; (server as any).databaseManager = { healthCheck: vi.fn().mockResolvedValue(true), }; }); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); }); it("should provide health check endpoint", async () => { vi.spyOn(server as any, "checkConnection").mockResolvedValue(true); const health = await server.healthCheck(); expect(health).toBeDefined(); expect(health.healthy).toBeDefined(); }); it("should report component status", async () => { vi.spyOn(server as any, "checkConnection").mockResolvedValue(true); const health = await server.healthCheck(); expect(health.components).toBeDefined(); expect(health.components.memoryRepository).toBe("healthy"); }); it("should report unhealthy when components fail", async () => { server.memoryRepository = undefined; vi.spyOn(server as any, "checkConnection").mockResolvedValue(true); const health = await server.healthCheck(); expect(health.healthy).toBe(false); expect(health.components.memoryRepository).toBe("unhealthy"); }); }); describe("Tool Execution", () => { beforeEach(async () => { vi.spyOn(server as any, "initializeComponents").mockResolvedValue(undefined); vi.spyOn(server as any, "registerTools").mockImplementation(() => {}); await server.initialize(); }); it("should execute tools successfully", async () => { server.toolRegistry.registerTool({ name: "test_tool", description: "Test tool", inputSchema: { type: "object", properties: {} }, handler: async () => ({ success: true, data: { value: "test" } }), }); const result = await server.executeTool("test_tool", {}); expect(result.success).toBe(true); expect((result.data as any).value).toBe("test"); }); it("should validate tool parameters", async () => { server.toolRegistry.registerTool({ name: "param_tool", description: "Tool with parameters", inputSchema: { type: "object", properties: { required_param: { type: "string" } }, required: ["required_param"], }, handler: async (params: any) => ({ success: true, params }), }); const result = await server.executeTool("param_tool", {}); expect(result.success).toBe(false); expect(result.error).toContain("required_param"); }); it("should handle tool not found", async () => { const result = await server.executeTool("nonexistent_tool", {}); expect(result.success).toBe(false); expect(result.error).toContain("Tool not found"); }); it("should handle tool execution errors", async () => { server.toolRegistry.registerTool({ name: "error_tool", description: "Tool that throws error", inputSchema: { type: "object", properties: {} }, handler: async () => { throw new Error("Tool execution failed"); }, }); const result = await server.executeTool("error_tool", {}); expect(result.success).toBe(false); expect(result.error).toContain("Tool execution failed"); }); it("should include metadata in responses", async () => { server.toolRegistry.registerTool({ name: "meta_tool", description: "Tool with metadata", inputSchema: { type: "object", properties: {} }, handler: async () => ({ success: true }), }); const result = await server.executeTool("meta_tool", {}); expect(result.metadata).toBeDefined(); expect(result.metadata?.timestamp).toBeDefined(); expect(result.metadata?.processingTime).toBeDefined(); }); }); }); describe("Batch Recall Handler - includeDeleted Parameter", () => { let server: CognitiveMCPServer; let mockBatchRetrieve: ReturnType<typeof vi.fn>; beforeEach(async () => { vi.clearAllMocks(); server = new CognitiveMCPServer(); mockBatchRetrieve = vi.fn(); vi.spyOn(server as any, "initializeComponents").mockImplementation(async () => { server.memoryRepository = { batchRetrieve: mockBatchRetrieve, healthCheck: vi.fn().mockResolvedValue({ healthy: true }), } as any; server.reasoningOrchestrator = {} as any; server.frameworkSelector = {} as any; server.confidenceAssessor = {} as any; server.evidenceExtractor = {} as any; server.biasDetector = {} as any; server.emotionAnalyzer = {} as any; server.performanceMonitor = {} as any; (server as any).databaseManager = { healthCheck: vi.fn().mockResolvedValue(true), }; }); await server.initialize(); }); afterEach(async () => { if (server?.isInitialized) { try { await server.shutdown(); } catch { // Ignore errors during cleanup } } vi.clearAllMocks(); }); it("should pass includeDeleted=true to batchRetrieve and return soft-deleted memories", async () => { const userId = "test-user"; const softDeletedMemory = createMockMemory("mem-1", userId, 0, "Soft-deleted memory"); const activeMemory = createMockMemory("mem-2", userId, 0.8, "Active memory"); mockBatchRetrieve.mockResolvedValue({ memories: [softDeletedMemory, activeMemory], notFound: [], processingTime: 10, }); const result = await server.executeTool("batch_recall", { userId, memoryIds: ["mem-1", "mem-2"], includeDeleted: true, }); expect(mockBatchRetrieve).toHaveBeenCalledWith({ userId, memoryIds: ["mem-1", "mem-2"], includeDeleted: true, }); expect(result.success).toBe(true); const data = result.data as any; expect(data.memories).toHaveLength(2); }); it("should default to excluding soft-deleted memories when includeDeleted is not provided", async () => { const userId = "test-user"; const activeMemory = createMockMemory("mem-2", userId, 0.8, "Active memory"); mockBatchRetrieve.mockResolvedValue({ memories: [activeMemory], notFound: ["mem-1"], processingTime: 10, }); const result = await server.executeTool("batch_recall", { userId, memoryIds: ["mem-1", "mem-2"], }); expect(mockBatchRetrieve).toHaveBeenCalledWith({ userId, memoryIds: ["mem-1", "mem-2"], includeDeleted: undefined, }); expect(result.success).toBe(true); const data = result.data as any; expect(data.memories).toHaveLength(1); }); it("should handle errors from batchRetrieve gracefully", async () => { mockBatchRetrieve.mockRejectedValue(new Error("Database connection failed")); const result = await server.executeTool("batch_recall", { userId: "test-user", memoryIds: ["mem-1"], includeDeleted: true, }); expect(result.success).toBe(false); expect(result.error).toContain("Database connection failed"); }); }); describe("Memory Tools - Essential Operations", () => { let server: CognitiveMCPServer; let mockMemoryRepository: any; beforeEach(async () => { vi.clearAllMocks(); mockMemoryRepository = { create: vi.fn(), retrieve: vi.fn(), update: vi.fn(), delete: vi.fn(), search: vi.fn(), }; server = new CognitiveMCPServer(); server.memoryRepository = mockMemoryRepository; (server as any).databaseManager = { healthCheck: vi.fn().mockResolvedValue(true), }; server.isInitialized = true; }); afterEach(async () => { if (server?.toolRegistry) { server.toolRegistry.clear(); } if (server) { server.isInitialized = false; } vi.clearAllMocks(); }); describe("remember tool", () => { beforeEach(() => { server.toolRegistry.registerTool({ name: "remember", description: "Store a new memory", inputSchema: { type: "object", properties: { content: { type: "string" }, type: { type: "string", enum: ["episodic", "semantic", "procedural", "emotional", "reflective"], }, userId: { type: "string" }, }, required: ["content", "type", "userId"], }, handler: async (params: any) => { const memory = await mockMemoryRepository.create( { content: params.content, userId: params.userId, primarySector: params.type, sessionId: "test", }, { keywords: [], tags: [], importance: 0.5, isAtomic: true } ); return { success: true, data: { memoryId: memory.id } }; }, }); }); it("should store memory with required parameters", async () => { mockMemoryRepository.create.mockResolvedValue({ id: "mem_123", content: "Test" }); const result = await server.executeTool("remember", { content: "Test memory content", type: "episodic", userId: "user_123", }); expect(result.success).toBe(true); expect((result.data as any).memoryId).toBe("mem_123"); expect(mockMemoryRepository.create).toHaveBeenCalled(); }); it("should validate remember tool requires content parameter", async () => { const result = await server.executeTool("remember", { type: "episodic", userId: "user_123", }); expect(result.success).toBe(false); expect(result.error).toContain("content"); }); it("should handle repository errors during memory creation", async () => { mockMemoryRepository.create.mockRejectedValue(new Error("Database error")); const result = await server.executeTool("remember", { content: "Test", type: "episodic", userId: "user_123", }); expect(result.success).toBe(false); expect(result.error).toContain("Database error"); }); }); describe("recall tool", () => { beforeEach(() => { server.toolRegistry.registerTool({ name: "recall", description: "Retrieve memories", inputSchema: { type: "object", properties: { memoryId: { type: "string" }, cue: { type: "string" }, userId: { type: "string" }, }, required: ["userId"], }, handler: async (params: any) => { if (params.memoryId) { const memory = await mockMemoryRepository.retrieve(params.memoryId, params.userId); return { success: true, data: { memories: memory ? [memory] : [], count: memory ? 1 : 0 }, }; } else if (params.cue) { const result = await mockMemoryRepository.search({ text: params.cue }); return { success: true, data: { memories: result.memories, count: result.totalCount } }; } return { success: false, error: "Either memoryId or cue must be provided" }; }, }); }); it("should retrieve memory by ID", async () => { mockMemoryRepository.retrieve.mockResolvedValue(createMockMemory("mem_123", "user_123", 1.0)); const result = await server.executeTool("recall", { memoryId: "mem_123", userId: "user_123", }); expect(result.success).toBe(true); expect((result.data as any).memories).toHaveLength(1); }); it("should handle non-existent memory", async () => { mockMemoryRepository.retrieve.mockResolvedValue(null); const result = await server.executeTool("recall", { memoryId: "nonexistent", userId: "user_123", }); expect(result.success).toBe(true); expect((result.data as any).memories).toHaveLength(0); }); }); describe("forget tool", () => { beforeEach(() => { server.toolRegistry.registerTool({ name: "forget", description: "Delete a memory", inputSchema: { type: "object", properties: { memoryId: { type: "string" }, userId: { type: "string" }, soft: { type: "boolean" }, }, required: ["memoryId", "userId"], }, handler: async (params: any) => { await mockMemoryRepository.delete(params.memoryId, params.soft ?? true); return { success: true, data: { memoryId: params.memoryId, deletionType: (params.soft ?? true) ? "soft" : "hard", }, }; }, }); }); it("should perform soft delete by default", async () => { mockMemoryRepository.delete.mockResolvedValue(undefined); const result = await server.executeTool("forget", { memoryId: "mem_123", userId: "user_123", }); expect(result.success).toBe(true); expect((result.data as any).deletionType).toBe("soft"); expect(mockMemoryRepository.delete).toHaveBeenCalledWith("mem_123", true); }); it("should perform hard delete when specified", async () => { mockMemoryRepository.delete.mockResolvedValue(undefined); const result = await server.executeTool("forget", { memoryId: "mem_123", userId: "user_123", soft: false, }); expect(result.success).toBe(true); expect((result.data as any).deletionType).toBe("hard"); }); }); }); describe("Metacognitive Tools - Essential Operations", () => { let server: CognitiveMCPServer; let mockConfidenceAssessor: any; let mockBiasDetector: any; let mockEmotionAnalyzer: any; beforeEach(async () => { vi.clearAllMocks(); mockConfidenceAssessor = { assessConfidence: vi.fn() }; mockBiasDetector = { detectBiasesFromText: vi.fn() }; mockEmotionAnalyzer = { analyzeCircumplex: vi.fn(), classifyEmotions: vi.fn() }; server = new CognitiveMCPServer(); server.confidenceAssessor = mockConfidenceAssessor; server.biasDetector = mockBiasDetector; server.biasCorrector = { getSuggestion: vi.fn().mockReturnValue({ biasType: "confirmation", suggestion: "Seek disconfirming evidence", techniques: ["Search for contradicting evidence"], challengeQuestions: ["What evidence would prove this wrong?"], }), } as any; server.emotionAnalyzer = mockEmotionAnalyzer; (server as any).databaseManager = { healthCheck: vi.fn().mockResolvedValue(true) }; server.isInitialized = true; (server as any).registerMetacognitiveTools(); }); afterEach(async () => { if (server?.toolRegistry) { server.toolRegistry.clear(); } if (server) { server.isInitialized = false; } vi.clearAllMocks(); }); describe("assess_confidence tool", () => { it("should assess confidence with multi-dimensional analysis", async () => { mockConfidenceAssessor.assessConfidence.mockResolvedValue({ overallConfidence: 0.85, dimensions: { evidenceQuality: 0.9, reasoningCoherence: 0.85, completeness: 0.8, uncertaintyLevel: 0.15, biasFreedom: 0.9, }, interpretation: "High confidence", recommendations: [], }); const result = await server.executeTool("assess_confidence", { reasoning: "Based on metrics, optimization will improve throughput by 40%", evidence: ["Benchmark results", "Load tests"], }); expect(result.success).toBe(true); expect((result.data as any).overallConfidence).toBe(0.85); expect((result.data as any).dimensions).toBeDefined(); }); it("should validate assess_confidence tool requires reasoning parameter", async () => { const result = await server.executeTool("assess_confidence", { evidence: ["Some evidence"], }); expect(result.success).toBe(false); expect(result.error).toContain("reasoning"); }); it("should handle confidence assessment errors gracefully", async () => { mockConfidenceAssessor.assessConfidence.mockRejectedValue(new Error("Assessment failed")); const result = await server.executeTool("assess_confidence", { reasoning: "Test reasoning", }); expect(result.success).toBe(false); expect(result.error).toContain("Assessment failed"); }); }); describe("detect_bias tool", () => { it("should detect biases and provide corrections", async () => { mockBiasDetector.detectBiasesFromText.mockReturnValue([ { type: "confirmation", severity: 0.7, description: "Seeking only supporting evidence", evidence: ["Ignored contradictory data"], }, ]); const result = await server.executeTool("detect_bias", { reasoning: "All the data supports my hypothesis", }); expect(result.success).toBe(true); expect((result.data as any).biases).toHaveLength(1); expect((result.data as any).biases[0].type).toBe("confirmation"); expect((result.data as any).biases[0].correction).toBeDefined(); }); it("should handle no biases detected", async () => { mockBiasDetector.detectBiasesFromText.mockReturnValue([]); const result = await server.executeTool("detect_bias", { reasoning: "Objective analysis based on comprehensive data", }); expect(result.success).toBe(true); expect((result.data as any).biases).toHaveLength(0); }); it("should validate detect_bias tool requires reasoning parameter", async () => { const result = await server.executeTool("detect_bias", { context: "Test context", }); expect(result.success).toBe(false); expect(result.error).toContain("reasoning"); }); }); describe("detect_emotion tool", () => { it("should detect emotions using Circumplex model", async () => { mockEmotionAnalyzer.analyzeCircumplex.mockReturnValue({ valence: 0.7, arousal: 0.6, dominance: 0.5, confidence: 0.85, }); mockEmotionAnalyzer.classifyEmotions.mockReturnValue([]); const result = await server.executeTool("detect_emotion", { text: "I'm really excited about this project!", }); expect(result.success).toBe(true); expect((result.data as any).circumplex).toBeDefined(); expect((result.data as any).circumplex.valence).toBe(0.7); }); it("should classify discrete emotions when requested", async () => { mockEmotionAnalyzer.analyzeCircumplex.mockReturnValue({ valence: 0.8, arousal: 0.7, dominance: 0.6, confidence: 0.85, }); mockEmotionAnalyzer.classifyEmotions.mockReturnValue([ { emotion: "joy", intensity: 0.85, confidence: 0.9 }, ]); const result = await server.executeTool("detect_emotion", { text: "I'm so happy!", includeDiscrete: true, }); expect(result.success).toBe(true); expect((result.data as any).discrete).toBeDefined(); expect((result.data as any).discrete[0].emotion).toBe("joy"); }); it("should validate detect_emotion tool requires text parameter", async () => { const result = await server.executeTool("detect_emotion", { includeDiscrete: true, }); expect(result.success).toBe(false); expect(result.error).toContain("text"); }); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/keyurgolani/ThoughtMcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server