Files
kurihada 5e32375f0e feat: 重构为 Monorepo 架构并实现 HTTP Server
架构变更:
- 采用 pnpm workspaces 实现 Monorepo 结构
- 将现有代码迁移到 packages/core
- 新增 packages/server HTTP 服务层

Server 功能:
- REST API: 会话管理、工具管理、配置管理
- WebSocket: 实时双向通信支持
- SSE: 服务端事件推送
- Hono + Bun 作为运行时

API 端点:
- GET/POST /api/sessions - 会话 CRUD
- GET/POST /api/sessions/:id/messages - 消息管理
- GET /api/sessions/:id/events - SSE 事件流
- WS /api/ws/:sessionId - WebSocket 连接
- GET/POST /api/tools - 工具管理
- GET/PUT /api/config - 配置管理
2025-12-12 10:42:20 +08:00

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);
});
});