index-multi.js•23.9 kB
// #!/usr/bin/env node
// import { Server } from '@modelcontextprotocol/sdk/server/index.js';
// import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
// import {
// CallToolRequestSchema,
// ErrorCode,
// ListToolsRequestSchema,
// McpError,
// } from '@modelcontextprotocol/sdk/types.js';
// import { chromium, firefox } from 'playwright';
// class MultiTabFirefoxMCPServer {
// constructor() {
// this.server = new Server(
// {
// name: 'firefox-multi-mcp-server',
// version: '2.0.0',
// },
// {
// capabilities: {
// tools: {},
// },
// }
// );
// this.browser = null;
// this.contexts = new Map(); // Map of contextId -> BrowserContext
// this.pages = new Map(); // Map of tabId -> Page
// this.activeTabId = null;
// this.setupToolHandlers();
// }
// setupToolHandlers() {
// this.server.setRequestHandler(ListToolsRequestSchema, async () => {
// return {
// tools: [
// {
// name: 'launch_firefox_multi',
// description: 'Launch Firefox browser with multi-tab support',
// inputSchema: {
// type: 'object',
// properties: {
// headless: {
// type: 'boolean',
// description: 'Run browser in headless mode',
// default: false
// }
// }
// }
// },
// {
// name: 'create_tab',
// description: 'Create a new tab with isolated session',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Unique identifier for this tab'
// },
// url: {
// type: 'string',
// description: 'Initial URL to navigate to',
// default: 'about:blank'
// },
// contextId: {
// type: 'string',
// description: 'Context ID for session isolation (optional, auto-generated if not provided)'
// }
// },
// required: ['tabId']
// }
// },
// {
// name: 'list_tabs',
// description: 'List all active tabs',
// inputSchema: {
// type: 'object',
// properties: {}
// }
// },
// {
// name: 'close_tab',
// description: 'Close a specific tab',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID to close'
// }
// },
// required: ['tabId']
// }
// },
// {
// name: 'navigate',
// description: 'Navigate to a URL',
// inputSchema: {
// type: 'object',
// properties: {
// url: {
// type: 'string',
// description: 'URL to navigate to'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// },
// required: ['url']
// }
// },
// {
// name: 'click',
// description: 'Click on an element',
// inputSchema: {
// type: 'object',
// properties: {
// selector: {
// type: 'string',
// description: 'CSS selector or text content to click'
// },
// coordinates: {
// type: 'object',
// properties: {
// x: { type: 'number' },
// y: { type: 'number' }
// },
// description: 'Click at specific coordinates (alternative to selector)'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'type_text',
// description: 'Type text into an input field',
// inputSchema: {
// type: 'object',
// properties: {
// selector: {
// type: 'string',
// description: 'CSS selector of the input field'
// },
// text: {
// type: 'string',
// description: 'Text to type'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// },
// required: ['selector', 'text']
// }
// },
// {
// name: 'get_page_content',
// description: 'Get the HTML content of the current page',
// inputSchema: {
// type: 'object',
// properties: {
// selector: {
// type: 'string',
// description: 'CSS selector to get specific element content (optional)'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'get_page_text',
// description: 'Get the visible text content of the current page',
// inputSchema: {
// type: 'object',
// properties: {
// selector: {
// type: 'string',
// description: 'CSS selector to get specific element text (optional)'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'screenshot',
// description: 'Take a screenshot of the current page',
// inputSchema: {
// type: 'object',
// properties: {
// path: {
// type: 'string',
// description: 'File path to save screenshot',
// default: 'screenshot.png'
// },
// fullPage: {
// type: 'boolean',
// description: 'Capture full page',
// default: false
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'wait_for_element',
// description: 'Wait for an element to appear on the page',
// inputSchema: {
// type: 'object',
// properties: {
// selector: {
// type: 'string',
// description: 'CSS selector to wait for'
// },
// timeout: {
// type: 'number',
// description: 'Timeout in milliseconds',
// default: 30000
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// },
// required: ['selector']
// }
// },
// {
// name: 'execute_script',
// description: 'Execute JavaScript in the browser',
// inputSchema: {
// type: 'object',
// properties: {
// script: {
// type: 'string',
// description: 'JavaScript code to execute'
// },
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// },
// required: ['script']
// }
// },
// {
// name: 'close_browser',
// description: 'Close the Firefox browser and all tabs',
// inputSchema: {
// type: 'object',
// properties: {}
// }
// },
// {
// name: 'get_current_url',
// description: 'Get the current page URL',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'back',
// description: 'Navigate back in browser history',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'forward',
// description: 'Navigate forward in browser history',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'reload',
// description: 'Reload the current page',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID (uses active tab if not provided)'
// }
// }
// }
// },
// {
// name: 'set_active_tab',
// description: 'Set the active tab for operations that don\'t specify a tabId',
// inputSchema: {
// type: 'object',
// properties: {
// tabId: {
// type: 'string',
// description: 'Tab ID to set as active'
// }
// },
// required: ['tabId']
// }
// }
// ],
// };
// });
// this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
// const { name, arguments: args } = request.params;
// try {
// switch (name) {
// case 'launch_firefox_multi':
// return await this.launchFirefoxMulti(args);
// case 'create_tab':
// return await this.createTab(args);
// case 'list_tabs':
// return await this.listTabs();
// case 'close_tab':
// return await this.closeTab(args);
// case 'set_active_tab':
// return await this.setActiveTab(args);
// case 'navigate':
// return await this.navigate(args);
// case 'click':
// return await this.click(args);
// case 'type_text':
// return await this.typeText(args);
// case 'get_page_content':
// return await this.getPageContent(args);
// case 'get_page_text':
// return await this.getPageText(args);
// case 'screenshot':
// return await this.screenshot(args);
// case 'wait_for_element':
// return await this.waitForElement(args);
// case 'execute_script':
// return await this.executeScript(args);
// case 'close_browser':
// return await this.closeBrowser();
// case 'get_current_url':
// return await this.getCurrentUrl(args);
// case 'back':
// return await this.back(args);
// case 'forward':
// return await this.forward(args);
// case 'reload':
// return await this.reload(args);
// default:
// throw new McpError(
// ErrorCode.MethodNotFound,
// `Unknown tool: ${name}`
// );
// }
// } catch (error) {
// throw new McpError(
// ErrorCode.InternalError,
// `Error executing ${name}: ${error.message}`
// );
// }
// });
// }
// async launchFirefoxMulti(args = {}) {
// const { headless = false } = args;
// try {
// this.browser = await firefox.launch({
// headless,
// firefoxUserPrefs: {
// 'dom.webnotifications.enabled': false,
// 'media.navigator.permission.disabled': true
// }
// });
// return {
// content: [
// {
// type: 'text',
// text: `Firefox launched successfully with multi-tab support. Use create_tab to create new tabs.`
// }
// ]
// };
// } catch (error) {
// throw new Error(`Failed to launch Firefox: ${error.message}`);
// }
// }
// async createTab(args) {
// this.ensureBrowserRunning();
// const { tabId, url = 'about:blank', contextId } = args;
// if (this.pages.has(tabId)) {
// throw new Error(`Tab with ID '${tabId}' already exists`);
// }
// // Create or reuse context
// const effectiveContextId = contextId || `context-${tabId}`;
// let context;
// if (this.contexts.has(effectiveContextId)) {
// context = this.contexts.get(effectiveContextId);
// } else {
// context = await this.browser.newContext({
// // Each context has isolated storage, cookies, etc.
// storageState: undefined // Start with clean state
// });
// this.contexts.set(effectiveContextId, context);
// }
// // Create new page in the context
// const page = await context.newPage();
// await page.goto(url);
// this.pages.set(tabId, page);
// // Set as active tab if no active tab exists
// if (!this.activeTabId) {
// this.activeTabId = tabId;
// }
// return {
// content: [
// {
// type: 'text',
// text: `Tab '${tabId}' created successfully. Navigated to: ${url}. Context: ${effectiveContextId}`
// }
// ]
// };
// }
// async listTabs() {
// const tabs = [];
// for (const [tabId, page] of this.pages) {
// const url = page.url();
// const isActive = tabId === this.activeTabId;
// tabs.push({
// tabId,
// url,
// active: isActive
// });
// }
// return {
// content: [
// {
// type: 'text',
// text: `Active tabs (${tabs.length}):\n` +
// tabs.map(tab => `- ${tab.tabId}: ${tab.url}${tab.active ? ' (active)' : ''}`).join('\n')
// }
// ]
// };
// }
// async closeTab(args) {
// const { tabId } = args;
// if (!this.pages.has(tabId)) {
// throw new Error(`Tab '${tabId}' not found`);
// }
// const page = this.pages.get(tabId);
// await page.close();
// this.pages.delete(tabId);
// // If this was the active tab, clear active tab
// if (this.activeTabId === tabId) {
// this.activeTabId = this.pages.size > 0 ? Array.from(this.pages.keys())[0] : null;
// }
// return {
// content: [
// {
// type: 'text',
// text: `Tab '${tabId}' closed successfully.${this.activeTabId ? ` Active tab is now '${this.activeTabId}'` : ''}`
// }
// ]
// };
// }
// async setActiveTab(args) {
// const { tabId } = args;
// if (!this.pages.has(tabId)) {
// throw new Error(`Tab '${tabId}' not found`);
// }
// this.activeTabId = tabId;
// return {
// content: [
// {
// type: 'text',
// text: `Active tab set to '${tabId}'`
// }
// ]
// };
// }
// getPage(tabId) {
// if (tabId) {
// if (!this.pages.has(tabId)) {
// throw new Error(`Tab '${tabId}' not found`);
// }
// return this.pages.get(tabId);
// } else {
// if (!this.activeTabId || !this.pages.has(this.activeTabId)) {
// throw new Error('No active tab. Use create_tab or set_active_tab first.');
// }
// return this.pages.get(this.activeTabId);
// }
// }
// async navigate(args) {
// this.ensureBrowserRunning();
// const { url, tabId } = args;
// const page = this.getPage(tabId);
// await page.goto(url);
// return {
// content: [
// {
// type: 'text',
// text: `Tab '${tabId || this.activeTabId}' navigated to: ${url}`
// }
// ]
// };
// }
// async click(args) {
// this.ensureBrowserRunning();
// const { selector, coordinates, tabId } = args;
// const page = this.getPage(tabId);
// if (coordinates) {
// await page.click(`body`, { position: coordinates });
// return {
// content: [
// {
// type: 'text',
// text: `Clicked at coordinates (${coordinates.x}, ${coordinates.y}) in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// } else if (selector) {
// await page.click(selector);
// return {
// content: [
// {
// type: 'text',
// text: `Clicked element '${selector}' in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// } else {
// throw new Error('Either selector or coordinates must be provided');
// }
// }
// async typeText(args) {
// this.ensureBrowserRunning();
// const { selector, text, tabId } = args;
// const page = this.getPage(tabId);
// await page.fill(selector, text);
// return {
// content: [
// {
// type: 'text',
// text: `Typed "${text}" into '${selector}' in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// }
// async getPageContent(args = {}) {
// this.ensureBrowserRunning();
// const { selector, tabId } = args;
// const page = this.getPage(tabId);
// let content;
// if (selector) {
// content = await page.innerHTML(selector);
// } else {
// content = await page.content();
// }
// return {
// content: [
// {
// type: 'text',
// text: content
// }
// ]
// };
// }
// async getPageText(args = {}) {
// this.ensureBrowserRunning();
// const { selector, tabId } = args;
// const page = this.getPage(tabId);
// let text;
// if (selector) {
// text = await page.textContent(selector);
// } else {
// text = await page.textContent('body');
// }
// return {
// content: [
// {
// type: 'text',
// text: text || ''
// }
// ]
// };
// }
// async screenshot(args = {}) {
// this.ensureBrowserRunning();
// const { path = 'screenshot.png', fullPage = false, tabId } = args;
// const page = this.getPage(tabId);
// // Add tab ID to path if not specified
// const effectiveTabId = tabId || this.activeTabId;
// const effectivePath = path.includes(effectiveTabId) ? path :
// path.replace(/\.([^.]+)$/, `_${effectiveTabId}.$1`);
// await page.screenshot({
// path: effectivePath,
// fullPage
// });
// return {
// content: [
// {
// type: 'text',
// text: `Screenshot of tab '${effectiveTabId}' saved to: ${effectivePath}`
// }
// ]
// };
// }
// async waitForElement(args) {
// this.ensureBrowserRunning();
// const { selector, timeout = 30000, tabId } = args;
// const page = this.getPage(tabId);
// await page.waitForSelector(selector, { timeout });
// return {
// content: [
// {
// type: 'text',
// text: `Element '${selector}' found in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// }
// async executeScript(args) {
// this.ensureBrowserRunning();
// const { script, tabId } = args;
// const page = this.getPage(tabId);
// const result = await page.evaluate(script);
// return {
// content: [
// {
// type: 'text',
// text: `Script executed in tab '${tabId || this.activeTabId}'. Result: ${JSON.stringify(result)}`
// }
// ]
// };
// }
// async closeBrowser() {
// if (this.browser) {
// await this.browser.close();
// this.browser = null;
// this.contexts.clear();
// this.pages.clear();
// this.activeTabId = null;
// }
// return {
// content: [
// {
// type: 'text',
// text: 'Firefox browser closed, all tabs and contexts cleared'
// }
// ]
// };
// }
// async getCurrentUrl(args = {}) {
// this.ensureBrowserRunning();
// const { tabId } = args;
// const page = this.getPage(tabId);
// const url = page.url();
// return {
// content: [
// {
// type: 'text',
// text: `Current URL in tab '${tabId || this.activeTabId}': ${url}`
// }
// ]
// };
// }
// async back(args = {}) {
// this.ensureBrowserRunning();
// const { tabId } = args;
// const page = this.getPage(tabId);
// await page.goBack();
// return {
// content: [
// {
// type: 'text',
// text: `Navigated back in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// }
// async forward(args = {}) {
// this.ensureBrowserRunning();
// const { tabId } = args;
// const page = this.getPage(tabId);
// await page.goForward();
// return {
// content: [
// {
// type: 'text',
// text: `Navigated forward in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// }
// async reload(args = {}) {
// this.ensureBrowserRunning();
// const { tabId } = args;
// const page = this.getPage(tabId);
// await page.reload();
// return {
// content: [
// {
// type: 'text',
// text: `Page reloaded in tab '${tabId || this.activeTabId}'`
// }
// ]
// };
// }
// ensureBrowserRunning() {
// if (!this.browser) {
// throw new Error('Firefox browser is not running. Please launch it first using the launch_firefox_multi tool.');
// }
// }
// async run() {
// const transport = new StdioServerTransport();
// await this.server.connect(transport);
// console.error('Multi-tab Firefox MCP server running on stdio');
// }
// }
// const server = new MultiTabFirefoxMCPServer();
// server.run().catch(console.error);