diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 394004aee..a69d2fdbd 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -1,6 +1,12 @@ # Changelog ## [Unreleased] +### Added + +- Added `filterBrowser` option to filter out browser automation MCP servers when builtin browser tool is enabled +- Added `isBrowserMCPServer()` function to detect browser automation MCP servers by name, URL, or command patterns +- Added `filterBrowserMCPServers()` function to remove browser MCP servers from loaded configurations +- Added `BrowserFilterResult` type for browser MCP server filtering results ## [12.17.0] - 2026-02-21 ### Added diff --git a/packages/coding-agent/src/mcp/config.ts b/packages/coding-agent/src/mcp/config.ts index 68d7f9604..fd95c324d 100644 --- a/packages/coding-agent/src/mcp/config.ts +++ b/packages/coding-agent/src/mcp/config.ts @@ -18,6 +18,8 @@ export interface LoadMCPConfigsOptions { enableProjectConfig?: boolean; /** Whether to filter out Exa MCP servers (default: true) */ filterExa?: boolean; + /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */ + filterBrowser?: boolean; } /** Result of loading MCP configs */ @@ -91,6 +93,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig { export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOptions): Promise { const enableProjectConfig = options?.enableProjectConfig ?? true; const filterExa = options?.filterExa ?? true; + const filterBrowser = options?.filterBrowser ?? false; // Load MCP servers via capability system const result = await loadCapability(mcpCapability.id, { cwd }); @@ -103,8 +106,8 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt // Load user-level disabled servers list const disabledServers = new Set(await readDisabledServers(getMCPConfigPath("user", cwd))); // Convert to legacy format and preserve source metadata - const configs: Record = {}; - const sources: Record = {}; + let configs: Record = {}; + let sources: Record = {}; for (const server of servers) { const config = convertToLegacyConfig(server); if (config.enabled === false || (server._source.level !== "user" && disabledServers.has(server.name))) { @@ -114,11 +117,19 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt sources[server.name] = server._source; } - const exaApiKeys: string[] = []; + let exaApiKeys: string[] = []; if (filterExa) { - const filterResult = filterExaMCPServers(configs, sources); - return { configs: filterResult.configs, exaApiKeys: filterResult.exaApiKeys, sources: filterResult.sources }; + const exaResult = filterExaMCPServers(configs, sources); + configs = exaResult.configs; + sources = exaResult.sources; + exaApiKeys = exaResult.exaApiKeys; + } + + if (filterBrowser) { + const browserResult = filterBrowserMCPServers(configs, sources); + configs = browserResult.configs; + sources = browserResult.sources; } return { configs, exaApiKeys, sources }; @@ -264,3 +275,89 @@ export function validateServerConfig(name: string, config: MCPServerConfig): str return errors; } + +/** Known browser automation MCP server names (lowercase) */ +const BROWSER_MCP_NAMES = new Set([ + "puppeteer", + "playwright", + "browserbase", + "browser-tools", + "browser-use", + "browser", +]); + +/** Patterns matching browser MCP package names in command/args */ +const BROWSER_MCP_PKG_PATTERN = + // Official packages + // - @modelcontextprotocol/server-puppeteer + // - @playwright/mcp + // - @browserbasehq/mcp-server-browserbase + // - @agentdeskai/browser-tools-mcp + // - @agent-infra/mcp-server-browser + // Community packages: puppeteer-mcp-server, playwright-mcp, pptr-mcp, etc. + /(?:@modelcontextprotocol\/server-puppeteer|@playwright\/mcp|@browserbasehq\/mcp-server-browserbase|@agentdeskai\/browser-tools-mcp|@agent-infra\/mcp-server-browser|puppeteer-mcp|playwright-mcp|pptr-mcp|browser-use-mcp|mcp-browser-use)/i; + +/** URL patterns for hosted browser MCP services */ +const BROWSER_MCP_URL_PATTERN = /browserbase\.com|browser-use\.com/i; + +/** + * Check if a server config is a browser automation MCP server. + */ +export function isBrowserMCPServer(name: string, config: MCPServerConfig): boolean { + // Check by server name + if (BROWSER_MCP_NAMES.has(name.toLowerCase())) { + return true; + } + + // Check by URL for HTTP/SSE servers + if (config.type === "http" || config.type === "sse") { + const httpConfig = config as { url?: string }; + if (httpConfig.url && BROWSER_MCP_URL_PATTERN.test(httpConfig.url)) { + return true; + } + } + + // Check by command/args for stdio servers + if (!config.type || config.type === "stdio") { + const stdioConfig = config as { command?: string; args?: string[] }; + if (stdioConfig.command && BROWSER_MCP_PKG_PATTERN.test(stdioConfig.command)) { + return true; + } + if (stdioConfig.args?.some(arg => BROWSER_MCP_PKG_PATTERN.test(arg))) { + return true; + } + } + + return false; +} + +/** Result of filtering browser MCP servers */ +export interface BrowserFilterResult { + /** Configs with browser servers removed */ + configs: Record; + /** Source metadata for remaining servers */ + sources: Record; +} + +/** + * Filter out browser automation MCP servers. + * Since we have a native browser tool, we don't need these MCP servers. + */ +export function filterBrowserMCPServers( + configs: Record, + sources: Record, +): BrowserFilterResult { + const filtered: Record = {}; + const filteredSources: Record = {}; + + for (const [name, config] of Object.entries(configs)) { + if (!isBrowserMCPServer(name, config)) { + filtered[name] = config; + if (sources[name]) { + filteredSources[name] = sources[name]; + } + } + } + + return { configs: filtered, sources: filteredSources }; +} diff --git a/packages/coding-agent/src/mcp/index.ts b/packages/coding-agent/src/mcp/index.ts index 5393352a9..7c3da09d9 100644 --- a/packages/coding-agent/src/mcp/index.ts +++ b/packages/coding-agent/src/mcp/index.ts @@ -8,10 +8,12 @@ // Client export { callTool, connectToServer, disconnectServer, listTools, serverSupportsTools } from "./client"; // Config -export type { ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config"; +export type { BrowserFilterResult, ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config"; export { extractExaApiKey, + filterBrowserMCPServers, filterExaMCPServers, + isBrowserMCPServer, isExaMCPServer, loadAllMCPConfigs, validateServerConfig, diff --git a/packages/coding-agent/src/mcp/loader.ts b/packages/coding-agent/src/mcp/loader.ts index 61ec1d9c1..e43a7753a 100644 --- a/packages/coding-agent/src/mcp/loader.ts +++ b/packages/coding-agent/src/mcp/loader.ts @@ -32,6 +32,8 @@ export interface MCPToolsLoadOptions { enableProjectConfig?: boolean; /** Whether to filter out Exa MCP servers (default: true) */ filterExa?: boolean; + /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */ + filterBrowser?: boolean; /** SQLite storage for MCP tool cache (null disables cache) */ cacheStorage?: AgentStorage | null; /** Auth storage used to resolve OAuth credentials before initial MCP connect */ @@ -69,6 +71,7 @@ export async function discoverAndLoadMCPTools(cwd: string, options?: MCPToolsLoa onConnecting: options?.onConnecting, enableProjectConfig: options?.enableProjectConfig, filterExa: options?.filterExa, + filterBrowser: options?.filterBrowser, }); } catch (error) { // If discovery fails entirely, return empty result diff --git a/packages/coding-agent/src/mcp/manager.ts b/packages/coding-agent/src/mcp/manager.ts index e94d5c86f..c734abe9e 100644 --- a/packages/coding-agent/src/mcp/manager.ts +++ b/packages/coding-agent/src/mcp/manager.ts @@ -68,6 +68,8 @@ export interface MCPDiscoverOptions { enableProjectConfig?: boolean; /** Whether to filter out Exa MCP servers (default: true) */ filterExa?: boolean; + /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */ + filterBrowser?: boolean; /** Called when starting to connect to servers */ onConnecting?: (serverNames: string[]) => void; } @@ -105,6 +107,7 @@ export class MCPManager { const { configs, exaApiKeys, sources } = await loadAllMCPConfigs(this.cwd, { enableProjectConfig: options?.enableProjectConfig, filterExa: options?.filterExa, + filterBrowser: options?.filterBrowser, }); const result = await this.connectServers(configs, sources, options?.onConnecting); result.exaApiKeys = exaApiKeys; diff --git a/packages/coding-agent/src/sdk.ts b/packages/coding-agent/src/sdk.ts index f317e5075..20b80d572 100644 --- a/packages/coding-agent/src/sdk.ts +++ b/packages/coding-agent/src/sdk.ts @@ -806,6 +806,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {} enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true, // Always filter Exa - we have native integration filterExa: true, + // Filter browser MCP servers when builtin browser tool is active + filterBrowser: (settings.get("browser.enabled") as boolean) ?? false, cacheStorage: settings.getStorage(), authStorage, });