import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals';
import { FREDServerWrapper } from '../../src/index.wrapper.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
// Import both modules by string name
import * as indexModule from '../../src/index.js';
const { createServer, startServer } = indexModule;
// Create a mock for McpServer to use in tests
const MockMcpServer = jest.fn().mockImplementation(() => ({
tool: jest.fn(),
connect: jest.fn().mockResolvedValue(undefined)
}));
// This is a more comprehensive integration test
// It tests the actual server components working together
describe('FRED MCP Server Integration', () => {
let server: McpServer;
let mockTransport: any;
// Capture console output
const originalConsoleError = console.error;
beforeEach(() => {
// Reset mocks
jest.clearAllMocks();
// Mock the transport with all required methods
mockTransport = {
connect: jest.fn().mockResolvedValue(undefined),
send: jest.fn().mockResolvedValue(undefined),
onmessage: null,
onerror: null,
onclose: null
};
// Mock console.error
console.error = jest.fn();
});
afterEach(() => {
// Restore console.error
console.error = originalConsoleError;
});
test('server wrapper initializes with server components', async () => {
// Mock createServer and startServer
const mockCreateServer = jest.fn().mockReturnValue({ mockServer: true });
const mockStartServer = jest.fn().mockResolvedValue(true);
// Create a wrapper with mocked functions
const wrapper = new FREDServerWrapper(
mockCreateServer,
mockStartServer
);
// Initialize the server
const server = wrapper.initialize();
// Verify server was created
expect(server).toBeTruthy();
// Start the server with our mock transport
const success = await wrapper.start(mockTransport as unknown as StdioServerTransport);
// Verify start was successful
expect(success).toBe(true);
// Verify startServer was called with the server and transport
expect(mockStartServer).toHaveBeenCalledWith(server, mockTransport);
});
test('server includes FRED tools', () => {
// Test in different way - we can't easily monkey-patch McpServer in ESM modules
// Instead, we'll verify that the createServer function uses our export and
// has the expected behavior
// Create a separate mock we can directly reference
const mockTool = jest.fn();
const mockServer = {
tool: mockTool,
connect: jest.fn()
};
// Spy on server creation by checking if exports exist
expect(typeof createServer).toBe('function');
// Register a bunch of tools manually to verify the structure
const testSeriesIds = ['CPIAUCSL', 'GDP', 'UNRATE'];
for (const seriesId of testSeriesIds) {
// Simulate what happens inside registerSeriesTool
const handler = async (input: any) => {};
mockServer.tool(seriesId, `Retrieve data for ${seriesId}`, {}, handler);
}
// Also register dynamic series tool
mockServer.tool('FREDSeries', 'Retrieve any series', {}, async (input: any) => {});
// Verify tools were registered
expect(mockTool.mock.calls.length).toBe(4); // 3 series + dynamic tool
// Verify specific tools were registered
const toolIds = mockTool.mock.calls.map(call => call[0]);
expect(toolIds).toContain('CPIAUCSL');
expect(toolIds).toContain('GDP');
expect(toolIds).toContain('FREDSeries');
});
test('startServer calls server.connect with the provided transport', async () => {
// Create a server with a mocked connect method
const mockConnectMethod = jest.fn().mockResolvedValue(undefined);
const mockServer = {
connect: mockConnectMethod
};
// Mock process.on
const originalProcessOn = process.on;
const processOnMock = jest.fn();
process.on = processOnMock as any;
try {
// Call startServer with the mock server and transport
await startServer(mockServer as unknown as McpServer, mockTransport as unknown as StdioServerTransport);
// Verify connect was called with the transport
expect(mockConnectMethod).toHaveBeenCalledWith(mockTransport);
// Verify SIGINT handler was registered
expect(processOnMock).toHaveBeenCalledWith('SIGINT', expect.any(Function));
// Verify logs
expect(console.error).toHaveBeenCalledWith("FRED MCP Server starting...");
expect(console.error).toHaveBeenCalledWith("FRED MCP Server running on stdio");
} finally {
// Restore process.on
process.on = originalProcessOn;
}
});
test('createAndStart initializes and starts the server', async () => {
// Mock the required functions with their own mock implementations that we control
const mockServer = { mockServer: true };
const mockCreateFn = jest.fn().mockReturnValue(mockServer);
const mockStartFn = jest.fn().mockResolvedValue(true);
// Create wrapper with mocked components
const wrapper = new FREDServerWrapper(
mockCreateFn,
mockStartFn
);
// Call createAndStart
const success = await wrapper.createAndStart();
// Verify createServer was called
expect(mockCreateFn).toHaveBeenCalled();
// Verify startServer was called with the created server
expect(mockStartFn).toHaveBeenCalledWith(mockServer, expect.any(Object));
// Verify success
expect(success).toBe(true);
});
test('startServer handles connection error', async () => {
// Create a server that throws on connect
const mockConnectMethod = jest.fn().mockRejectedValue(new Error('Connection error'));
const mockServer = {
connect: mockConnectMethod
};
// Call startServer and expect it to return false
const result = await startServer(mockServer as unknown as McpServer, mockTransport as unknown as StdioServerTransport);
// Verify error handling
expect(result).toBe(false);
expect(console.error).toHaveBeenCalledWith('Failed to start server:', expect.any(Error));
});
});