/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import assert from 'node:assert';
import fs from 'node:fs';
import {describe, it} from 'node:test';
import {Client} from '@modelcontextprotocol/sdk/client/index.js';
import {StdioClientTransport} from '@modelcontextprotocol/sdk/client/stdio.js';
import {executablePath} from 'puppeteer';
import type {ToolDefinition} from '../src/tools/ToolDefinition';
describe('e2e', () => {
async function withClient(
cb: (client: Client) => Promise<void>,
extraArgs: string[] = [],
) {
const transport = new StdioClientTransport({
command: 'node',
args: [
'build/src/index.js',
'--headless',
'--isolated',
'--executable-path',
executablePath(),
...extraArgs,
],
});
const client = new Client(
{
name: 'e2e-test',
version: '1.0.0',
},
{
capabilities: {},
},
);
try {
await client.connect(transport);
await cb(client);
} finally {
await client.close();
}
}
it('calls a tool', async () => {
await withClient(async client => {
const result = await client.callTool({
name: 'list_pages',
arguments: {},
});
assert.deepStrictEqual(result, {
content: [
{
type: 'text',
text: '# list_pages response\n## Pages\n1: about:blank [selected]',
},
],
});
});
});
it('calls a tool multiple times', async () => {
await withClient(async client => {
let result = await client.callTool({
name: 'list_pages',
arguments: {},
});
result = await client.callTool({
name: 'list_pages',
arguments: {},
});
assert.deepStrictEqual(result, {
content: [
{
type: 'text',
text: '# list_pages response\n## Pages\n1: about:blank [selected]',
},
],
});
});
});
it('has all tools', async () => {
await withClient(async client => {
const {tools} = await client.listTools();
const exposedNames = tools.map(t => t.name).sort();
const files = fs.readdirSync('build/src/tools');
const definedNames = [];
for (const file of files) {
if (file === 'ToolDefinition.js') {
continue;
}
const fileTools = await import(`../src/tools/${file}`);
for (const maybeTool of Object.values<ToolDefinition>(fileTools)) {
if ('name' in maybeTool) {
if (maybeTool.annotations?.conditions) {
continue;
}
definedNames.push(maybeTool.name);
}
}
}
definedNames.sort();
assert.deepStrictEqual(exposedNames, definedNames);
});
});
it('has experimental extensions tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const clickAt = tools.find(t => t.name === 'install_extension');
assert.ok(clickAt);
},
['--category-extensions'],
);
});
it('has experimental vision tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const clickAt = tools.find(t => t.name === 'click_at');
assert.ok(clickAt);
},
['--experimental-vision'],
);
});
it('has experimental interop tools', async () => {
await withClient(
async client => {
const {tools} = await client.listTools();
const getTabId = tools.find(t => t.name === 'get_tab_id');
assert.ok(getTabId);
},
['--experimental-interop-tools'],
);
});
});