@trishchuk/ai-think-gate-mcp
Version:
Model Context Protocol (MCP) server that provides AI-powered thinking and code architecture tools
194 lines (193 loc) • 8.5 kB
JavaScript
import * as fs from 'fs';
import { mcpServer } from "../../application/server/mcp-server.js";
import { ToolNames } from "../../domain/constants.js";
// Create log file for debugging
fs.writeFileSync('mcp-server-test.log', `=== MCP Server Test Started: ${new Date().toISOString()} ===\n\n`);
// Mock for process.stdin and process.stdout
const mockStdin = {
on: jest.fn((event, handler) => {
// Store handlers to call them directly
if (event === 'data') {
mockStdin._dataHandler = handler;
}
}),
off: jest.fn(),
listenerCount: jest.fn().mockReturnValue(0),
pause: jest.fn(),
emit: jest.fn(),
setEncoding: jest.fn(),
resume: jest.fn(),
isTTY: false,
_dataHandler: null // Store the data handler for direct calls
};
// Store all written responses
const serverResponses = [];
const mockStdout = {
write: jest.fn((data) => {
if (typeof data === 'string') {
// Log responses for debugging
fs.appendFileSync('mcp-server-test.log', `SERVER RESPONSE [${new Date().toISOString()}]: ${data}\n`);
// Store response for later assertions
serverResponses.push(data);
}
return true;
}),
once: jest.fn()
};
// Function for emulating client requests
const sendClientRequest = async (method, params = {}, id = 1) => {
const request = {
jsonrpc: "2.0",
id,
method,
params
};
const requestStr = JSON.stringify(request);
fs.appendFileSync('mcp-server-test.log', `CLIENT REQUEST [${new Date().toISOString()}]: ${requestStr}\n`);
// Use the stored handler directly instead of emit
if (mockStdin._dataHandler) {
mockStdin._dataHandler(Buffer.from(requestStr + '\n'));
}
else {
fs.appendFileSync('mcp-server-test.log', `ERROR: No data handler registered\n`);
}
// Small delay to allow processing
await new Promise(resolve => setTimeout(resolve, 100));
return request;
};
// Override console.log to avoid test output clutter
const originalConsoleLog = console.log;
console.log = jest.fn();
describe('MCP Server Tests', () => {
beforeAll(async () => {
// Replace stdin and stdout with mocks
Object.defineProperty(process, 'stdin', { value: mockStdin });
Object.defineProperty(process, 'stdout', { value: mockStdout });
// Start server once for all tests
await mcpServer.start();
// Directly inject server handler into transport if needed
const transport = mcpServer.server?.transport;
if (transport) {
fs.appendFileSync('mcp-server-test.log', `Transport found, configuring direct access\n`);
}
// Give the server time to initialize
await new Promise(resolve => setTimeout(resolve, 500));
});
afterAll(async () => {
// Stop the server
await mcpServer.stop();
// Restore original stdin/stdout
Object.defineProperty(process, 'stdin', { value: process.stdin });
Object.defineProperty(process, 'stdout', { value: process.stdout });
// Restore console.log
console.log = originalConsoleLog;
fs.appendFileSync('mcp-server-test.log', `\n=== MCP Server Test Ended: ${new Date().toISOString()} ===\n`);
});
beforeEach(() => {
// Clear mocks and stored responses before each test
jest.clearAllMocks();
serverResponses.length = 0;
});
test('Server should handle tools/list request', async () => {
// Send request and wait for response
await sendClientRequest('tools/list');
// Create a spy on the server's internal handler for direct invocation if needed
const serverTransport = mcpServer.server?.transport;
fs.appendFileSync('mcp-server-test.log', `Server transport: ${serverTransport ? 'exists' : 'missing'}\n`);
// Check if we got a response
expect(serverResponses.length).toBeGreaterThan(0);
if (serverResponses.length === 0) {
// If we still have no responses, manually parse the log file
fs.appendFileSync('mcp-server-test.log', `TEST DEBUG: No responses received. Server state inspection:\n` +
`mcpServer keys: ${Object.keys(mcpServer)}\n` +
`stdin handlers count: ${mockStdin.on.mock.calls.length}\n`);
// Fail the test but provide useful debug info
fail("No server responses received - see mcp-server-test.log for details");
return;
}
// Parse the response
const response = JSON.parse(serverResponses[0]);
// Verify the response structure
expect(response).toHaveProperty('jsonrpc', '2.0');
expect(response).toHaveProperty('id', 1);
expect(response).toHaveProperty('result.tools');
expect(Array.isArray(response.result.tools)).toBe(true);
// Verify tools
const toolNames = response.result.tools.map((tool) => tool.name);
expect(toolNames).toContain(ToolNames.think);
expect(toolNames).toContain(ToolNames.architect);
expect(toolNames).toContain(ToolNames.llm_gateway);
expect(toolNames).toContain(ToolNames.sequential_thinking);
});
// Additional tests condensed to a single test for brevity
test('Server should handle various requests', async () => {
// Test resources/list (unsupported)
await sendClientRequest('resources/list', {}, 1);
// Test sequential requests
await sendClientRequest('tools/list', {}, 2);
await sendClientRequest('tools/call', {
name: ToolNames.think,
arguments: {
thought: "Testing the think tool"
}
}, 3);
// Verify we got responses
expect(serverResponses.length).toBeGreaterThan(0);
// Parse and check responses
const parsedResponses = serverResponses.map(res => {
try {
return JSON.parse(res);
}
catch (e) {
const error = e;
fs.appendFileSync('mcp-server-test.log', `ERROR parsing response: ${error.message}, response: ${res}\n`);
return null;
}
}).filter(Boolean);
// Check that we have some valid responses
expect(parsedResponses.length).toBeGreaterThan(0);
// Log what we received for debugging
fs.appendFileSync('mcp-server-test.log', `Received ${parsedResponses.length} valid responses\n`);
parsedResponses.forEach((resp, i) => {
fs.appendFileSync('mcp-server-test.log', `Response #${i}: id=${resp.id}, type=${resp.error ? 'error' : 'result'}\n`);
});
// Find responses by ID
const resourcesResponse = parsedResponses.find(r => r.id === 1);
const toolsResponse = parsedResponses.find(r => r.id === 2);
const thinkResponse = parsedResponses.find(r => r.id === 3);
// If we found the responses, verify their structure
if (resourcesResponse) {
expect(resourcesResponse).toHaveProperty('error.code', -32601);
}
if (toolsResponse) {
expect(toolsResponse).toHaveProperty('result.tools');
}
if (thinkResponse) {
expect(thinkResponse).toHaveProperty('result.content');
}
});
// Test to check JSON formatting issue
test('Server should send properly formatted JSON', async () => {
// Start the server
await mcpServer.start();
// Send tools/list request
sendClientRequest('tools/list');
// Wait for asynchronous processing
await new Promise(resolve => setTimeout(resolve, 100));
// Get the last write call for analysis
const lastWriteCall = mockStdout.write.mock.calls[mockStdout.write.mock.calls.length - 1];
// Check that the sent JSON is correct
let response;
expect(() => {
response = JSON.parse(lastWriteCall[0]);
}).not.toThrow();
// Check that the sent string does not contain extra characters after JSON
const jsonString = lastWriteCall[0];
// Parse JSON and serialize again for comparison
const parsedAndStringified = JSON.stringify(JSON.parse(jsonString));
// Check that the strings are identical after trimming spaces
expect(jsonString.trim()).toBe(parsedAndStringified);
// Stop the server
await mcpServer.stop();
});
});