chore: initialize qiming workspace repository
This commit is contained in:
148
qimingclaw/crates/agent-gui-server/tests/mcp/integration.test.ts
Normal file
148
qimingclaw/crates/agent-gui-server/tests/mcp/integration.test.ts
Normal file
@@ -0,0 +1,148 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user