/** * MCP 工具适配器测试 */ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { MCPToolAdapter, createMCPToolAdapter } from '../../../src/mcp/tool-adapter.js'; import type { MCPTool, MCPManager, MCPToolCallResult } from '../../../src/mcp/types.js'; // 创建 mock MCPManager function createMockManager(): MCPManager { return { callTool: vi.fn(), getTools: vi.fn().mockReturnValue([]), getServerStatuses: vi.fn().mockReturnValue([]), initialize: vi.fn(), shutdown: vi.fn(), on: vi.fn(), emit: vi.fn(), } as unknown as MCPManager; } describe('MCPToolAdapter', () => { let mockManager: MCPManager; let adapter: MCPToolAdapter; beforeEach(() => { mockManager = createMockManager(); adapter = new MCPToolAdapter(mockManager); }); describe('adaptToInternalTool', () => { it('应该将 MCP 工具转换为内部格式', () => { const mcpTool: MCPTool = { server: 'filesystem', name: 'filesystem-read_file', originalName: 'read_file', description: 'Read a file from the filesystem', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Path to the file', }, }, required: ['path'], }, }; const internalTool = adapter.adaptToInternalTool(mcpTool); expect(internalTool.name).toBe('filesystem-read_file'); expect(internalTool.description).toBe('Read a file from the filesystem'); expect(internalTool.parameters).toHaveProperty('path'); expect(internalTool.parameters.path.type).toBe('string'); expect(internalTool.parameters.path.required).toBe(true); expect(internalTool.metadata.category).toBe('agent'); expect(internalTool.metadata.keywords).toContain('mcp'); expect(internalTool.metadata.keywords).toContain('filesystem'); }); it('应该正确转换参数类型', () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: { type: 'object', properties: { stringParam: { type: 'string' }, numberParam: { type: 'number' }, integerParam: { type: 'integer' }, booleanParam: { type: 'boolean' }, arrayParam: { type: 'array' }, objectParam: { type: 'object' }, }, }, }; const internalTool = adapter.adaptToInternalTool(mcpTool); expect(internalTool.parameters.stringParam.type).toBe('string'); expect(internalTool.parameters.numberParam.type).toBe('number'); expect(internalTool.parameters.integerParam.type).toBe('number'); expect(internalTool.parameters.booleanParam.type).toBe('boolean'); expect(internalTool.parameters.arrayParam.type).toBe('array'); expect(internalTool.parameters.objectParam.type).toBe('object'); }); it('应该处理联合类型', () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: { type: 'object', properties: { nullable: { type: ['string', 'null'] }, }, }, }; const internalTool = adapter.adaptToInternalTool(mcpTool); expect(internalTool.parameters.nullable.type).toBe('string'); }); it('应该处理空 inputSchema', () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: {}, }; const internalTool = adapter.adaptToInternalTool(mcpTool); expect(internalTool.parameters).toEqual({}); }); it('execute 应该调用 manager.callTool', async () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: { type: 'object', properties: { arg: { type: 'string' }, }, }, }; const mockResult: MCPToolCallResult = { success: true, content: [{ type: 'text', text: 'result' }], isError: false, }; vi.mocked(mockManager.callTool).mockResolvedValue(mockResult); const internalTool = adapter.adaptToInternalTool(mcpTool); const result = await internalTool.execute({ arg: 'value' }); expect(mockManager.callTool).toHaveBeenCalledWith('test-tool', { arg: 'value', }); expect(result.success).toBe(true); expect(result.output).toBe('result'); }); it('execute 应该处理错误结果', async () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: {}, }; const mockResult: MCPToolCallResult = { success: false, content: [{ type: 'text', text: 'error message' }], isError: true, }; vi.mocked(mockManager.callTool).mockResolvedValue(mockResult); const internalTool = adapter.adaptToInternalTool(mcpTool); const result = await internalTool.execute({}); expect(result.success).toBe(false); expect(result.error).toBe('error message'); }); }); describe('adaptTools', () => { it('应该批量转换工具', () => { const mcpTools: MCPTool[] = [ { server: 'server1', name: 'server1-tool1', originalName: 'tool1', description: 'Tool 1', inputSchema: {}, }, { server: 'server2', name: 'server2-tool2', originalName: 'tool2', description: 'Tool 2', inputSchema: {}, }, ]; const internalTools = adapter.adaptTools(mcpTools); expect(internalTools).toHaveLength(2); expect(internalTools[0].name).toBe('server1-tool1'); expect(internalTools[1].name).toBe('server2-tool2'); }); }); describe('createMCPToolAdapter', () => { it('应该创建适配器实例', () => { const adapter = createMCPToolAdapter(mockManager); expect(adapter).toBeInstanceOf(MCPToolAdapter); }); }); describe('内容转换', () => { it('应该处理图片内容', async () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: {}, }; const mockResult: MCPToolCallResult = { success: true, content: [{ type: 'image', data: 'base64data', mimeType: 'image/png' }], isError: false, }; vi.mocked(mockManager.callTool).mockResolvedValue(mockResult); const internalTool = adapter.adaptToInternalTool(mcpTool); const result = await internalTool.execute({}); expect(result.output).toBe('[Image: image/png]'); }); it('应该处理资源内容', async () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: {}, }; const mockResult: MCPToolCallResult = { success: true, content: [ { type: 'resource', resource: { uri: 'file:///test.txt', text: 'file content', }, }, ], isError: false, }; vi.mocked(mockManager.callTool).mockResolvedValue(mockResult); const internalTool = adapter.adaptToInternalTool(mcpTool); const result = await internalTool.execute({}); expect(result.output).toBe('file content'); }); it('应该处理混合内容', async () => { const mcpTool: MCPTool = { server: 'test', name: 'test-tool', originalName: 'tool', description: 'Test tool', inputSchema: {}, }; const mockResult: MCPToolCallResult = { success: true, content: [ { type: 'text', text: 'line 1' }, { type: 'text', text: 'line 2' }, ], isError: false, }; vi.mocked(mockManager.callTool).mockResolvedValue(mockResult); const internalTool = adapter.adaptToInternalTool(mcpTool); const result = await internalTool.execute({}); expect(result.output).toBe('line 1\nline 2'); }); }); });