/**
* 浏览器自动化工具(基于 Puppeteer)
*/
export class BrowserTools {
constructor() {
this.browsers = new Map(); // 存储打开的浏览器实例
this.pages = new Map(); // 存储打开的页面实例
this.puppeteer = null;
this.puppeteerAvailable = false;
// 尝试加载 puppeteer
try {
import('puppeteer').then(module => {
this.puppeteer = module.default;
this.puppeteerAvailable = true;
}).catch(() => {
console.error('[MCP] puppeteer 未安装,浏览器自动化功能将不可用');
});
} catch (error) {
console.error('[MCP] puppeteer 加载失败:', error.message);
}
}
getToolDefinitions() {
return [
{
name: 'browser_launch',
description: '启动浏览器',
inputSchema: {
type: 'object',
properties: {
headless: { type: 'boolean', description: '是否无头模式(可选,默认 false)' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
},
},
{
name: 'browser_navigate',
description: '导航到网页',
inputSchema: {
type: 'object',
properties: {
url: { type: 'string', description: '网页 URL' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
required: ['url'],
},
},
{
name: 'browser_click',
description: '点击元素',
inputSchema: {
type: 'object',
properties: {
selector: { type: 'string', description: 'CSS 选择器' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
required: ['selector'],
},
},
{
name: 'browser_type',
description: '输入文本到元素',
inputSchema: {
type: 'object',
properties: {
selector: { type: 'string', description: 'CSS 选择器' },
text: { type: 'string', description: '要输入的文本' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
required: ['selector', 'text'],
},
},
{
name: 'browser_screenshot',
description: '截取网页截图',
inputSchema: {
type: 'object',
properties: {
path: { type: 'string', description: '保存路径(可选)' },
fullPage: { type: 'boolean', description: '是否整页截图(可选)' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
},
},
{
name: 'browser_get_text',
description: '获取元素文本',
inputSchema: {
type: 'object',
properties: {
selector: { type: 'string', description: 'CSS 选择器' },
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
required: ['selector'],
},
},
{
name: 'browser_close',
description: '关闭浏览器',
inputSchema: {
type: 'object',
properties: {
sessionId: { type: 'string', description: '会话 ID(可选)' },
},
},
},
];
}
canHandle(toolName) {
const tools = ['browser_launch', 'browser_navigate', 'browser_click',
'browser_type', 'browser_screenshot', 'browser_get_text', 'browser_close'];
return tools.includes(toolName);
}
async executeTool(name, args) {
if (!this.puppeteerAvailable) {
return {
success: false,
error: 'puppeteer 未安装。请运行: npm install puppeteer'
};
}
switch (name) {
case 'browser_launch':
return await this.launchBrowser(args.headless, args.sessionId);
case 'browser_navigate':
return await this.navigate(args.url, args.sessionId);
case 'browser_click':
return await this.click(args.selector, args.sessionId);
case 'browser_type':
return await this.type(args.selector, args.text, args.sessionId);
case 'browser_screenshot':
return await this.screenshot(args.path, args.fullPage, args.sessionId);
case 'browser_get_text':
return await this.getText(args.selector, args.sessionId);
case 'browser_close':
return await this.closeBrowser(args.sessionId);
default:
throw new Error(`未知工具: ${name}`);
}
}
async launchBrowser(headless = false, sessionId = 'default') {
try {
if (this.browsers.has(sessionId)) {
return { success: true, message: '浏览器已存在', sessionId };
}
const browser = await this.puppeteer.launch({
headless,
defaultViewport: null,
args: ['--start-maximized']
});
const page = await browser.newPage();
this.browsers.set(sessionId, browser);
this.pages.set(sessionId, page);
return { success: true, message: '浏览器已启动', sessionId };
} catch (error) {
return { success: false, error: error.message };
}
}
async navigate(url, sessionId = 'default') {
try {
const page = this.pages.get(sessionId);
if (!page) {
return { success: false, error: '浏览器未启动,请先调用 browser_launch' };
}
await page.goto(url, { waitUntil: 'networkidle2' });
const title = await page.title();
return { success: true, url, title, message: '导航成功' };
} catch (error) {
return { success: false, error: error.message };
}
}
async click(selector, sessionId = 'default') {
try {
const page = this.pages.get(sessionId);
if (!page) {
return { success: false, error: '浏览器未启动' };
}
await page.waitForSelector(selector, { timeout: 5000 });
await page.click(selector);
return { success: true, selector, message: '点击成功' };
} catch (error) {
return { success: false, error: error.message };
}
}
async type(selector, text, sessionId = 'default') {
try {
const page = this.pages.get(sessionId);
if (!page) {
return { success: false, error: '浏览器未启动' };
}
await page.waitForSelector(selector, { timeout: 5000 });
await page.type(selector, text);
return { success: true, selector, text, message: '输入成功' };
} catch (error) {
return { success: false, error: error.message };
}
}
async screenshot(savePath, fullPage = false, sessionId = 'default') {
try {
const page = this.pages.get(sessionId);
if (!page) {
return { success: false, error: '浏览器未启动' };
}
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const defaultPath = `screenshot-browser-${timestamp}.png`;
const path = savePath || defaultPath;
await page.screenshot({ path, fullPage });
return { success: true, path, fullPage, message: '截图已保存' };
} catch (error) {
return { success: false, error: error.message };
}
}
async getText(selector, sessionId = 'default') {
try {
const page = this.pages.get(sessionId);
if (!page) {
return { success: false, error: '浏览器未启动' };
}
await page.waitForSelector(selector, { timeout: 5000 });
const text = await page.$eval(selector, el => el.textContent);
return { success: true, selector, text };
} catch (error) {
return { success: false, error: error.message };
}
}
async closeBrowser(sessionId = 'default') {
try {
const browser = this.browsers.get(sessionId);
if (!browser) {
return { success: false, error: '浏览器未找到' };
}
await browser.close();
this.browsers.delete(sessionId);
this.pages.delete(sessionId);
return { success: true, message: '浏览器已关闭', sessionId };
} catch (error) {
return { success: false, error: error.message };
}
}
}