mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-23 04:21:31 -07:00
197 lines
5.8 KiB
TypeScript
197 lines
5.8 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
import { createMcpDeclarativeTools } from './mcpToolWrapper.js';
|
|
import type { BrowserManager, McpToolCallResult } from './browserManager.js';
|
|
import type { MessageBus } from '../../confirmation-bus/message-bus.js';
|
|
import type { Tool as McpTool } from '@modelcontextprotocol/sdk/types.js';
|
|
|
|
describe('mcpToolWrapper', () => {
|
|
let mockBrowserManager: BrowserManager;
|
|
let mockMessageBus: MessageBus;
|
|
let mockMcpTools: McpTool[];
|
|
|
|
beforeEach(() => {
|
|
vi.resetAllMocks();
|
|
|
|
// Setup mock MCP tools discovered from server
|
|
mockMcpTools = [
|
|
{
|
|
name: 'take_snapshot',
|
|
description: 'Take a snapshot of the page accessibility tree',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
verbose: { type: 'boolean', description: 'Include details' },
|
|
},
|
|
},
|
|
},
|
|
{
|
|
name: 'click',
|
|
description: 'Click on an element by uid',
|
|
inputSchema: {
|
|
type: 'object',
|
|
properties: {
|
|
uid: { type: 'string', description: 'Element uid' },
|
|
},
|
|
required: ['uid'],
|
|
},
|
|
},
|
|
];
|
|
|
|
// Setup mock browser manager
|
|
mockBrowserManager = {
|
|
getDiscoveredTools: vi.fn().mockResolvedValue(mockMcpTools),
|
|
callTool: vi.fn().mockResolvedValue({
|
|
content: [{ type: 'text', text: 'Tool result' }],
|
|
} as McpToolCallResult),
|
|
} as unknown as BrowserManager;
|
|
|
|
// Setup mock message bus
|
|
mockMessageBus = {
|
|
publish: vi.fn().mockResolvedValue(undefined),
|
|
subscribe: vi.fn(),
|
|
unsubscribe: vi.fn(),
|
|
} as unknown as MessageBus;
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('createMcpDeclarativeTools', () => {
|
|
it('should create declarative tools from discovered MCP tools', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
expect(tools).toHaveLength(3);
|
|
expect(tools[0].name).toBe('take_snapshot');
|
|
expect(tools[1].name).toBe('click');
|
|
expect(tools[2].name).toBe('type_text');
|
|
});
|
|
|
|
it('should return tools with correct description', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
// Descriptions include augmented hints, so we check they contain the original
|
|
expect(tools[0].description).toContain(
|
|
'Take a snapshot of the page accessibility tree',
|
|
);
|
|
expect(tools[1].description).toContain('Click on an element by uid');
|
|
});
|
|
|
|
it('should return tools with proper FunctionDeclaration schema', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const schema = tools[0].schema;
|
|
expect(schema.name).toBe('take_snapshot');
|
|
expect(schema.parametersJsonSchema).toBeDefined();
|
|
});
|
|
});
|
|
|
|
describe('McpDeclarativeTool.build', () => {
|
|
it('should create invocation that can be executed', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[0].build({ verbose: true });
|
|
|
|
expect(invocation).toBeDefined();
|
|
expect(invocation.params).toEqual({ verbose: true });
|
|
});
|
|
|
|
it('should return invocation with correct description', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[0].build({});
|
|
|
|
expect(invocation.getDescription()).toContain('take_snapshot');
|
|
});
|
|
});
|
|
|
|
describe('McpToolInvocation.execute', () => {
|
|
it('should call browserManager.callTool with correct params', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[1].build({ uid: 'elem-123' });
|
|
await invocation.execute(new AbortController().signal);
|
|
|
|
expect(mockBrowserManager.callTool).toHaveBeenCalledWith(
|
|
'click',
|
|
{
|
|
uid: 'elem-123',
|
|
},
|
|
expect.any(AbortSignal),
|
|
);
|
|
});
|
|
|
|
it('should return success result from MCP tool', async () => {
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[0].build({ verbose: true });
|
|
const result = await invocation.execute(new AbortController().signal);
|
|
|
|
expect(result.llmContent).toBe('Tool result');
|
|
expect(result.error).toBeUndefined();
|
|
});
|
|
|
|
it('should handle MCP tool errors', async () => {
|
|
vi.mocked(mockBrowserManager.callTool).mockResolvedValue({
|
|
content: [{ type: 'text', text: 'Element not found' }],
|
|
isError: true,
|
|
} as McpToolCallResult);
|
|
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[1].build({ uid: 'invalid' });
|
|
const result = await invocation.execute(new AbortController().signal);
|
|
|
|
expect(result.error).toBeDefined();
|
|
expect(result.error?.message).toBe('Element not found');
|
|
});
|
|
|
|
it('should handle exceptions during tool call', async () => {
|
|
vi.mocked(mockBrowserManager.callTool).mockRejectedValue(
|
|
new Error('Connection lost'),
|
|
);
|
|
|
|
const tools = await createMcpDeclarativeTools(
|
|
mockBrowserManager,
|
|
mockMessageBus,
|
|
);
|
|
|
|
const invocation = tools[0].build({});
|
|
const result = await invocation.execute(new AbortController().signal);
|
|
|
|
expect(result.error).toBeDefined();
|
|
expect(result.error?.message).toBe('Connection lost');
|
|
});
|
|
});
|
|
});
|