UNPKG

@kazuph/mcp-browser-tabs

Version:

MCP server for retrieving Chrome browser tabs information

239 lines (237 loc) • 8.69 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import { zodToJsonSchema } from "zod-to-json-schema"; import { exec } from "node:child_process"; import { promisify } from "node:util"; const execAsync = promisify(exec); // Get Chrome tabs using legacy window/tab index method async function getChromeTabsLegacy() { const script = ` tell application "Google Chrome" set windowList to windows set output to "" repeat with windowIndex from 1 to count of windowList set theWindow to item windowIndex of windowList set activeTabIndex to active tab index of theWindow set tabList to tabs of theWindow repeat with tabIndexInWindow from 1 to count of tabList set theTab to item tabIndexInWindow of tabList set isActive to (tabIndexInWindow = activeTabIndex) set output to output & windowIndex & "|||" & tabIndexInWindow & "|||" & isActive & "|||" & (title of theTab) & "|||" & (URL of theTab) & "\\n" end repeat end repeat return output end tell `; try { const { stdout } = await execAsync(`osascript -e '${script}'`); const tabsData = stdout .trim() .split("\n") .filter((line) => line.length > 0) .map((line) => { const [windowIndex, tabIndex, isActive, title, url] = line.split("|||"); return { windowIndex: Number.parseInt(windowIndex, 10), tabIndex: Number.parseInt(tabIndex, 10), isActive: isActive === "true", title: title || "", url: url || "", }; }); // Group by windows const windowMap = new Map(); for (const tabData of tabsData) { if (!windowMap.has(tabData.windowIndex)) { windowMap.set(tabData.windowIndex, { windowIndex: tabData.windowIndex, tabs: [], }); } const window = windowMap.get(tabData.windowIndex); window.tabs.push({ windowIndex: tabData.windowIndex, tabIndex: tabData.tabIndex, title: tabData.title, url: tabData.url, isActive: tabData.isActive, }); } return Array.from(windowMap.values()); } catch (error) { throw new Error(`Failed to get Chrome tabs: ${error instanceof Error ? error.message : String(error)}`); } } // Close tab by window/tab index async function closeChromeTabByIndex(windowIndex, tabIndex) { const script = ` tell application "Google Chrome" try set targetWindow to window ${windowIndex} set targetTab to tab ${tabIndex} of targetWindow close targetTab on error errMsg return "Error: " & errMsg end try end tell `; try { await execAsync(`osascript -e '${script}'`); } catch (error) { throw new Error(`Failed to close Chrome tab: ${error instanceof Error ? error.message : String(error)}`); } } // Activate tab by window/tab index async function activateChromeTabByIndex(windowIndex, tabIndex) { const script = ` tell application "Google Chrome" try set targetWindow to window ${windowIndex} set (active tab index of targetWindow) to ${tabIndex} set index of targetWindow to 1 on error errMsg return "Error: " & errMsg end try end tell `; try { await execAsync(`osascript -e '${script}'`); } catch (error) { throw new Error(`Failed to activate Chrome tab: ${error instanceof Error ? error.message : String(error)}`); } } // Schema definitions const ListToolsSchema = z.object({ method: z.literal("tools/list"), }); const CallToolSchema = z.object({ method: z.literal("tools/call"), params: z.object({ name: z.string(), arguments: z.record(z.unknown()).optional(), }), }); // Server setup const server = new Server({ name: "mcp-browser-tabs-legacy", version: "2.0.0-legacy", }, { capabilities: { tools: {}, }, }); // Tools list handler server.setRequestHandler(ListToolsSchema, async (request, extra) => { const tools = [ { name: "get_tabs", description: "Get all open tabs from Google Chrome browser using window/tab index method. Shows tab information with window and tab indices for reliable operations.", inputSchema: zodToJsonSchema(z.object({})), }, { name: "close_tab", description: "Close a specific tab using window/tab index. Uses 1-based indexing (window 1, tab 1, etc.).", inputSchema: zodToJsonSchema(z.object({ windowIndex: z.number().int().positive().describe("Window index (1-based)"), tabIndex: z.number().int().positive().describe("Tab index (1-based)"), })), }, { name: "activate_tab", description: "Activate (focus) a specific tab using window/tab index. Brings the tab to the front and makes it active.", inputSchema: zodToJsonSchema(z.object({ windowIndex: z.number().int().positive().describe("Window index (1-based)"), tabIndex: z.number().int().positive().describe("Tab index (1-based)"), })), }, ]; return { tools }; }); // Tool execution handler server.setRequestHandler(CallToolSchema, async (request, extra) => { try { const { name } = request.params; if (name === "get_tabs") { const windows = await getChromeTabsLegacy(); const formattedOutput = windows .map((window) => { const activeTab = window.tabs.find(tab => tab.isActive); const activeIndicator = activeTab ? ` (Active: ${activeTab.windowIndex}-${activeTab.tabIndex})` : ""; return `Window ${window.windowIndex}${activeIndicator}: ${window.tabs .map((tab) => { const activeMarker = tab.isActive ? " ā˜…" : ""; return ` ${tab.windowIndex}-${tab.tabIndex}. ${tab.title}${activeMarker} ${tab.url}`; }) .join("\n")}`; }) .join("\n\n"); const totalTabs = windows.reduce((sum, window) => sum + window.tabs.length, 0); return { content: [ { type: "text", text: `Found ${totalTabs} open tabs in Chrome: ${formattedOutput} šŸ“ LEGACY MODE: Using window/tab index method - Use close_tab or activate_tab with windowIndex and tabIndex - Example: window 1, tab 3 = windowIndex: 1, tabIndex: 3`, }, ], }; } if (name === "close_tab") { const { windowIndex, tabIndex } = request.params.arguments; await closeChromeTabByIndex(windowIndex, tabIndex); return { content: [ { type: "text", text: `āœ… Successfully closed tab at window ${windowIndex}, tab ${tabIndex}`, }, ], }; } if (name === "activate_tab") { const { windowIndex, tabIndex } = request.params.arguments; await activateChromeTabByIndex(windowIndex, tabIndex); return { content: [ { type: "text", text: `āœ… Successfully activated tab at window ${windowIndex}, tab ${tabIndex}`, }, ], }; } throw new Error(`Unknown tool: ${name}`); } catch (error) { return { content: [ { type: "text", text: `āŒ Error: ${error instanceof Error ? error.message : String(error)}`, }, ], isError: true, }; } }); // Server startup async function runServer() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Browser Tabs MCP server (Legacy Mode) running on stdio"); } runServer().catch((error) => { process.stderr.write(`Fatal error running server: ${error}\n`); process.exit(1); }); //# sourceMappingURL=index-legacy-only.js.map