4beaf088d0
实现 MCP 协议集成,允许通过外部服务器扩展工具能力:
核心模块:
- types.ts: MCP 类型定义 (LocalMCPServer, RemoteMCPServer, MCPTool 等)
- config.ts: 配置加载、验证,支持 {env:VAR} 环境变量语法
- transports/stdio.ts: stdio 传输层实现 (JSON-RPC 2.0)
- client.ts: MCP 客户端,处理协议握手和工具调用
- manager.ts: 多服务器生命周期管理
- tool-adapter.ts: 将 MCP 工具转换为内部格式
CLI 命令:
- ai-assist mcp list: 列出服务器状态
- ai-assist mcp tools: 列出可用工具
- ai-assist mcp test <server>: 测试连接
配置支持:
- 用户级 (~/.ai-assist/config.json) 和项目级配置
- JSON/YAML 格式
- 通配符模式工具启用/禁用
158 lines
4.6 KiB
TypeScript
158 lines
4.6 KiB
TypeScript
/**
|
|
* MCP Manager 测试
|
|
* 测试不需要实际连接的功能
|
|
*/
|
|
|
|
import { describe, it, expect, vi, afterEach } from 'vitest';
|
|
import { MCPManager, getMCPManager, resetMCPManager } from '../../../src/mcp/manager.js';
|
|
import type { MCPConfig } from '../../../src/mcp/types.js';
|
|
|
|
describe('MCPManager', () => {
|
|
afterEach(async () => {
|
|
resetMCPManager();
|
|
});
|
|
|
|
describe('基础状态', () => {
|
|
it('初始状态应该是未初始化', () => {
|
|
const manager = new MCPManager();
|
|
expect(manager.isInitialized()).toBe(false);
|
|
});
|
|
|
|
it('初始时没有工具', () => {
|
|
const manager = new MCPManager();
|
|
expect(manager.getTools()).toEqual([]);
|
|
});
|
|
|
|
it('初始时没有服务器状态', () => {
|
|
const manager = new MCPManager();
|
|
expect(manager.getServerStatuses()).toEqual([]);
|
|
});
|
|
});
|
|
|
|
describe('配置处理', () => {
|
|
it('空配置不应该连接任何服务器', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({});
|
|
expect(manager.isInitialized()).toBe(true);
|
|
expect(manager.getTools()).toEqual([]);
|
|
});
|
|
|
|
it('禁用的服务器应该显示在状态中', async () => {
|
|
const manager = new MCPManager();
|
|
const config: MCPConfig = {
|
|
mcp: {
|
|
disabled: {
|
|
type: 'local',
|
|
command: ['echo'],
|
|
enabled: false,
|
|
},
|
|
},
|
|
};
|
|
await manager.initialize(config);
|
|
const statuses = manager.getServerStatuses();
|
|
expect(statuses).toHaveLength(1);
|
|
expect(statuses[0].name).toBe('disabled');
|
|
expect(statuses[0].status).toBe('disabled');
|
|
});
|
|
});
|
|
|
|
describe('工具名解析', () => {
|
|
it('无效的工具名格式应返回错误', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({});
|
|
|
|
const result = await manager.callTool('invalidname', {});
|
|
expect(result.success).toBe(false);
|
|
expect(result.isError).toBe(true);
|
|
expect((result.content[0] as { text: string }).text).toContain('Invalid tool name');
|
|
});
|
|
|
|
it('服务器未连接时应返回错误', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({});
|
|
|
|
const result = await manager.callTool('server-tool', {});
|
|
expect(result.success).toBe(false);
|
|
expect(result.isError).toBe(true);
|
|
expect((result.content[0] as { text: string }).text).toContain('not connected');
|
|
});
|
|
});
|
|
|
|
describe('reconnect', () => {
|
|
it('服务器不存在时应抛出错误', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({ mcp: {} });
|
|
|
|
await expect(manager.reconnect('nonexistent')).rejects.toThrow('Server not found');
|
|
});
|
|
});
|
|
|
|
describe('setServerEnabled', () => {
|
|
it('服务器不存在时应抛出错误', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({ mcp: {} });
|
|
|
|
await expect(manager.setServerEnabled('nonexistent', true)).rejects.toThrow(
|
|
'Server not found'
|
|
);
|
|
});
|
|
});
|
|
|
|
describe('shutdown', () => {
|
|
it('空初始化后可以安全关闭', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.initialize({});
|
|
await manager.shutdown();
|
|
expect(manager.isInitialized()).toBe(false);
|
|
});
|
|
|
|
it('未初始化时可以安全关闭', async () => {
|
|
const manager = new MCPManager();
|
|
await manager.shutdown();
|
|
expect(manager.isInitialized()).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('事件发射', () => {
|
|
it('应该是 EventEmitter 实例', () => {
|
|
const manager = new MCPManager();
|
|
expect(typeof manager.on).toBe('function');
|
|
expect(typeof manager.emit).toBe('function');
|
|
});
|
|
|
|
it('应该支持事件监听', () => {
|
|
const manager = new MCPManager();
|
|
const callback = vi.fn();
|
|
manager.on('tools:changed', callback);
|
|
manager.emit('tools:changed');
|
|
expect(callback).toHaveBeenCalled();
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('getMCPManager / resetMCPManager', () => {
|
|
afterEach(() => {
|
|
resetMCPManager();
|
|
});
|
|
|
|
it('应该返回单例实例', () => {
|
|
const manager1 = getMCPManager();
|
|
const manager2 = getMCPManager();
|
|
expect(manager1).toBe(manager2);
|
|
});
|
|
|
|
it('重置后应该返回新实例', () => {
|
|
const manager1 = getMCPManager();
|
|
resetMCPManager();
|
|
const manager2 = getMCPManager();
|
|
expect(manager1).not.toBe(manager2);
|
|
});
|
|
|
|
it('重置的实例应该是未初始化状态', () => {
|
|
const manager1 = getMCPManager();
|
|
resetMCPManager();
|
|
const manager2 = getMCPManager();
|
|
expect(manager2.isInitialized()).toBe(false);
|
|
});
|
|
});
|