mcp-server.test.ts•4.08 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';
/**
* MCP Server Discovery and Error Handling tests.
*
* NOTE: Comprehensive tool tests are in mcp-all-tools.test.ts.
* This file focuses on:
* - Tool discovery (listTools)
* - Schema validation
* - Error handling
*/
describe('E2E: MCP Server Discovery & Errors', () => {
let client: Client;
let storagePath: string;
let cleanup: () => Promise<void>;
beforeAll(async () => {
storagePath = path.join(process.cwd(), '.test-mcp-server-' + 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: 'test-client', 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();
});
describe('Tool Discovery', () => {
it('should list all 8 tools', async () => {
const result = await client.listTools();
expect(result.tools).toHaveLength(8);
const toolNames = result.tools.map(t => t.name).sort();
expect(toolNames).toEqual([
'artifact',
'decision',
'link',
'phase',
'plan',
'query',
'requirement',
'solution',
]);
});
it('should have correct schema for plan tool', async () => {
const result = await client.listTools();
const planTool = result.tools.find(t => t.name === 'plan');
expect(planTool).toBeDefined();
expect(planTool!.inputSchema.properties).toHaveProperty('action');
expect(planTool!.inputSchema.required).toContain('action');
});
it('should have correct actions for each tool', async () => {
const result = await client.listTools();
const expectedActions: Record<string, string[]> = {
plan: ['create', 'list', 'get', 'update', 'archive', 'set_active', 'get_active'],
requirement: ['add', 'get', 'update', 'list', 'delete'],
solution: ['propose', 'get', 'update', 'compare', 'select', 'delete'],
decision: ['record', 'get', 'list', 'supersede'],
phase: ['add', 'get_tree', 'update_status', 'move', 'delete', 'get_next_actions'],
artifact: ['add', 'get', 'update', 'list', 'delete'],
link: ['create', 'get', 'delete'],
query: ['search', 'trace', 'validate', 'export', 'health'],
};
for (const tool of result.tools) {
const actionProp = tool.inputSchema.properties?.action as { enum?: string[] };
expect(actionProp?.enum?.sort()).toEqual(expectedActions[tool.name].sort());
}
});
});
describe('Error Handling via MCP', () => {
it('should return error for unknown action', async () => {
await expect(
client.callTool({
name: 'plan',
arguments: {
action: 'unknown_action',
},
})
).rejects.toThrow();
});
it('should return error for non-existent plan', async () => {
await expect(
client.callTool({
name: 'plan',
arguments: {
action: 'get',
planId: 'non-existent-id',
},
})
).rejects.toThrow();
});
it('should return error for unknown tool', async () => {
await expect(
client.callTool({
name: 'unknown_tool',
arguments: {},
})
).rejects.toThrow();
});
});
});