149 lines
4.7 KiB
TypeScript
149 lines
4.7 KiB
TypeScript
/**
|
|
* MCP Integration Test
|
|
*
|
|
* Tests the full MCP server lifecycle:
|
|
* 1. Start server on a random port
|
|
* 2. Connect MCP client via Streamable HTTP
|
|
* 3. List tools (should return 14)
|
|
* 4. Verify tool names
|
|
* 5. Stop server
|
|
*
|
|
* Note: gui_screenshot and gui_execute_task require actual desktop access,
|
|
* so we only verify tool listing and schema in this test.
|
|
*/
|
|
|
|
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';
|
|
import { createGuiAgentServer, type GuiAgentServer } from '../../src/mcp/server.js';
|
|
import type { GuiAgentConfig } from '../../src/config.js';
|
|
|
|
const TEST_PORT = 60099; // Use a high port unlikely to conflict
|
|
|
|
function createTestConfig(): GuiAgentConfig {
|
|
return {
|
|
provider: 'anthropic',
|
|
model: 'claude-sonnet-4-20250514',
|
|
apiKey: 'test-integration-key',
|
|
port: TEST_PORT,
|
|
maxSteps: 10,
|
|
stepDelayMs: 100,
|
|
stuckThreshold: 3,
|
|
jpegQuality: 75,
|
|
displayIndex: 0,
|
|
transport: 'http',
|
|
};
|
|
}
|
|
|
|
const EXPECTED_ATOMIC_TOOLS = [
|
|
'gui_screenshot',
|
|
'gui_click',
|
|
'gui_double_click',
|
|
'gui_move_mouse',
|
|
'gui_drag',
|
|
'gui_scroll',
|
|
'gui_type',
|
|
'gui_press_key',
|
|
'gui_hotkey',
|
|
'gui_cursor_position',
|
|
'gui_list_displays',
|
|
'gui_find_image',
|
|
'gui_wait_for_image',
|
|
'gui_analyze_screen',
|
|
];
|
|
|
|
const EXPECTED_TASK_TOOLS = [
|
|
'gui_execute_task',
|
|
];
|
|
|
|
describe('MCP Integration', () => {
|
|
let server: GuiAgentServer;
|
|
let client: Client;
|
|
let transport: StreamableHTTPClientTransport;
|
|
|
|
beforeAll(async () => {
|
|
// Start server
|
|
server = createGuiAgentServer(createTestConfig());
|
|
await server.start();
|
|
|
|
// Connect client
|
|
transport = new StreamableHTTPClientTransport(
|
|
new URL(`http://127.0.0.1:${TEST_PORT}/mcp`),
|
|
);
|
|
client = new Client({ name: 'test-client', version: '1.0.0' });
|
|
await client.connect(transport);
|
|
}, 10000);
|
|
|
|
afterAll(async () => {
|
|
try { await client?.close(); } catch { /* ignore */ }
|
|
try { await server?.stop(); } catch { /* ignore */ }
|
|
}, 10000);
|
|
|
|
it('should list all 15 tools', async () => {
|
|
const result = await client.listTools();
|
|
expect(result.tools).toHaveLength(15);
|
|
});
|
|
|
|
it('should include all atomic tools', async () => {
|
|
const result = await client.listTools();
|
|
const toolNames = result.tools.map(t => t.name);
|
|
for (const name of EXPECTED_ATOMIC_TOOLS) {
|
|
expect(toolNames).toContain(name);
|
|
}
|
|
});
|
|
|
|
it('should include gui_execute_task tool', async () => {
|
|
const result = await client.listTools();
|
|
const toolNames = result.tools.map(t => t.name);
|
|
for (const name of EXPECTED_TASK_TOOLS) {
|
|
expect(toolNames).toContain(name);
|
|
}
|
|
});
|
|
|
|
it('should have correct schema for gui_execute_task', async () => {
|
|
const result = await client.listTools();
|
|
const taskTool = result.tools.find(t => t.name === 'gui_execute_task');
|
|
expect(taskTool).toBeDefined();
|
|
expect(taskTool!.inputSchema).toBeDefined();
|
|
expect(taskTool!.inputSchema.properties).toHaveProperty('task');
|
|
expect(taskTool!.inputSchema.required).toContain('task');
|
|
});
|
|
|
|
it('should have correct schema for gui_click', async () => {
|
|
const result = await client.listTools();
|
|
const clickTool = result.tools.find(t => t.name === 'gui_click');
|
|
expect(clickTool).toBeDefined();
|
|
expect(clickTool!.inputSchema.properties).toHaveProperty('x');
|
|
expect(clickTool!.inputSchema.properties).toHaveProperty('y');
|
|
expect(clickTool!.inputSchema.required).toContain('x');
|
|
expect(clickTool!.inputSchema.required).toContain('y');
|
|
});
|
|
|
|
it('should list resources', async () => {
|
|
const result = await client.listResources();
|
|
expect(result.resources.length).toBeGreaterThanOrEqual(3);
|
|
const uris = result.resources.map(r => r.uri);
|
|
expect(uris).toContain('gui://status');
|
|
expect(uris).toContain('gui://permissions');
|
|
expect(uris).toContain('gui://audit-log');
|
|
});
|
|
|
|
it('should read gui://status resource', async () => {
|
|
const result = await client.readResource({ uri: 'gui://status' });
|
|
expect(result.contents).toHaveLength(1);
|
|
expect(result.contents[0].mimeType).toBe('application/json');
|
|
|
|
const status = JSON.parse(result.contents[0].text as string);
|
|
expect(status).toHaveProperty('platform');
|
|
expect(status).toHaveProperty('model');
|
|
expect(status.model).toBe('claude-sonnet-4-20250514');
|
|
});
|
|
|
|
it('should read gui://audit-log resource', async () => {
|
|
const result = await client.readResource({ uri: 'gui://audit-log' });
|
|
expect(result.contents).toHaveLength(1);
|
|
const log = JSON.parse(result.contents[0].text as string);
|
|
expect(Array.isArray(log)).toBe(true);
|
|
});
|
|
});
|