Skip to main content
Glama
mcp-all-tools.test.ts35.1 kB
import { describe, it, expect, beforeAll, afterAll } from '@jest/globals'; import { Client } from '@modelcontextprotocol/sdk/client/index.js'; import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; import { createMcpServer, createServices } from '../../src/server/index.js'; import * as fs from 'fs/promises'; import * as path from 'path'; /** * Comprehensive test of ALL MCP tools and ALL their actions. * This validates that tool-definitions.ts matches the actual service implementations. */ // Helper to parse MCP tool result function parseResult<T>(result: unknown): T { const r = result as { content: Array<{ type: string; text: string }> }; return JSON.parse(r.content[0].text) as T; } describe('E2E: All MCP Tools Validation', () => { let client: Client; let storagePath: string; let cleanup: () => Promise<void>; // IDs created during tests for cross-referencing let planId: string; let requirementId: string; let solutionId: string; let solutionId2: string; let decisionId: string; let phaseId: string; let childPhaseId: string; let linkId: string; beforeAll(async () => { storagePath = path.join(process.cwd(), '.test-all-tools-' + Date.now()); await fs.mkdir(storagePath, { recursive: true }); const services = await createServices(storagePath); const { server } = createMcpServer(services); const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair(); client = new Client( { name: 'all-tools-test', version: '1.0.0' }, { capabilities: {} } ); await server.connect(serverTransport); await client.connect(clientTransport); cleanup = async () => { await client.close(); await server.close(); await fs.rm(storagePath, { recursive: true, force: true }); }; }); afterAll(async () => { await cleanup(); }); // ============================================================ // TOOL: plan (7 actions) // ============================================================ describe('Tool: plan', () => { it('action: create', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'create', name: 'All Tools Test Plan', description: 'Testing all MCP tools', }, }); const parsed = parseResult<{ planId: string; manifest: { name: string } }>(result); expect(parsed.planId).toBeDefined(); expect(parsed.manifest.name).toBe('All Tools Test Plan'); planId = parsed.planId; }); it('action: list', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'list', // Don't pass status to get all plans (status is for filtering) limit: 10, offset: 0, }, }); const parsed = parseResult<{ plans: Array<{ id: string }> }>(result); expect(parsed.plans.length).toBeGreaterThan(0); }); it('action: list with status filter', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'list', status: 'active', limit: 10, }, }); const parsed = parseResult<{ plans: Array<{ id: string; status: string }> }>(result); expect(parsed.plans).toBeDefined(); // All returned plans should be active parsed.plans.forEach((plan) => { expect(plan.status).toBe('active'); }); }); it('action: get', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'get', planId, includeEntities: true, includeLinks: true, }, }); const parsed = parseResult<{ plan: { manifest: { id: string } } }>(result); expect(parsed.plan.manifest.id).toBe(planId); }); it('action: update', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'update', planId, updates: { name: 'Updated Plan Name', description: 'Updated description', status: 'active', }, }, }); const parsed = parseResult<{ plan: { name: string } }>(result); expect(parsed.plan.name).toBe('Updated Plan Name'); }); it('action: set_active', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'set_active', planId, workspacePath: '/test-workspace', }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); it('action: get_active', async () => { const result = await client.callTool({ name: 'plan', arguments: { action: 'get_active', workspacePath: '/test-workspace', }, }); const parsed = parseResult<{ activePlan: { planId: string } }>(result); expect(parsed.activePlan.planId).toBe(planId); }); it('action: archive (at end)', async () => { // Create a separate plan to archive const createResult = await client.callTool({ name: 'plan', arguments: { action: 'create', name: 'Plan to Archive', description: 'Will be archived', }, }); const created = parseResult<{ planId: string }>(createResult); const result = await client.callTool({ name: 'plan', arguments: { action: 'archive', planId: created.planId, reason: 'Testing archive', }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); }); // ============================================================ // TOOL: requirement (5 actions) // ============================================================ describe('Tool: requirement', () => { it('action: add', async () => { const result = await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Test Requirement', description: 'A test requirement', rationale: 'For testing purposes', source: { type: 'user-request', context: 'Testing', }, acceptanceCriteria: ['Criteria 1', 'Criteria 2'], priority: 'high', category: 'functional', }, }, }); const parsed = parseResult<{ requirementId: string }>(result); expect(parsed.requirementId).toBeDefined(); requirementId = parsed.requirementId; }); it('action: add with all source types and categories', async () => { // Test 'discovered' source type with 'technical' category const result1 = await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Discovered Requirement', description: 'Found during analysis', source: { type: 'discovered' }, acceptanceCriteria: ['AC1'], priority: 'medium', category: 'technical', }, }, }); expect(parseResult<{ requirementId: string }>(result1).requirementId).toBeDefined(); // Test 'derived' source type with 'business' category const result2 = await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Derived Requirement', description: 'Derived from another', source: { type: 'derived', parentId: requirementId }, acceptanceCriteria: ['AC1'], priority: 'low', category: 'business', }, }, }); expect(parseResult<{ requirementId: string }>(result2).requirementId).toBeDefined(); // Test 'non-functional' category (performance, security, etc.) const result3 = await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'Performance Requirement', description: 'System must handle 1000 requests per second', source: { type: 'user-request' }, acceptanceCriteria: ['Load tests pass at 1000 RPS'], priority: 'high', category: 'non-functional', }, }, }); expect(parseResult<{ requirementId: string }>(result3).requirementId).toBeDefined(); }); it('action: get', async () => { const result = await client.callTool({ name: 'requirement', arguments: { action: 'get', planId, requirementId, includeTraceability: true, }, }); const parsed = parseResult<{ requirement: { id: string } }>(result); expect(parsed.requirement.id).toBe(requirementId); }); it('action: list', async () => { const result = await client.callTool({ name: 'requirement', arguments: { action: 'list', planId, filters: { priority: 'high', category: 'functional', }, }, }); const parsed = parseResult<{ requirements: unknown[] }>(result); expect(parsed.requirements.length).toBeGreaterThan(0); }); it('action: list with status filter', async () => { // Test filtering by status (active, completed, etc.) const result = await client.callTool({ name: 'requirement', arguments: { action: 'list', planId, filters: { status: 'active', }, }, }); const parsed = parseResult<{ requirements: unknown[] }>(result); expect(parsed.requirements).toBeDefined(); }); it('action: update', async () => { const result = await client.callTool({ name: 'requirement', arguments: { action: 'update', planId, requirementId, updates: { title: 'Updated Requirement', priority: 'critical', }, }, }); const parsed = parseResult<{ requirement: { title: string; priority: string } }>(result); expect(parsed.requirement.title).toBe('Updated Requirement'); expect(parsed.requirement.priority).toBe('critical'); }); it('action: delete', async () => { // Create a requirement to delete const createResult = await client.callTool({ name: 'requirement', arguments: { action: 'add', planId, requirement: { title: 'To Delete', description: 'Will be deleted', source: { type: 'user-request' }, acceptanceCriteria: ['AC1'], priority: 'low', category: 'functional', }, }, }); const created = parseResult<{ requirementId: string }>(createResult); const result = await client.callTool({ name: 'requirement', arguments: { action: 'delete', planId, requirementId: created.requirementId, force: true, }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); }); // ============================================================ // TOOL: solution (6 actions) // ============================================================ describe('Tool: solution', () => { it('action: propose', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'propose', planId, solution: { title: 'Test Solution', description: 'A test solution', approach: 'Use testing approach', implementationNotes: 'Implementation details here', addressing: [requirementId], tradeoffs: [ { aspect: 'Performance', pros: ['Fast', 'Efficient'], cons: ['Complex'], }, ], evaluation: { effortEstimate: { value: 5, unit: 'days', confidence: 'medium', }, technicalFeasibility: 'high', riskAssessment: 'Low risk', }, }, }, }); const parsed = parseResult<{ solutionId: string }>(result); expect(parsed.solutionId).toBeDefined(); solutionId = parsed.solutionId; }); it('action: propose second solution for compare', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'propose', planId, solution: { title: 'Alternative Solution', description: 'Another approach', approach: 'Different approach', addressing: [requirementId], tradeoffs: [], evaluation: { effortEstimate: { value: 3, unit: 'days', confidence: 'high' }, technicalFeasibility: 'medium', riskAssessment: 'Medium risk', }, }, }, }); const parsed = parseResult<{ solutionId: string }>(result); solutionId2 = parsed.solutionId; }); it('action: propose with all technicalFeasibility values', async () => { // Test 'low' feasibility (high and medium already tested above) const result = await client.callTool({ name: 'solution', arguments: { action: 'propose', planId, solution: { title: 'Risky Experimental Solution', description: 'Unproven technology approach', approach: 'Use cutting-edge experimental framework', addressing: [requirementId], tradeoffs: [ { aspect: 'Innovation', pros: ['Cutting edge'], cons: ['Unproven', 'High learning curve'], }, ], evaluation: { effortEstimate: { value: 20, unit: 'days', confidence: 'low' }, technicalFeasibility: 'low', riskAssessment: 'High risk - technology not mature', }, }, }, }); const parsed = parseResult<{ solutionId: string }>(result); expect(parsed.solutionId).toBeDefined(); }); it('action: get', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'get', planId, solutionId, }, }); const parsed = parseResult<{ solution: { id: string } }>(result); expect(parsed.solution.id).toBe(solutionId); }); it('action: update', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'update', planId, solutionId, updates: { title: 'Updated Solution', }, }, }); const parsed = parseResult<{ solution: { title: string } }>(result); expect(parsed.solution.title).toBe('Updated Solution'); }); it('action: compare', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'compare', planId, solutionIds: [solutionId, solutionId2], aspects: ['effort', 'risk'], }, }); const parsed = parseResult<{ comparison: { solutions: unknown[] } }>(result); expect(parsed.comparison.solutions).toHaveLength(2); }); it('action: select', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'select', planId, solutionId, reason: 'Best fit for requirements', createDecisionRecord: true, }, }); const parsed = parseResult<{ solution: { status: string } }>(result); expect(parsed.solution.status).toBe('selected'); }); it('action: delete', async () => { const result = await client.callTool({ name: 'solution', arguments: { action: 'delete', planId, solutionId: solutionId2, }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); }); // ============================================================ // TOOL: decision (4 actions) // ============================================================ describe('Tool: decision', () => { it('action: record', async () => { const result = await client.callTool({ name: 'decision', arguments: { action: 'record', planId, decision: { title: 'Test Decision', question: 'What approach should we use?', context: 'We need to decide on testing approach', decision: 'Use unit testing with Jest', consequences: 'Need to maintain test suite', alternativesConsidered: [ { option: 'Integration tests only', reasoning: 'Faster but less coverage', }, ], }, }, }); const parsed = parseResult<{ decisionId: string }>(result); expect(parsed.decisionId).toBeDefined(); decisionId = parsed.decisionId; }); it('action: get', async () => { const result = await client.callTool({ name: 'decision', arguments: { action: 'get', planId, decisionId, }, }); const parsed = parseResult<{ decision: { id: string } }>(result); expect(parsed.decision.id).toBe(decisionId); }); it('action: list with status active', async () => { const result = await client.callTool({ name: 'decision', arguments: { action: 'list', planId, status: 'active', }, }); const parsed = parseResult<{ decisions: unknown[] }>(result); expect(parsed.decisions.length).toBeGreaterThan(0); }); it('action: list with all status values', async () => { // First supersede a decision so we have different statuses await client.callTool({ name: 'decision', arguments: { action: 'supersede', planId, decisionId, newDecision: { decision: 'Temporary change' }, reason: 'Testing statuses', }, }); // Test 'superseded' status filter const supersededResult = await client.callTool({ name: 'decision', arguments: { action: 'list', planId, status: 'superseded', }, }); const supersededParsed = parseResult<{ decisions: unknown[] }>(supersededResult); expect(supersededParsed.decisions).toBeDefined(); // Test 'reversed' status filter (may be empty but should work) const reversedResult = await client.callTool({ name: 'decision', arguments: { action: 'list', planId, status: 'reversed', }, }); const reversedParsed = parseResult<{ decisions: unknown[] }>(reversedResult); expect(reversedParsed.decisions).toBeDefined(); }); it('action: supersede', async () => { const result = await client.callTool({ name: 'decision', arguments: { action: 'supersede', planId, decisionId, newDecision: { decision: 'Use integration tests with Playwright', consequences: 'Better E2E coverage', }, reason: 'Requirements changed', }, }); const parsed = parseResult<{ newDecision: { id: string }; supersededDecision: { status: string }; }>(result); expect(parsed.newDecision.id).toBeDefined(); expect(parsed.supersededDecision.status).toBe('superseded'); }); }); // ============================================================ // TOOL: phase (6 actions) // ============================================================ describe('Tool: phase', () => { it('action: add', async () => { const result = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Phase 1: Setup', description: 'Initial setup phase', objectives: ['Setup environment', 'Configure tools'], deliverables: ['Configured project'], successCriteria: ['All tools working'], estimatedEffort: { value: 2, unit: 'days', confidence: 'high', }, }, }, }); const parsed = parseResult<{ phaseId: string }>(result); expect(parsed.phaseId).toBeDefined(); phaseId = parsed.phaseId; }); it('action: add child phase', async () => { const result = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Task 1.1: Install Dependencies', description: 'Install all required packages', parentId: phaseId, objectives: ['npm install'], deliverables: ['node_modules'], successCriteria: ['No errors'], }, }, }); const parsed = parseResult<{ phaseId: string }>(result); childPhaseId = parsed.phaseId; }); it('action: get_tree', async () => { const result = await client.callTool({ name: 'phase', arguments: { action: 'get_tree', planId, includeCompleted: true, maxDepth: 10, }, }); const parsed = parseResult<{ tree: unknown[] }>(result); expect(parsed.tree.length).toBeGreaterThan(0); }); it('action: update_status', async () => { const result = await client.callTool({ name: 'phase', arguments: { action: 'update_status', planId, phaseId: childPhaseId, status: 'in_progress', progress: 50, notes: 'Half done', actualEffort: 4, }, }); const parsed = parseResult<{ phase: { status: string; progress: number } }>(result); expect(parsed.phase.status).toBe('in_progress'); expect(parsed.phase.progress).toBe(50); }); it('action: update_status with all status values', async () => { // Create a test phase for status transitions const createResult = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Status Test Phase', description: 'For testing all statuses', objectives: ['Test'], deliverables: ['None'], successCriteria: ['Pass'], }, }, }); const testPhaseId = parseResult<{ phaseId: string }>(createResult).phaseId; // Test all phase statuses (blocked requires notes) const statusConfigs: Array<{ status: string; notes?: string }> = [ { status: 'planned' }, { status: 'in_progress' }, { status: 'completed' }, { status: 'blocked', notes: 'Blocked by dependency issue' }, { status: 'skipped' }, ]; for (const { status, notes } of statusConfigs) { const result = await client.callTool({ name: 'phase', arguments: { action: 'update_status', planId, phaseId: testPhaseId, status, ...(notes && { notes }), }, }); const parsed = parseResult<{ phase: { status: string } }>(result); expect(parsed.phase.status).toBe(status); } }); it('action: update_status with progress boundaries (0 and 100)', async () => { // Create a test phase for progress testing const createResult = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Progress Test Phase', description: 'For testing progress boundaries', objectives: ['Test progress'], deliverables: ['Progress verified'], successCriteria: ['0% and 100% work'], }, }, }); const progressPhaseId = parseResult<{ phaseId: string }>(createResult).phaseId; // Test progress: 0% (start) const result0 = await client.callTool({ name: 'phase', arguments: { action: 'update_status', planId, phaseId: progressPhaseId, status: 'in_progress', progress: 0, }, }); const parsed0 = parseResult<{ phase: { progress: number } }>(result0); expect(parsed0.phase.progress).toBe(0); // Test progress: 100% (complete) const result100 = await client.callTool({ name: 'phase', arguments: { action: 'update_status', planId, phaseId: progressPhaseId, status: 'completed', progress: 100, }, }); const parsed100 = parseResult<{ phase: { progress: number } }>(result100); expect(parsed100.phase.progress).toBe(100); }); it('action: move', async () => { // Create another parent phase const createResult = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Phase 2: Development', description: 'Development phase', objectives: ['Develop'], deliverables: ['Code'], successCriteria: ['Tests pass'], }, }, }); const newParent = parseResult<{ phaseId: string }>(createResult); const result = await client.callTool({ name: 'phase', arguments: { action: 'move', planId, phaseId: childPhaseId, newParentId: newParent.phaseId, newOrder: 0, }, }); const parsed = parseResult<{ phase: { parentId: string } }>(result); expect(parsed.phase.parentId).toBe(newParent.phaseId); }); it('action: get_next_actions', async () => { const result = await client.callTool({ name: 'phase', arguments: { action: 'get_next_actions', planId, limit: 5, }, }); const parsed = parseResult<{ actions: unknown[] }>(result); expect(parsed.actions).toBeDefined(); }); it('action: delete', async () => { // Create a phase to delete const createResult = await client.callTool({ name: 'phase', arguments: { action: 'add', planId, phase: { title: 'Phase to Delete', description: 'Will be deleted', objectives: ['None'], deliverables: ['None'], successCriteria: ['None'], }, }, }); const created = parseResult<{ phaseId: string }>(createResult); const result = await client.callTool({ name: 'phase', arguments: { action: 'delete', planId, phaseId: created.phaseId, deleteChildren: true, }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); }); // ============================================================ // TOOL: link (3 actions) // ============================================================ describe('Tool: link', () => { it('action: create with all relation types', async () => { // Test 'implements' relation const result = await client.callTool({ name: 'link', arguments: { action: 'create', planId, sourceId: solutionId, targetId: requirementId, relationType: 'implements', metadata: { coverage: 'full' }, }, }); const parsed = parseResult<{ linkId: string }>(result); expect(parsed.linkId).toBeDefined(); linkId = parsed.linkId; // Test other relation types const relationTypes = [ 'addresses', 'depends_on', 'blocks', 'alternative_to', 'supersedes', 'references', 'derived_from', ]; for (const relationType of relationTypes) { const r = await client.callTool({ name: 'link', arguments: { action: 'create', planId, sourceId: `test-source-${relationType}`, targetId: `test-target-${relationType}`, relationType, }, }); expect(parseResult<{ linkId: string }>(r).linkId).toBeDefined(); } }); it('action: get with direction both', async () => { const result = await client.callTool({ name: 'link', arguments: { action: 'get', planId, entityId: solutionId, direction: 'both', }, }); const parsed = parseResult<{ links: unknown[] }>(result); expect(parsed.links.length).toBeGreaterThan(0); }); it('action: get with all direction values', async () => { // Test 'outgoing' direction - links where entity is source const outgoingResult = await client.callTool({ name: 'link', arguments: { action: 'get', planId, entityId: solutionId, direction: 'outgoing', }, }); const outgoingParsed = parseResult<{ links: unknown[] }>(outgoingResult); expect(outgoingParsed.links).toBeDefined(); // Test 'incoming' direction - links where entity is target const incomingResult = await client.callTool({ name: 'link', arguments: { action: 'get', planId, entityId: requirementId, direction: 'incoming', }, }); const incomingParsed = parseResult<{ links: unknown[] }>(incomingResult); expect(incomingParsed.links).toBeDefined(); }); it('action: get with relationType filter', async () => { // Test filtering links by relation type const result = await client.callTool({ name: 'link', arguments: { action: 'get', planId, entityId: solutionId, direction: 'both', relationType: 'implements', }, }); const parsed = parseResult<{ links: Array<{ relationType: string }> }>(result); expect(parsed.links).toBeDefined(); // All returned links should be of type 'implements' parsed.links.forEach((link) => { expect(link.relationType).toBe('implements'); }); }); it('action: delete', async () => { const result = await client.callTool({ name: 'link', arguments: { action: 'delete', planId, linkId, }, }); const parsed = parseResult<{ success: boolean }>(result); expect(parsed.success).toBe(true); }); }); // ============================================================ // TOOL: query (5 actions) // ============================================================ describe('Tool: query', () => { it('action: search', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'search', planId, query: 'Test', entityTypes: ['requirement', 'solution'], limit: 10, offset: 0, }, }); const parsed = parseResult<{ results: unknown[] }>(result); expect(parsed.results).toBeDefined(); }); it('action: trace', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'trace', planId, requirementId, }, }); const parsed = parseResult<{ requirement: { id: string } }>(result); expect(parsed.requirement.id).toBe(requirementId); }); it('action: validate', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'validate', planId, checks: ['orphaned-requirements', 'missing-solutions'], }, }); const parsed = parseResult<{ checksPerformed: unknown[]; summary: unknown }>(result); expect(parsed.checksPerformed).toBeDefined(); expect(parsed.summary).toBeDefined(); }); it('action: validate with empty checks (runs all)', async () => { // When checks array is empty or not provided, all checks should run const result = await client.callTool({ name: 'query', arguments: { action: 'validate', planId, checks: [], }, }); const parsed = parseResult<{ checksPerformed: unknown[]; summary: unknown }>(result); expect(parsed.checksPerformed).toBeDefined(); expect(parsed.summary).toBeDefined(); }); it('action: export markdown', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'export', planId, format: 'markdown', sections: ['requirements', 'solutions', 'decisions'], includeVersionHistory: false, }, }); const parsed = parseResult<{ format: string; content: string }>(result); expect(parsed.format).toBe('markdown'); expect(parsed.content).toContain('Updated Plan Name'); }); it('action: export json', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'export', planId, format: 'json', }, }); const parsed = parseResult<{ format: string; content: unknown }>(result); expect(parsed.format).toBe('json'); expect(parsed.content).toBeDefined(); }); it('action: health', async () => { const result = await client.callTool({ name: 'query', arguments: { action: 'health', }, }); const parsed = parseResult<{ status: string; version: string; storagePath: string }>(result); expect(parsed.status).toBe('healthy'); expect(parsed.version).toBe('1.0.0'); expect(parsed.storagePath).toBe(storagePath); }); }); });

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/cppmyjob/cpp-mcp-planner'

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