Skip to main content
Glama
orneryd

M.I.M.I.R - Multi-agent Intelligent Memory & Insight Repository

by orneryd
get-task-context-tool.test.ts22.8 kB
/** * get_task_context MCP Tool Integration Tests * * Tests the complete flow of the get_task_context tool through the MCP interface: * - Tool registration and availability * - Parameter validation * - Context filtering for each agent type (PM, worker, QC) * - Error handling for missing tasks * - Integration with GraphManager and ContextManager */ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { createMockGraphManager } from './helpers/mockGraphManager.js'; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; describe('get_task_context MCP Tool (Integration)', () => { let graphManager: any; let server: Server; beforeEach(async () => { // Use in-memory mock GraphManager for tests graphManager = createMockGraphManager(); await graphManager.initialize(); await graphManager.clear('ALL'); // Small delay for database clear await new Promise(resolve => setTimeout(resolve, 50)); // Note: We'll test through the actual MCP server by importing the handler // For now, we'll test the underlying implementation directly }); afterEach(async () => { if (graphManager) { await graphManager.clear('ALL'); await graphManager.close(); } }); describe('Tool Availability', () => { it('should be registered in GRAPH_TOOLS', async () => { const { GRAPH_TOOLS } = await import('../src/tools/graph.tools.js'); const tool = GRAPH_TOOLS.find(t => t.name === 'get_task_context'); expect(tool).toBeDefined(); expect(tool?.name).toBe('get_task_context'); expect(tool?.description).toContain('filtered task context'); expect(tool?.description).toContain('PM/worker/QC'); }); it('should have correct input schema', async () => { const { GRAPH_TOOLS } = await import('../src/tools/graph.tools.js'); const tool = GRAPH_TOOLS.find(t => t.name === 'get_task_context'); expect(tool?.inputSchema.properties).toHaveProperty('taskId'); expect(tool?.inputSchema.properties).toHaveProperty('agentType'); expect(tool?.inputSchema.required).toEqual(['taskId', 'agentType']); // Check agentType enum const agentTypeProp = (tool?.inputSchema.properties as any).agentType; expect(agentTypeProp.enum).toEqual(['pm', 'worker', 'qc']); }); }); describe('Worker Context Filtering', () => { it('should return minimal context for worker agents', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with full PM context const taskNode = await graphManager.addNode('todo', { title: 'Build authentication API', requirements: 'Implement JWT authentication with refresh tokens', description: 'Create secure authentication endpoints', workerRole: 'Backend engineer with Node.js expertise', qcRole: 'Security auditor', files: Array.from({ length: 20 }, (_, i) => `src/auth/file-${i}.ts`), dependencies: ['task-dep-1', 'task-dep-2', 'task-dep-3', 'task-dep-4', 'task-dep-5', 'task-dep-6'], status: 'pending', priority: 'high', // Large PM-only fields research: JSON.stringify({ findings: Array.from({ length: 50 }, (_, i) => `Research finding ${i}`), alternatives: Array.from({ length: 20 }, (_, i) => `Alternative ${i}`) }), planningNotes: JSON.stringify(Array.from({ length: 100 }, (_, i) => `Planning note ${i}`)), architectureDecisions: JSON.stringify({ patterns: Array.from({ length: 30 }, (_, i) => `Pattern ${i}`), rationale: Array.from({ length: 30 }, (_, i) => `Rationale ${i}`) }) }); // Get worker context const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Verify worker context structure expect(result.context).toBeDefined(); expect(result.context.title).toBe('Build authentication API'); expect(result.context.requirements).toBe('Implement JWT authentication with refresh tokens'); expect(result.context.description).toBe('Create secure authentication endpoints'); expect(result.context.workerRole).toBe('Backend engineer with Node.js expertise'); // Files limited to 10 expect(result.context.files).toBeDefined(); expect(result.context.files.length).toBeLessThanOrEqual(10); // Dependencies limited to 5 expect(result.context.dependencies).toBeDefined(); expect(result.context.dependencies.length).toBeLessThanOrEqual(5); // PM fields should be filtered out expect((result.context as any).research).toBeUndefined(); expect((result.context as any).planningNotes).toBeUndefined(); expect((result.context as any).architectureDecisions).toBeUndefined(); // Verify 90%+ reduction expect(result.metrics.reductionPercent).toBeGreaterThanOrEqual(89); }); it('should include errorContext for retry scenarios', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with error context (after QC failure) const errorData = { previousAttempt: 1, errorMessage: 'Authentication tests failed', suggestedFix: 'Add error handling for expired tokens', qcFeedback: 'Missing edge case handling' }; const taskNode = await graphManager.addNode('todo', { title: 'Fix authentication bugs', requirements: 'Handle all edge cases', description: 'Fix issues from previous attempt', workerRole: 'Backend engineer', attemptNumber: 2, errorContext: JSON.stringify(errorData) }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Verify error context is included for retries expect(result.context.errorContext).toBeDefined(); expect(result.context.errorContext).toContain('previousAttempt'); expect(result.context.errorContext).toContain('Authentication tests failed'); }); it('should handle tasks with minimal fields', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with only required fields const taskNode = await graphManager.addNode('todo', { title: 'Simple task', requirements: 'Basic requirements', description: 'Minimal description' }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Should still work with minimal fields expect(result.context.title).toBe('Simple task'); expect(result.context.requirements).toBe('Basic requirements'); expect(result.context.description).toBe('Minimal description'); expect(result.context.files || []).toEqual([]); expect(result.context.dependencies || []).toEqual([]); }); }); describe('QC Context Filtering', () => { it('should return requirements + worker output for QC agents', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with worker output const verificationCriteria = { security: ['No hardcoded secrets', 'Input validation'], functionality: ['All endpoints work', 'Proper error handling'], codeQuality: ['Unit tests >80%', 'TypeScript types defined'] }; const taskNode = await graphManager.addNode('todo', { title: 'Authentication API', requirements: 'JWT auth with refresh tokens', description: 'Secure authentication', workerRole: 'Backend engineer', qcRole: 'Security auditor', verificationCriteria: JSON.stringify(verificationCriteria), workerOutput: 'Implemented JWT auth with bcrypt password hashing and refresh token rotation', status: 'awaiting_qc', // Should be filtered out for QC research: JSON.stringify({ findings: ['Finding 1', 'Finding 2'] }), planningNotes: JSON.stringify(['Note 1', 'Note 2']) }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'qc'); // Verify QC context has what's needed expect(result.context.requirements).toBe('JWT auth with refresh tokens'); expect(result.context.workerOutput).toBe('Implemented JWT auth with bcrypt password hashing and refresh token rotation'); expect(result.context.verificationCriteria).toContain('security'); // Files should be included (inherited from worker scope) // Files are optional, may be undefined // PM fields should be filtered out expect((result.context as any).research).toBeUndefined(); expect((result.context as any).planningNotes).toBeUndefined(); }); it('should handle QC context without worker output (first verification)', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Task without worker output yet const taskNode = await graphManager.addNode('todo', { title: 'New task', requirements: 'Requirements here', description: 'Description', qcRole: 'QC auditor', status: 'pending' }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'qc'); // Should still work without worker output expect(result.context.requirements).toBe('Requirements here'); expect(result.context.workerOutput).toBeUndefined(); }); }); describe('PM Context (Full Context)', () => { it('should return full context for PM agents', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with all fields const research = { findings: ['F1', 'F2'], alternatives: ['A1', 'A2'] }; const planningNotes = ['Note 1', 'Note 2']; const taskNode = await graphManager.addNode('todo', { title: 'Complex task', requirements: 'Complex requirements', description: 'Detailed description', workerRole: 'Engineer', qcRole: 'Auditor', files: ['file1.ts', 'file2.ts'], dependencies: ['dep1', 'dep2'], research: JSON.stringify(research), planningNotes: JSON.stringify(planningNotes), architectureDecisions: JSON.stringify({ patterns: ['P1'] }) }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'pm'); // PM should get everything expect(result.context.title).toBe('Complex task'); expect(result.context.requirements).toBe('Complex requirements'); expect(result.context.workerRole).toBe('Engineer'); expect(result.context.qcRole).toBe('Auditor'); expect(result.context.research).toBeDefined(); expect(result.context.planningNotes).toBeDefined(); expect(result.context.architectureDecisions).toBeDefined(); // No reduction for PM expect(result.metrics.reductionPercent).toBe(0); }); }); describe('Context Metrics', () => { it('should calculate accurate byte sizes and reduction percentages', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with measurable size difference const largeResearch = JSON.stringify({ findings: Array.from({ length: 100 }, (_, i) => `Finding ${i}: ${'x'.repeat(100)}`), alternatives: Array.from({ length: 50 }, (_, i) => `Alternative ${i}: ${'y'.repeat(100)}`) }); const taskNode = await graphManager.addNode('todo', { title: 'Task with large PM context', requirements: 'Requirements', description: 'Description', workerRole: 'Worker', files: Array.from({ length: 20 }, (_, i) => `file-${i}.ts`), research: largeResearch, planningNotes: JSON.stringify(Array.from({ length: 100 }, (_, i) => `Note ${i}`)) }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Verify metrics expect(result.metrics.originalSize).toBeGreaterThan(0); expect(result.metrics.filteredSize).toBeGreaterThan(0); expect(result.metrics.filteredSize).toBeLessThan(result.metrics.originalSize); expect(result.metrics.reductionPercent).toBeGreaterThan(0); expect(result.metrics.reductionPercent).toBeLessThan(100); // Verify removed and retained fields are tracked expect(result.metrics.fieldsRemoved).toContain('research'); expect(result.metrics.fieldsRemoved).toContain('planningNotes'); expect(result.metrics.fieldsRetained).toContain('title'); expect(result.metrics.fieldsRetained).toContain('requirements'); }); it('should validate worker context meets 90% reduction requirement', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with enough PM data to ensure 90%+ reduction const taskNode = await graphManager.addNode('todo', { title: 'Auth system', requirements: 'JWT authentication', description: 'Implement secure auth', workerRole: 'Backend engineer', files: Array.from({ length: 15 }, (_, i) => `src/file-${i}.ts`), dependencies: ['dep1', 'dep2', 'dep3', 'dep4', 'dep5', 'dep6'], research: JSON.stringify({ findings: Array.from({ length: 200 }, (_, i) => `Research finding ${i} with lots of detail about authentication patterns and security considerations`), alternatives: Array.from({ length: 100 }, (_, i) => `Alternative approach ${i} with detailed pros and cons analysis`) }), planningNotes: JSON.stringify( Array.from({ length: 150 }, (_, i) => `Planning note ${i} about implementation strategy and timeline`) ), architectureDecisions: JSON.stringify({ patterns: Array.from({ length: 80 }, (_, i) => `Architecture pattern ${i} with rationale`), rationale: Array.from({ length: 80 }, (_, i) => `Decision rationale ${i}`) }) }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Verify validation passes expect(result.metrics.reductionPercent).toBeGreaterThanOrEqual(89); }); }); describe('Error Handling', () => { it('should throw error for non-existent task', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); const contextManager = new ContextManager(graphManager); await expect( contextManager.getFilteredTaskContext('non-existent-task-id', 'worker') ).rejects.toThrow('Task not found'); }); it('should handle tasks with malformed JSON fields gracefully', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); // Create task with invalid JSON in a field const taskNode = await graphManager.addNode('todo', { title: 'Task with bad JSON', requirements: 'Requirements', description: 'Description', workerRole: 'Worker', errorContext: 'THIS IS NOT VALID JSON' // Invalid JSON }); const contextManager = new ContextManager(graphManager); const result = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); // Should still work, treating it as a string expect(result.context.title).toBe('Task with bad JSON'); expect(result.context.errorContext).toBe('THIS IS NOT VALID JSON'); }); }); describe('Multi-Agent Workflow Integration', () => { it('should support complete PM → Worker → QC flow', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); const contextManager = new ContextManager(graphManager); // 1. PM creates task with full context const taskNode = await graphManager.addNode('todo', { title: 'Build user registration', requirements: 'Email validation, password hashing, duplicate check', description: 'Complete user registration endpoint', workerRole: 'Backend developer with validation expertise', qcRole: 'Security specialist focusing on input validation', verificationCriteria: JSON.stringify({ security: ['Email validation prevents injection', 'Password meets complexity rules'], functionality: ['Duplicate email detection works', 'Success response correct'], codeQuality: ['Unit tests present', 'Error messages clear'] }), files: Array.from({ length: 12 }, (_, i) => `src/user/file-${i}.ts`), dependencies: ['task-db-schema', 'task-email-service'], maxRetries: 2, attemptNumber: 1, status: 'pending', research: JSON.stringify({ findings: ['Finding 1', 'Finding 2', 'Finding 3'] }), planningNotes: JSON.stringify(['Note 1', 'Note 2']) }); // 2. Worker gets minimal context const workerContext = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); expect(workerContext.context.title).toBeDefined(); expect(workerContext.context.workerRole).toBeDefined(); expect(workerContext.context.files.length).toBeLessThanOrEqual(10); expect((workerContext.context as any).research).toBeUndefined(); expect(workerContext.metrics.reductionPercent).toBeGreaterThanOrEqual(45); // 3. Worker completes task await graphManager.updateNode(taskNode.id, { workerOutput: 'Implemented registration with bcrypt hashing and email validation', status: 'awaiting_qc' }); // 4. QC gets verification context const qcContext = await contextManager.getFilteredTaskContext(taskNode.id, 'qc'); expect(qcContext.context.requirements).toBeDefined(); expect(qcContext.context.workerOutput).toBe('Implemented registration with bcrypt hashing and email validation'); expect(qcContext.context.verificationCriteria).toContain('security'); expect((qcContext.context as any).research).toBeUndefined(); // 5. PM can still access full context const pmContext = await contextManager.getFilteredTaskContext(taskNode.id, 'pm'); expect(pmContext.context.research).toBeDefined(); expect(pmContext.context.planningNotes).toBeDefined(); expect(pmContext.metrics.reductionPercent).toBe(0); }); it('should support retry workflow with error context', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); const contextManager = new ContextManager(graphManager); // Create task after QC failure const taskNode = await graphManager.addNode('todo', { title: 'Fix validation bugs', requirements: 'Proper email validation', description: 'Fix issues from attempt 1', workerRole: 'Backend developer', qcRole: 'Security auditor', attemptNumber: 2, maxRetries: 2, status: 'pending', errorContext: JSON.stringify({ previousAttempt: 1, qcFeedback: 'Email validation allows invalid formats', issues: ['Missing regex validation', 'No domain verification'], requiredFixes: ['Add proper email regex', 'Verify domain exists'] }), qcVerificationHistory: JSON.stringify([ { attempt: 1, passed: false, score: 45, feedback: 'Validation logic incomplete' } ]) }); // Worker gets context including error details const workerContext = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); expect(workerContext.context.errorContext).toBeDefined(); expect(workerContext.context.errorContext).toContain('previousAttempt'); expect(workerContext.context.errorContext).toContain('Email validation allows invalid formats'); expect(workerContext.context.attemptNumber).toBe(2); }); }); describe('Context Scope Validation', () => { it('should respect DEFAULT_CONTEXT_SCOPES for each agent type', async () => { const { ContextManager } = await import('../src/managers/ContextManager.js'); const { DEFAULT_CONTEXT_SCOPES } = await import('../src/types/context.types.js'); const taskNode = await graphManager.addNode('todo', { title: 'Test task', requirements: 'Requirements', description: 'Description', workerRole: 'Worker', qcRole: 'QC', files: ['file1.ts'], dependencies: ['dep1'], research: JSON.stringify({ data: 'research' }), planningNotes: JSON.stringify(['note']), architectureDecisions: JSON.stringify({ pattern: 'pattern' }), workerOutput: 'Output', verificationCriteria: JSON.stringify({ security: ['check1'] }) }); const contextManager = new ContextManager(graphManager); // Test worker scope const workerResult = await contextManager.getFilteredTaskContext(taskNode.id, 'worker'); const workerScope = DEFAULT_CONTEXT_SCOPES.worker; for (const field of workerScope.allowedFields) { if (field !== 'errorContext') { // errorContext is optional expect(workerResult.context).toHaveProperty(field); } } // Test QC scope const qcResult = await contextManager.getFilteredTaskContext(taskNode.id, 'qc'); const qcScope = DEFAULT_CONTEXT_SCOPES.qc; expect(qcResult.context).toHaveProperty('requirements'); expect(qcResult.context).toHaveProperty('workerOutput'); expect(qcResult.context).toHaveProperty('verificationCriteria'); // Test PM scope (should have everything) const pmResult = await contextManager.getFilteredTaskContext(taskNode.id, 'pm'); expect(pmResult.context.research).toBeDefined(); expect(pmResult.context.planningNotes).toBeDefined(); expect(pmResult.context.architectureDecisions).toBeDefined(); }); }); });

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/orneryd/Mimir'

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