UNPKG

@emmahyde/thinking-patterns

Version:

MCP server combining systematic thinking, mental models, debugging approaches, and stochastic algorithms for comprehensive cognitive pattern support

343 lines (342 loc) 15.3 kB
/** * Integration tests for tool registry system * Tests tool discovery, routing, and registry-first vs legacy fallback */ import { ToolRegistry, BaseToolServer } from '../../src/base/BaseToolServer.js'; import { SequentialThoughtSchema } from '../../src/schemas/SequentialThoughtSchema.js'; import { validSequentialThought } from '../helpers/testFixtures.js'; // Test implementation of BaseToolServer for registry testing class TestToolServer extends BaseToolServer { constructor() { super(SequentialThoughtSchema); } handle(validInput) { return { success: true, message: `Processed thought: ${validInput.thought}` }; } } // Another test server for multiple tool testing class AlternativeToolServer extends BaseToolServer { constructor() { super(SequentialThoughtSchema); } handle(validInput) { return { result: `Alternative processing of: ${validInput.thought}`, processed: true }; } } describe('Tool Registry Integration Tests', () => { let testToolServer; let alternativeToolServer; beforeEach(() => { testToolServer = new TestToolServer(); alternativeToolServer = new AlternativeToolServer(); // Clear the registry before each test ToolRegistry.tools = []; }); afterEach(() => { // Clean up after each test ToolRegistry.tools = []; }); describe('tool registration', () => { it('should register a tool successfully', () => { const toolEntry = { name: "test-tool", schema: SequentialThoughtSchema, server: testToolServer, description: "Test tool for integration testing" }; ToolRegistry.register(toolEntry); const registeredTools = ToolRegistry.getAllTools(); expect(registeredTools).toHaveLength(1); expect(registeredTools[0].name).toBe("test-tool"); expect(registeredTools[0].description).toBe("Test tool for integration testing"); }); it('should register multiple tools', () => { const toolEntry1 = { name: "tool-one", schema: SequentialThoughtSchema, server: testToolServer, description: "First test tool" }; const toolEntry2 = { name: "tool-two", schema: SequentialThoughtSchema, server: alternativeToolServer, description: "Second test tool" }; ToolRegistry.register(toolEntry1); ToolRegistry.register(toolEntry2); const registeredTools = ToolRegistry.getAllTools(); expect(registeredTools).toHaveLength(2); const toolNames = registeredTools.map(tool => tool.name); expect(toolNames).toContain("tool-one"); expect(toolNames).toContain("tool-two"); }); it('should handle tool registration with minimal fields', () => { const minimalToolEntry = { name: "minimal-tool", schema: SequentialThoughtSchema, server: testToolServer // No description provided }; ToolRegistry.register(minimalToolEntry); const registeredTools = ToolRegistry.getAllTools(); expect(registeredTools).toHaveLength(1); expect(registeredTools[0].name).toBe("minimal-tool"); expect(registeredTools[0].description).toBeUndefined(); }); }); describe('tool discovery', () => { beforeEach(() => { // Register test tools ToolRegistry.register({ name: "sequential_thinking", schema: SequentialThoughtSchema, server: testToolServer, description: "Sequential thinking tool" }); ToolRegistry.register({ name: "mental_model", schema: SequentialThoughtSchema, server: alternativeToolServer, description: "Mental model tool" }); }); it('should find registered tool by name', () => { const foundTool = ToolRegistry.findTool("sequential_thinking"); expect(foundTool).toBeDefined(); expect(foundTool?.name).toBe("sequential_thinking"); expect(foundTool?.server).toBe(testToolServer); }); it('should return undefined for non-existent tool', () => { const foundTool = ToolRegistry.findTool("nonexistent-tool"); expect(foundTool).toBeUndefined(); }); it('should handle case-sensitive tool names', () => { const foundTool = ToolRegistry.findTool("SequentialThinking"); // Different case expect(foundTool).toBeUndefined(); }); it('should get all registered tools', () => { const allTools = ToolRegistry.getAllTools(); expect(allTools).toHaveLength(2); expect(allTools.map(t => t.name)).toEqual(expect.arrayContaining(["sequential_thinking", "mental_model"])); }); }); describe('tool execution workflow', () => { beforeEach(() => { ToolRegistry.register({ name: "test-processor", schema: SequentialThoughtSchema, server: testToolServer, description: "Test processing tool" }); }); it('should execute tool through registry', () => { const tool = ToolRegistry.findTool("test-processor"); expect(tool).toBeDefined(); const response = tool.server.run(validSequentialThought); expect(response.isError).toBeUndefined(); expect(response.content).toHaveLength(1); expect(response.content[0].type).toBe("text"); const result = JSON.parse(response.content[0].text); expect(result.success).toBe(true); expect(result.message).toContain("Processed thought"); }); it('should handle validation errors in registry workflow', () => { const tool = ToolRegistry.findTool("test-processor"); expect(tool).toBeDefined(); const invalidInput = { thought: "Valid thought", // Missing required fields }; const response = tool.server.run(invalidInput); expect(response.isError).toBe(true); expect(response.content).toHaveLength(1); const error = JSON.parse(response.content[0].text); expect(error.error).toContain("Validation failed"); }); it('should route different tools correctly', () => { ToolRegistry.register({ name: "alternative-processor", schema: SequentialThoughtSchema, server: alternativeToolServer, description: "Alternative processing tool" }); const tool1 = ToolRegistry.findTool("test-processor"); const tool2 = ToolRegistry.findTool("alternative-processor"); const response1 = tool1.server.run(validSequentialThought); const response2 = tool2.server.run(validSequentialThought); const result1 = JSON.parse(response1.content[0].text); const result2 = JSON.parse(response2.content[0].text); // Different servers should produce different results expect(result1.message).toContain("Processed thought"); expect(result2.result).toContain("Alternative processing"); expect(result1).not.toEqual(result2); }); }); describe('MCP tool definitions generation', () => { beforeEach(() => { ToolRegistry.register({ name: "sequential_thinking", schema: SequentialThoughtSchema, server: testToolServer, description: "Sequential thinking for systematic analysis" }); ToolRegistry.register({ name: "debugging", schema: SequentialThoughtSchema, server: alternativeToolServer // No description }); }); it('should generate tool definitions for MCP protocol', () => { const toolDefinitions = ToolRegistry.getToolDefinitions(); expect(toolDefinitions).toHaveLength(2); const sequentialTool = toolDefinitions.find(t => t.name === "sequential_thinking"); expect(sequentialTool).toBeDefined(); expect(sequentialTool?.description).toBe("Sequential thinking for systematic analysis"); expect(sequentialTool?.inputSchema).toBeDefined(); expect(sequentialTool?.inputSchema.type).toBe("object"); const debuggingTool = toolDefinitions.find(t => t.name === "debugging"); expect(debuggingTool).toBeDefined(); expect(debuggingTool?.description).toBe("Tool for debugging operations"); }); it('should provide default description when none specified', () => { const toolDefinitions = ToolRegistry.getToolDefinitions(); const toolWithoutDescription = toolDefinitions.find(t => t.name === "debugging"); expect(toolWithoutDescription?.description).toBe("Tool for debugging operations"); }); it('should include proper input schema structure', () => { const toolDefinitions = ToolRegistry.getToolDefinitions(); toolDefinitions.forEach(toolDef => { expect(toolDef.inputSchema).toBeDefined(); expect(toolDef.inputSchema.type).toBe("object"); expect(toolDef.inputSchema).toHaveProperty("properties"); expect(toolDef.inputSchema).toHaveProperty("required"); expect(toolDef.inputSchema).toHaveProperty("additionalProperties", false); // Should have actual properties from the schema, not empty expect(Object.keys(toolDef.inputSchema.properties)).not.toHaveLength(0); }); }); }); describe('registry isolation and cleanup', () => { it('should maintain registry state across tests properly', () => { // This test should start with empty registry expect(ToolRegistry.getAllTools()).toHaveLength(0); ToolRegistry.register({ name: "isolation-test", schema: SequentialThoughtSchema, server: testToolServer }); expect(ToolRegistry.getAllTools()).toHaveLength(1); }); it('should handle multiple registrations of same name', () => { const tool1 = { name: "duplicate-name", schema: SequentialThoughtSchema, server: testToolServer, description: "First registration" }; const tool2 = { name: "duplicate-name", schema: SequentialThoughtSchema, server: alternativeToolServer, description: "Second registration" }; ToolRegistry.register(tool1); ToolRegistry.register(tool2); const allTools = ToolRegistry.getAllTools(); expect(allTools).toHaveLength(2); // Both should be registered // Should find the first one registered const foundTool = ToolRegistry.findTool("duplicate-name"); expect(foundTool?.description).toBe("First registration"); }); }); describe('error handling and edge cases', () => { it('should handle server errors gracefully', () => { class ErrorThrowingServer extends BaseToolServer { constructor() { super(SequentialThoughtSchema); } handle(_validInput) { throw new Error("Server processing error"); } } ToolRegistry.register({ name: "error-server", schema: SequentialThoughtSchema, server: new ErrorThrowingServer() }); const tool = ToolRegistry.findTool("error-server"); const response = tool.server.run(validSequentialThought); expect(response.isError).toBe(true); const error = JSON.parse(response.content[0].text); expect(error.error).toBe("Server processing error"); }); it('should handle empty tool name', () => { ToolRegistry.register({ name: "", schema: SequentialThoughtSchema, server: testToolServer }); const foundTool = ToolRegistry.findTool(""); expect(foundTool).toBeDefined(); expect(foundTool?.name).toBe(""); }); it('should handle null/undefined search', () => { ToolRegistry.register({ name: "valid-tool", schema: SequentialThoughtSchema, server: testToolServer }); expect(ToolRegistry.findTool(null)).toBeUndefined(); expect(ToolRegistry.findTool(undefined)).toBeUndefined(); }); }); describe('performance and scalability', () => { it('should handle large number of tool registrations efficiently', () => { const start = Date.now(); // Register 100 tools for (let i = 0; i < 100; i++) { ToolRegistry.register({ name: `performance-tool-${i}`, schema: SequentialThoughtSchema, server: testToolServer, description: `Performance test tool ${i}` }); } const registrationTime = Date.now() - start; expect(registrationTime).toBeLessThan(1000); // Should be fast // Test lookup performance const lookupStart = Date.now(); for (let i = 0; i < 100; i++) { const tool = ToolRegistry.findTool(`performance-tool-${i}`); expect(tool).toBeDefined(); } const lookupTime = Date.now() - lookupStart; expect(lookupTime).toBeLessThan(500); // Lookups should be fast expect(ToolRegistry.getAllTools()).toHaveLength(100); }); it('should handle tool definition generation for many tools efficiently', () => { // Register multiple tools for (let i = 0; i < 50; i++) { ToolRegistry.register({ name: `def-tool-${i}`, schema: SequentialThoughtSchema, server: testToolServer, description: `Definition test tool ${i}` }); } const start = Date.now(); const definitions = ToolRegistry.getToolDefinitions(); const elapsed = Date.now() - start; expect(definitions).toHaveLength(50); expect(elapsed).toBeLessThan(200); // Should be fast }); }); });