diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1c1e487..822e54b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,17 @@ jobs: echo "✅ All versions synchronized at $MCP_VERSION" + - name: Build MCP Bundle + run: | + cd MCPBrowser + npm run build:mcpb + + - name: Verify MCP Bundle + run: | + ls -lh MCPBrowser/dist/*.mcpb + # Verify it's a valid ZIP with manifest.json + unzip -l MCPBrowser/dist/*.mcpb | head -5 + - name: Verify VS Code Extension packages run: | npm install -g @vscode/vsce ovsx diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index a82720c..83b23fa 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -99,6 +99,12 @@ jobs: cd VSCodeExtension ovsx publish mcpbrowser-${{ steps.version.outputs.version }}.vsix -p ${{ secrets.OVSX_PAT }} + - name: Build MCP Bundle + run: | + cd MCPBrowser + npm run build:mcpb + ls -lh dist/*.mcpb + - name: Extract changelog for version id: changelog run: | @@ -126,7 +132,8 @@ jobs: gh release create "v$VERSION" \ --title "v$VERSION" \ --notes-file release-notes.md \ - VSCodeExtension/mcpbrowser-$VERSION.vsix + VSCodeExtension/mcpbrowser-$VERSION.vsix \ + MCPBrowser/dist/mcpbrowser-$VERSION.mcpb - name: Deployment Summary run: | @@ -136,5 +143,5 @@ jobs: echo " ✅ MCP Registry (Visual Studio & other MCP clients)" echo " ✅ VS Code Marketplace" echo " ✅ Open VSX Registry" - echo " ✅ GitHub Releases" + echo " ✅ GitHub Releases (with .vsix + .mcpb bundle)" diff --git a/MCPBrowser/README.md b/MCPBrowser/README.md index 822091f..909e6d4 100644 --- a/MCPBrowser/README.md +++ b/MCPBrowser/README.md @@ -5,7 +5,7 @@ [![Claude Desktop](https://img.shields.io/badge/Claude-Desktop-5865F2?logo=anthropic)](https://modelcontextprotocol.io/quickstart/user) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. +> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. MCPBrowser combines private data access, untrusted content exposure, and external communication (the MCP "[lethal trifecta](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/)") — all tool annotations accurately declare these risks so MCP clients can enforce appropriate safety controls. See [MCP_COMPLIANCE.md](docs/MCP_COMPLIANCE.md) for details. > 💡 **Why MCPBrowser over Puppeteer/Playwright MCP servers?** Puppeteer and Playwright are browser automation libraries — their MCP servers give agents raw, low-level browser commands. MCPBrowser uses Puppeteer under the hood and was built specifically for AI agents, adding an intelligence layer that handles the hard parts automatically. > @@ -167,7 +167,7 @@ For VS Code, Kiro, Antigravity, or other editors — manual MCP setup. Add to yo ### `browser_fetch_webpage` -Fetches web pages using your Chrome/Edge browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content. +Fetches web pages using your Chrome/Edge/Brave browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content. **Parameters:** - `url` (string, required) - The URL to fetch @@ -458,7 +458,7 @@ Environment variables for advanced setup: | Variable | Description | Default | |----------|-------------|---------| -| `CHROME_PATH` | Path to Chrome/Edge | Auto-detect | +| `CHROME_PATH` | Path to Chrome/Edge/Brave | Auto-detect | | `CHROME_USER_DATA_DIR` | Browser profile directory | `%LOCALAPPDATA%/ChromeAuthProfile` | | `CHROME_REMOTE_DEBUG_PORT` | DevTools port | `9222` | diff --git a/MCPBrowser/docs/MCP_COMPLIANCE.md b/MCPBrowser/docs/MCP_COMPLIANCE.md index 730ef70..2468d24 100644 --- a/MCPBrowser/docs/MCP_COMPLIANCE.md +++ b/MCPBrowser/docs/MCP_COMPLIANCE.md @@ -21,11 +21,11 @@ Per the MCP spec, each tool definition must include: | `title` | ⚪ Optional | ✅ Implemented | Human-readable display name (moved from annotations to top-level) | | `outputSchema` | ⚪ Optional | ✅ Implemented | JSON Schema for structured output validation | | `icons` | ⚪ Optional | ❌ Not used | Array of icons for UI display | -| `annotations` | ⚪ Optional | ❌ Removed | Properties describing tool behavior | +| `annotations` | ⚪ Optional | ✅ Implemented | Risk hints: readOnlyHint, destructiveHint, idempotentHint, openWorldHint | ## Verified Tool Definitions -All 5 tools conform to the specification: +All 12 tools conform to the specification: ### 1. browser_fetch_webpage ✅ @@ -262,37 +262,52 @@ Following MCP security recommendations: - ⚠️ **Timeouts**: Clients should implement tool call timeouts - ⚠️ **Audit logging**: Clients should log tool usage -## Changes Made for Compliance +## Tool Annotations ✅ -### Before (Non-Compliant) -```javascript -{ - name: "browser_close_tab", - description: "...", - inputSchema: { ... }, - outputSchema: { ... }, - annotations: { - title: "Close Tab" // ❌ Title in annotations - } -} -``` +All 12 tools declare MCP risk annotations per the [Tool Annotations spec](https://modelcontextprotocol.io/specification/2025-11-25/server/tools#annotations): -### After (Compliant) -```javascript -{ - name: "browser_close_tab", - title: "Close Tab", // ✅ Title at top level - description: "...", - inputSchema: { ... }, - outputSchema: { ... } -} -``` +| Tool | readOnly | destructive | idempotent | openWorld | +|------|----------|-------------|------------|-----------| +| `browser_fetch_webpage` | ✅ true | false | ✅ true | ✅ true | +| `browser_get_current_html` | ✅ true | false | ✅ true | false | +| `browser_take_screenshot` | ✅ true | false | ✅ true | false | +| `browser_detect_forms` | ✅ true | false | ✅ true | false | +| `browser_plugin_info` | ✅ true | false | ✅ true | false | +| `browser_scroll_page` | false | false | false | false | +| `browser_click_element` | false | false | false | ✅ true | +| `browser_type_text` | false | false | false | false | +| `browser_navigate_history` | false | false | false | ✅ true | +| `browser_plugin_action` | false | false | false | ✅ true | +| `browser_execute_javascript` | false | ⚠️ true | false | ✅ true | +| `browser_close_tab` | false | ⚠️ true | ✅ true | false | + +Clients can use these hints to auto-approve read-only tools and prompt for confirmation on destructive ones. + +## Security Model — Lethal Trifecta ⚠️ + +MCPBrowser inherently combines all three legs of the "lethal trifecta" defined in the [MCP Tool Annotations blog post](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/): + +| Leg | Present | How | +|-----|---------|-----| +| **Private data access** | ✅ | Connects to user's existing browser with cookies, SSO sessions, and saved credentials | +| **Untrusted content exposure** | ✅ | Loads and processes arbitrary web pages | +| **External communication** | ✅ | Can navigate to any URL, execute JavaScript in page context | + +This is **by design** — browser automation requires all three capabilities. The following mitigations are in place: + +- **Tool annotations** accurately reflect risk levels (see table above) +- **Destructive tools** (`execute_javascript`, `close_tab`) are annotated with `destructiveHint: true` +- **Open-world tools** that reach external resources are annotated with `openWorldHint: true` +- **Single-user model** — connects to the user's own browser (no shared/multi-tenant mode) +- **EULA acceptance** required before first use +- **Sequential request queue** — one operation at a time, no parallel mutations +- **HTML sanitization** — script tags and event handlers stripped from extracted content ## Testing -All 5 tools verified with: -- ✅ 158 unit tests passing -- ✅ 5 MCP compliance tests passing +All 12 tools verified with: +- ✅ Unit tests passing +- ✅ MCP compliance tests passing - ✅ Response format validation - ✅ Structured content validation @@ -302,11 +317,10 @@ All 5 tools verified with: All tool definitions: - Follow required field structure -- Implement optional fields correctly +- Implement optional fields correctly (title, outputSchema, annotations) - Use valid JSON Schema format - Document inputs and outputs - Handle errors properly - Follow naming conventions - Support structured output validation - -No violations or warnings. +- Declare risk annotations for client safety UX diff --git a/MCPBrowser/package-lock.json b/MCPBrowser/package-lock.json index bb496d8..b573cb9 100644 --- a/MCPBrowser/package-lock.json +++ b/MCPBrowser/package-lock.json @@ -1,12 +1,12 @@ { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.25.1", diff --git a/MCPBrowser/package.json b/MCPBrowser/package.json index b1c7e54..31a7e0c 100644 --- a/MCPBrowser/package.json +++ b/MCPBrowser/package.json @@ -1,9 +1,9 @@ { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "mcpName": "io.github.cherchyk/mcpbrowser", "type": "module", - "description": "MCP browser server — load and interact with any web page using a real Chrome/Edge/Brave browser with full JavaScript execution. Handles login flows, authentication, SSO, CAPTCHAs, and anti-bot protection. Use whenever a real browser is needed to load a page, especially when JavaScript rendering or user login is required.", + "description": "MCP browser server — loads and interacts with web pages using the user's Chromium-based browser with full JavaScript execution. Handles authentication, SSO, and anti-bot protection automatically via the user's existing browser session.", "main": "src/mcp-browser.js", "bin": { "mcpbrowser": "src/mcp-browser.js" @@ -13,7 +13,8 @@ "test": "node tests/run-all.js", "test:unit": "node tests/run-unit.js", "test:cli": "node tests/cli.test.js", - "test:descriptions": "node tests/tool-selection/run-tool-selection-tests.js" + "test:descriptions": "node tests/tool-selection/run-tool-selection-tests.js", + "build:mcpb": "node scripts/build-mcpb.js" }, "keywords": [ "mcp", diff --git a/MCPBrowser/scripts/build-mcpb.js b/MCPBrowser/scripts/build-mcpb.js new file mode 100644 index 0000000..5f1c095 --- /dev/null +++ b/MCPBrowser/scripts/build-mcpb.js @@ -0,0 +1,190 @@ +#!/usr/bin/env node +/** + * Build script for MCP Bundle (.mcpb) format. + * Creates a portable ZIP archive that MCP clients can install with one click. + * + * Usage: node scripts/build-mcpb.js + * Output: dist/mcpbrowser-.mcpb + * + * See: https://github.com/modelcontextprotocol/mcpb/blob/main/MANIFEST.md + */ + +import { readFileSync, writeFileSync, mkdirSync, existsSync, cpSync, rmSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath, pathToFileURL } from 'url'; +import { execSync } from 'child_process'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); +const ROOT = join(__dirname, '..'); + +// --------------------------------------------------------------------------- +// 1. Read package.json for version and metadata +// --------------------------------------------------------------------------- +const pkg = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')); + +// --------------------------------------------------------------------------- +// 2. Dynamically import action modules to extract tool name + description +// --------------------------------------------------------------------------- +async function getTools() { + const actionFiles = [ + 'fetch-page.js', 'get-current-html.js', 'take-screenshot.js', + 'detect-forms.js', 'click-element.js', 'type-text.js', + 'scroll-page.js', 'navigate-history.js', 'execute-javascript.js', + 'close-tab.js', 'plugin-info.js', 'plugin-action.js' + ]; + + const tools = []; + for (const file of actionFiles) { + const mod = await import(pathToFileURL(join(ROOT, 'src', 'actions', file)).href); + // Each module exports a *_TOOL constant + const toolExport = Object.values(mod).find(v => v && typeof v === 'object' && v.name && v.description); + if (toolExport) { + tools.push({ name: toolExport.name, description: toolExport.description }); + } + } + return tools; +} + +// --------------------------------------------------------------------------- +// 3. Import prompts +// --------------------------------------------------------------------------- +async function getPrompts() { + const { PROMPTS } = await import(pathToFileURL(join(ROOT, 'src', 'core', 'prompts.js')).href); + return PROMPTS.map(p => ({ + name: p.name, + description: p.description, + arguments: p.arguments?.map(a => a.name) || [] + })); +} + +// --------------------------------------------------------------------------- +// 4. Build manifest.json +// --------------------------------------------------------------------------- +async function buildManifest() { + const tools = await getTools(); + const prompts = await getPrompts(); + + return { + manifest_version: "0.3", + name: "mcpbrowser", + display_name: "MCP Browser", + version: pkg.version, + description: pkg.description, + long_description: "Browser automation MCP server that connects to the user's existing Chromium-based browser (Chrome, Edge, Brave) with all cookies, logins, and SSO sessions intact. Provides 12 tools for fetching pages, clicking elements, typing text, taking screenshots, detecting forms, executing JavaScript, and more. Includes site-specific plugins for optimized interactions with known services.", + author: { + name: "cherchyk", + url: "https://github.com/cherchyk" + }, + repository: { + type: "git", + url: "https://github.com/cherchyk/MCPBrowser.git" + }, + homepage: "https://github.com/cherchyk/MCPBrowser#readme", + support: "https://github.com/cherchyk/MCPBrowser/issues", + icon: "icon.png", + license: "MIT", + server: { + type: "node", + entry_point: "server/src/mcp-browser.js", + mcp_config: { + command: "node", + args: ["${__dirname}/server/src/mcp-browser.js"], + env: {} + } + }, + tools, + tools_generated: false, + prompts, + prompts_generated: false, + keywords: [ + "browser", "web-automation", "puppeteer", "chrome", "edge", + "authentication", "sso", "mcp-server", "scraping" + ], + compatibility: { + platforms: ["darwin", "win32", "linux"], + runtimes: { + node: ">=18.0.0" + } + } + }; +} + +// --------------------------------------------------------------------------- +// 5. Package into .mcpb (ZIP) +// --------------------------------------------------------------------------- +async function build() { + const distDir = join(ROOT, 'dist'); + const stageDir = join(distDir, '_mcpb_stage'); + + // Clean previous build + if (existsSync(stageDir)) rmSync(stageDir, { recursive: true }); + mkdirSync(join(stageDir, 'server'), { recursive: true }); + + // Generate manifest.json + const manifest = await buildManifest(); + writeFileSync(join(stageDir, 'manifest.json'), JSON.stringify(manifest, null, 2)); + console.log(`✅ manifest.json (${manifest.tools.length} tools, ${manifest.prompts.length} prompts)`); + + // Copy icon + const iconSrc = join(ROOT, '..', 'VSCodeExtension', 'icon.png'); + if (existsSync(iconSrc)) { + cpSync(iconSrc, join(stageDir, 'icon.png')); + console.log('✅ icon.png'); + } else { + console.log('⚠️ icon.png not found at VSCodeExtension/icon.png — skipping'); + } + + // Copy server files (src/ + package.json) + cpSync(join(ROOT, 'src'), join(stageDir, 'server', 'src'), { recursive: true }); + cpSync(join(ROOT, 'package.json'), join(stageDir, 'server', 'package.json')); + console.log('✅ server/src + server/package.json'); + + // Install production dependencies in staging (clean, minimal node_modules) + console.log('📦 Installing production dependencies...'); + execSync('npm install --omit=dev --ignore-scripts', { + cwd: join(stageDir, 'server'), + stdio: 'pipe' + }); + // Move node_modules up to bundle root (MCPB convention) + const nmStaged = join(stageDir, 'server', 'node_modules'); + if (existsSync(nmStaged)) { + cpSync(nmStaged, join(stageDir, 'node_modules'), { recursive: true }); + rmSync(nmStaged, { recursive: true }); + } + console.log('✅ node_modules (production only)'); + + // Create .mcpb ZIP using .NET ZipFile (fast, cross-platform on PowerShell/Node) + const outFile = join(distDir, `mcpbrowser-${pkg.version}.mcpb`); + if (existsSync(outFile)) rmSync(outFile); + + if (process.platform === 'win32') { + // .NET ZipFile is orders of magnitude faster than Compress-Archive + execSync( + `powershell -NoProfile -Command "Add-Type -Assembly System.IO.Compression.FileSystem; [IO.Compression.ZipFile]::CreateFromDirectory('${stageDir}', '${outFile}')"`, + { stdio: 'inherit' } + ); + } else { + execSync(`cd "${stageDir}" && zip -r "${outFile}" .`, { stdio: 'inherit' }); + } + + // Clean staging (use system rmdir on Windows — much faster for deep node_modules) + if (process.platform === 'win32') { + execSync(`rmdir /s /q "${stageDir}"`, { stdio: 'pipe' }); + } else { + rmSync(stageDir, { recursive: true }); + } + + const stats = readFileSync(outFile); + const sizeMB = (stats.length / 1024 / 1024).toFixed(2); + console.log(`\n📦 ${outFile}`); + console.log(` Size: ${sizeMB} MB`); + console.log(` Version: ${pkg.version}`); + console.log(` Tools: ${manifest.tools.length}`); + console.log(` Prompts: ${manifest.prompts.length}`); +} + +build().catch(err => { + console.error('❌ Build failed:', err.message); + process.exit(1); +}); diff --git a/MCPBrowser/src/actions/click-element.js b/MCPBrowser/src/actions/click-element.js index b16f9a5..8395dee 100644 --- a/MCPBrowser/src/actions/click-element.js +++ b/MCPBrowser/src/actions/click-element.js @@ -159,6 +159,13 @@ export const CLICK_ELEMENT_TOOL = { }, required: ["status", "fallbackUsed", "nativeAttempt", "currentUrl", "message", "html", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Click Element", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true } }; diff --git a/MCPBrowser/src/actions/close-tab.js b/MCPBrowser/src/actions/close-tab.js index 6cbea12..12ef204 100644 --- a/MCPBrowser/src/actions/close-tab.js +++ b/MCPBrowser/src/actions/close-tab.js @@ -81,6 +81,13 @@ export const CLOSE_TAB_TOOL = { }, required: ["message", "hostname", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Close Tab", + readOnlyHint: false, + destructiveHint: true, + idempotentHint: true, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/detect-forms.js b/MCPBrowser/src/actions/detect-forms.js index 27684fe..31fe6a7 100644 --- a/MCPBrowser/src/actions/detect-forms.js +++ b/MCPBrowser/src/actions/detect-forms.js @@ -149,6 +149,13 @@ export const DETECT_FORMS_TOOL = { }, required: ["forms", "orphanedFields", "totalFieldCount", "summary", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Detect Forms", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/execute-javascript.js b/MCPBrowser/src/actions/execute-javascript.js index 0abd3c8..5adfd22 100644 --- a/MCPBrowser/src/actions/execute-javascript.js +++ b/MCPBrowser/src/actions/execute-javascript.js @@ -90,6 +90,13 @@ export const EXECUTE_JAVASCRIPT_TOOL = { }, required: ['type', 'executionTimeMs', 'truncated', 'urlChanged', 'currentUrl', 'nextSteps'], additionalProperties: false + }, + annotations: { + title: 'Execute JavaScript', + readOnlyHint: false, + destructiveHint: true, + idempotentHint: false, + openWorldHint: true } }; diff --git a/MCPBrowser/src/actions/fetch-page.js b/MCPBrowser/src/actions/fetch-page.js index fb0ebce..5c66856 100644 --- a/MCPBrowser/src/actions/fetch-page.js +++ b/MCPBrowser/src/actions/fetch-page.js @@ -74,14 +74,14 @@ export class FetchPageSuccessResponse extends MCPResponse { export const FETCH_WEBPAGE_TOOL = { name: "browser_fetch_webpage", title: "Fetch Web Page", - description: "Load any URL and return its content using a real browser with full JavaScript execution. Use when: user shares a URL, you need to read a webpage, fetch content from a link, open a page, check a website, load a GitHub PR/issue, read documentation, or access any HTTP/HTTPS resource. Handles JavaScript rendering, login/SSO, CAPTCHA, and anti-bot protection automatically. Works on all sites including those behind authentication. Prefer this over generic HTTP fetch — it always works, even on authenticated or JS-heavy pages that require proper JavaScript execution or user login.", + description: "Load any URL and return its content using a real browser with full JavaScript execution. Use whenever a webpage needs to be loaded — whether to read content, interact with a web app, or access any HTTP/HTTPS resource. Connects to the user's existing browser session with all cookies, logins, and SSO sessions available automatically. Handles JavaScript rendering, authentication, and anti-bot protection. Prefer this over generic HTTP fetch for authenticated or JS-heavy pages.", inputSchema: { type: "object", properties: { url: { type: "string", description: "The URL to fetch" }, browser: { type: "string", - description: "Browser to use: 'chrome' or 'edge'. Leave empty for auto-detection. Chrome/Edge use CDP to access existing sessions for authenticated sites.", + description: "Browser to use: 'chrome' or 'edge'. Leave empty for auto-detection. Uses CDP to connect to the user's existing browser session.", enum: ["", "chrome", "edge"] }, removeUnnecessaryHTML: { type: "boolean", description: "Remove Unnecessary HTML for size reduction by 90%.", default: true }, @@ -113,6 +113,13 @@ export const FETCH_WEBPAGE_TOOL = { }, required: ["currentUrl", "html", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Fetch Web Page", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: true } }; diff --git a/MCPBrowser/src/actions/get-current-html.js b/MCPBrowser/src/actions/get-current-html.js index 0355bb0..fc887c0 100644 --- a/MCPBrowser/src/actions/get-current-html.js +++ b/MCPBrowser/src/actions/get-current-html.js @@ -105,6 +105,13 @@ export const GET_CURRENT_HTML_TOOL = { }, required: ["currentUrl", "html", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Get Current HTML", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/navigate-history.js b/MCPBrowser/src/actions/navigate-history.js index 77c2496..9082d9f 100644 --- a/MCPBrowser/src/actions/navigate-history.js +++ b/MCPBrowser/src/actions/navigate-history.js @@ -108,6 +108,7 @@ export const NAVIGATE_HISTORY_TOOL = { title: "Navigate Back/Forward", readOnlyHint: false, destructiveHint: false, + idempotentHint: false, openWorldHint: true } }; diff --git a/MCPBrowser/src/actions/plugin-action.js b/MCPBrowser/src/actions/plugin-action.js index 5d75f48..a79b8a9 100644 --- a/MCPBrowser/src/actions/plugin-action.js +++ b/MCPBrowser/src/actions/plugin-action.js @@ -70,6 +70,13 @@ export const PLUGIN_ACTION_TOOL = { }, required: ["nextSteps"], additionalProperties: true + }, + annotations: { + title: "Plugin Action", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: true } }; diff --git a/MCPBrowser/src/actions/plugin-info.js b/MCPBrowser/src/actions/plugin-info.js index 69ea193..9966b00 100644 --- a/MCPBrowser/src/actions/plugin-info.js +++ b/MCPBrowser/src/actions/plugin-info.js @@ -81,6 +81,13 @@ export const PLUGIN_INFO_TOOL = { }, required: ["nextSteps"], additionalProperties: true + }, + annotations: { + title: "Plugin Info", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/scroll-page.js b/MCPBrowser/src/actions/scroll-page.js index 449a065..8030ff2 100644 --- a/MCPBrowser/src/actions/scroll-page.js +++ b/MCPBrowser/src/actions/scroll-page.js @@ -146,6 +146,13 @@ export const SCROLL_PAGE_TOOL = { }, required: ["currentUrl", "scrollX", "scrollY", "pageWidth", "pageHeight", "viewportWidth", "viewportHeight", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Scroll Page", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/take-screenshot.js b/MCPBrowser/src/actions/take-screenshot.js index 127e53d..be6f814 100644 --- a/MCPBrowser/src/actions/take-screenshot.js +++ b/MCPBrowser/src/actions/take-screenshot.js @@ -113,6 +113,13 @@ export const TAKE_SCREENSHOT_TOOL = { }, required: ["currentUrl", "screenshotBase64", "mimeType", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Take Screenshot", + readOnlyHint: true, + destructiveHint: false, + idempotentHint: true, + openWorldHint: false } }; diff --git a/MCPBrowser/src/actions/type-text.js b/MCPBrowser/src/actions/type-text.js index a73f056..b3b3719 100644 --- a/MCPBrowser/src/actions/type-text.js +++ b/MCPBrowser/src/actions/type-text.js @@ -111,6 +111,13 @@ export const TYPE_TEXT_TOOL = { }, required: ["currentUrl", "message", "html", "nextSteps"], additionalProperties: false + }, + annotations: { + title: "Type Text", + readOnlyHint: false, + destructiveHint: false, + idempotentHint: false, + openWorldHint: false } }; diff --git a/MCPBrowser/src/core/prompts.js b/MCPBrowser/src/core/prompts.js new file mode 100644 index 0000000..619297f --- /dev/null +++ b/MCPBrowser/src/core/prompts.js @@ -0,0 +1,148 @@ +/** + * MCP Prompts — reusable workflow templates exposed as slash-commands. + * Each prompt returns a message array that guides the LLM through + * a multi-tool workflow using MCPBrowser's tools. + */ + +// ============================================================================ +// PROMPT DEFINITIONS +// ============================================================================ + +export const PROMPTS = [ + { + name: "scrape-page", + description: "Fetch a web page and extract its content as clean HTML", + arguments: [ + { name: "url", description: "URL to scrape", required: true }, + { name: "selector", description: "CSS selector to scope extraction (optional)", required: false } + ] + }, + { + name: "fill-form", + description: "Detect form fields on a page and fill them with provided values", + arguments: [ + { name: "url", description: "URL of the page with the form", required: true }, + { name: "values", description: "JSON object mapping field selectors to values, e.g. {\"#email\": \"user@example.com\", \"#password\": \"secret\"}", required: true } + ] + }, + { + name: "visual-audit", + description: "Take a screenshot and get HTML of a page for visual comparison or accessibility review", + arguments: [ + { name: "url", description: "URL to audit", required: true } + ] + }, + { + name: "authenticated-workflow", + description: "Navigate an authenticated site — fetch the page, handle login if needed, then continue", + arguments: [ + { name: "url", description: "URL that may require authentication", required: true } + ] + } +]; + +// ============================================================================ +// PROMPT MESSAGE GENERATORS +// ============================================================================ + +/** + * Returns the messages array for a given prompt name and arguments. + * @param {string} name - Prompt name + * @param {Record} args - Prompt arguments + * @returns {{ description: string, messages: Array<{ role: string, content: { type: string, text: string } }> }} + */ +export function getPromptMessages(name, args = {}) { + const prompt = PROMPTS.find(p => p.name === name); + if (!prompt) { + throw new Error(`Unknown prompt: ${name}`); + } + + // Validate required arguments + const missing = (prompt.arguments || []) + .filter(a => a.required && !args[a.name]) + .map(a => a.name); + if (missing.length > 0) { + throw new Error(`Missing required argument(s) for prompt "${name}": ${missing.join(', ')}`); + } + + switch (name) { + case "scrape-page": + return buildScrapePage(args); + case "fill-form": + return buildFillForm(args); + case "visual-audit": + return buildVisualAudit(args); + case "authenticated-workflow": + return buildAuthenticatedWorkflow(args); + default: + throw new Error(`Unknown prompt: ${name}`); + } +} + +// ============================================================================ +// BUILDERS +// ============================================================================ + +function buildScrapePage({ url, selector }) { + const selectorInstruction = selector + ? ` Focus on the content matching the CSS selector \`${selector}\`.` + : ''; + + return { + description: `Scrape content from ${url}`, + messages: [ + { + role: "user", + content: { + type: "text", + text: `Fetch the web page at ${url} using browser_fetch_webpage.${selectorInstruction} Return the extracted HTML content. If the page requires scrolling to load more content, use browser_scroll_page to load it, then browser_get_current_html to capture the full page.` + } + } + ] + }; +} + +function buildFillForm({ url, values }) { + return { + description: `Fill form on ${url}`, + messages: [ + { + role: "user", + content: { + type: "text", + text: `Fill out the form on ${url}:\n\n1. Fetch the page with browser_fetch_webpage.\n2. Detect available forms with browser_detect_forms.\n3. For each field in the values below, use browser_type_text to enter the value:\n\n${values}\n\n4. After filling all fields, use browser_click_element to submit the form (look for a submit button).\n5. Return the resulting page HTML with browser_get_current_html.` + } + } + ] + }; +} + +function buildVisualAudit({ url }) { + return { + description: `Visual audit of ${url}`, + messages: [ + { + role: "user", + content: { + type: "text", + text: `Perform a visual audit of ${url}:\n\n1. Fetch the page with browser_fetch_webpage.\n2. Take a full-page screenshot with browser_take_screenshot.\n3. Get the current HTML with browser_get_current_html.\n4. Analyze the screenshot and HTML for:\n - Layout issues or broken elements\n - Missing alt text on images\n - Color contrast problems\n - Mobile responsiveness concerns\n5. Provide a summary of findings.` + } + } + ] + }; +} + +function buildAuthenticatedWorkflow({ url }) { + return { + description: `Authenticated workflow for ${url}`, + messages: [ + { + role: "user", + content: { + type: "text", + text: `Navigate to ${url} using browser_fetch_webpage. This page may require authentication.\n\nIf the page shows a login form or redirects to a login page:\n1. Notify me that authentication is needed.\n2. Wait for me to complete the login in the browser window.\n3. After I confirm login is complete, use browser_fetch_webpage again with the original URL.\n\nOnce authenticated, return the page content using browser_get_current_html.` + } + } + ] + }; +} diff --git a/MCPBrowser/src/mcp-browser.js b/MCPBrowser/src/mcp-browser.js index 4185b30..84ac290 100644 --- a/MCPBrowser/src/mcp-browser.js +++ b/MCPBrowser/src/mcp-browser.js @@ -7,7 +7,7 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; -import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; +import { ListToolsRequestSchema, CallToolRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema } from "@modelcontextprotocol/sdk/types.js"; import { fileURLToPath } from 'url'; import { readFileSync } from 'fs'; import { dirname, join } from 'path'; @@ -38,6 +38,9 @@ import { detectForms, DETECT_FORMS_TOOL } from './actions/detect-forms.js'; import { pluginAction, PLUGIN_ACTION_TOOL } from './actions/plugin-action.js'; import { pluginInfo, PLUGIN_INFO_TOOL } from './actions/plugin-info.js'; +// Import prompt definitions +import { PROMPTS, getPromptMessages } from './core/prompts.js'; + // Import functions for testing exports import { getBrowser, closeBrowser } from './core/browser.js'; import { getOrCreatePage, queueRequest, navigateToUrl, waitForPageReady, extractAndProcessHtml } from './core/page.js'; @@ -62,7 +65,10 @@ async function main() { const server = new Server( { name: "MCPBrowser", version: packageJson.version }, - { capabilities: { tools: {}, logging: {} } } + { + capabilities: { tools: {}, logging: {}, prompts: {} }, + instructions: "Browser automation server using the user's existing browser session (cookies and auth intact). Workflow: browser_fetch_webpage → browser_take_screenshot (visual content) or browser_get_current_html (re-read after interaction) → browser_click_element / browser_type_text (interact) → browser_close_tab (cleanup). All tools except browser_fetch_webpage and browser_plugin_info require a page loaded first. One tab per domain — same-domain navigations reuse the existing tab. Requests are queued and processed sequentially. Maximum one browser connection at a time. Pages loaded via browser_fetch_webpage persist until browser_close_tab is called or the server shuts down. Screenshots return base64 PNG — prefer browser_get_current_html for text extraction. browser_execute_javascript runs in the page context with access to the full DOM and JavaScript APIs. If plugins are loaded, browser_plugin_info provides site-specific optimized actions for known sites. If authentication is required, the user must complete login in the browser window, then retry the same URL." + } ); // Wire server to logger so logs flow to agent via notifications/message. @@ -102,6 +108,14 @@ async function main() { server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools })); + // --- Prompts handlers --- + server.setRequestHandler(ListPromptsRequestSchema, async () => ({ prompts: PROMPTS })); + + server.setRequestHandler(GetPromptRequestSchema, async (request) => { + const { name, arguments: args } = request.params; + return getPromptMessages(name, args); + }); + server.setRequestHandler(CallToolRequestSchema, async (request, extra) => { const { name, arguments: args } = request.params; const safeArgs = args || {}; diff --git a/MCPBrowser/tests/tool-selection/tool-selection-tests.json b/MCPBrowser/tests/tool-selection/tool-selection-tests.json index c66a529..4b01ba9 100644 --- a/MCPBrowser/tests/tool-selection/tool-selection-tests.json +++ b/MCPBrowser/tests/tool-selection/tool-selection-tests.json @@ -167,6 +167,54 @@ "expectedTool": "browser_fetch_webpage", "expectedReason": "Verifying a deployment URL by loading it in a real browser", "priority": "medium" + }, + { + "id": "intent-check-email", + "userRequest": "Check my Gmail inbox and summarize the top 3 emails", + "expectedToolSequence": ["browser_fetch_webpage", "browser_take_screenshot"], + "expectedReason": "No dedicated email tool exists, but browser can access Gmail via user's logged-in session", + "mustNotUse": ["generic HTTP fetch"], + "priority": "critical" + }, + { + "id": "intent-calendar-events", + "userRequest": "What meetings do I have today on Google Calendar?", + "expectedTool": "browser_fetch_webpage", + "expectedReason": "Agent should navigate to calendar web app using the user's logged-in browser session", + "mustNotUse": ["generic HTTP fetch"], + "priority": "critical" + }, + { + "id": "intent-jira-tickets", + "userRequest": "Read my latest Jira tickets", + "expectedTool": "browser_fetch_webpage", + "expectedReason": "Agent should infer Jira URL and use browser to access the authenticated web app", + "mustNotUse": ["generic HTTP fetch"], + "priority": "high" + }, + { + "id": "intent-outlook-inbox", + "userRequest": "Do I have any new emails in Outlook?", + "expectedTool": "browser_fetch_webpage", + "expectedReason": "Browser can access Outlook web app with the user's existing session cookies", + "mustNotUse": ["generic HTTP fetch"], + "priority": "critical" + }, + { + "id": "intent-azure-dashboard", + "userRequest": "Show me the status of my Azure resources", + "expectedTool": "browser_fetch_webpage", + "expectedReason": "Agent should open Azure Portal dashboard using the authenticated browser session", + "mustNotUse": ["generic HTTP fetch"], + "priority": "high" + }, + { + "id": "intent-github-notifications", + "userRequest": "Check my GitHub notifications", + "expectedTool": "browser_fetch_webpage", + "expectedReason": "Browser can access GitHub with the user's logged-in session to read notifications", + "mustNotUse": ["generic HTTP fetch"], + "priority": "high" } ], "evaluationCriteria": { diff --git a/README.md b/README.md index d8acf0a..dcbd808 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Claude Desktop](https://img.shields.io/badge/Claude-Desktop-5865F2?logo=anthropic)](https://modelcontextprotocol.io/quickstart/user) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) -> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. +> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Claude, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. MCPBrowser combines private data access, untrusted content exposure, and external communication (the MCP "[lethal trifecta](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/)") — all tool annotations accurately declare these risks so MCP clients can enforce appropriate safety controls. See [MCPBrowser/docs/MCP_COMPLIANCE.md](MCPBrowser/docs/MCP_COMPLIANCE.md) for details. > 💡 **Why MCPBrowser over Puppeteer/Playwright MCP servers?** Puppeteer and Playwright are browser automation libraries — their MCP servers give agents raw, low-level browser commands. MCPBrowser uses Puppeteer under the hood and was built specifically for AI agents, adding an intelligence layer that handles the hard parts automatically. > @@ -412,7 +412,7 @@ Follow Windsurf MCP [documentation](https://docs.windsurf.com/windsurf/cascade/m ### `browser_fetch_webpage` -Fetches web pages using your Chrome/Edge browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content. +Fetches web pages using your Chrome/Edge/Brave browser. Handles authentication, CAPTCHA, SSO, anti-bot protection, and JavaScript-heavy sites. Opens the URL in a browser tab (reuses existing tab for same domain) and waits for the page to fully load before returning content. **Automatically detects SPAs** (React, Vue, Angular) and waits for JavaScript to render content. **Parameters:** - `url` (string, required) - The URL to fetch @@ -709,7 +709,7 @@ Environment variables for advanced setup: | Variable | Description | Default | |----------|-------------|---------| -| `CHROME_PATH` | Path to Chrome/Edge | Auto-detect | +| `CHROME_PATH` | Path to Chrome/Edge/Brave | Auto-detect | | `CHROME_USER_DATA_DIR` | Browser profile directory | `%LOCALAPPDATA%/ChromeAuthProfile` | | `CHROME_REMOTE_DEBUG_PORT` | DevTools port | `9222` | diff --git a/VSCodeExtension/README.md b/VSCodeExtension/README.md index b628c98..b91b917 100644 --- a/VSCodeExtension/README.md +++ b/VSCodeExtension/README.md @@ -1,13 +1,13 @@ # ✅ MCPBrowser (MCP Browser) -> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. +> ⚠️ **Security Notice:** MCPBrowser extracts webpage content and provides it to your AI agent (e.g., GitHub Copilot, Kiro, Antigravity), which then sends it to the LLM provider it uses (e.g., Anthropic, OpenAI, GitHub) for processing. Make sure you trust both your agent and the LLM provider — especially when accessing pages with sensitive or private data. MCPBrowser combines private data access, untrusted content exposure, and external communication (the MCP "[lethal trifecta](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/)") — all tool annotations accurately declare these risks so MCP clients can enforce appropriate safety controls. **MCP browser server extension for VS Code, Kiro, Antigravity, and compatible editors.** This browser-based MCP server extension enables in-browser web page fetching using your real Chrome, Edge, or Brave browser. Used when loading web pages via browser automation is preferred - handles login, SSO, CAPTCHA, and anti-crawler restrictions. Should be used when standard browser_fetch_webpage fails. ## Features - 🚀 **One-Click Setup**: Installs npm package and configures mcp.json automatically - complete setup with a single click -- 🔐 **Authentication Support**: Fetches web pages in your Chrome/Edge browser - authenticate once, reuse sessions automatically +- 🔐 **Authentication Support**: Fetches web pages in your Chrome/Edge/Brave browser - authenticate once, reuse sessions automatically - 🤖 **Bypass Anti-Crawler**: Fetch sites that block automated tools, including CAPTCHA and human verification ## Requirements @@ -50,7 +50,7 @@ Fetch the content from https://portal.azure.com/resources - use my authenticated Fetch https://github.com/private-repo/issues using MCPBrowser ``` -Your AI agent will use your Chrome/Edge browser session to fetch these pages, bypassing authentication and anti-crawler restrictions. +Your AI agent will use your Chrome/Edge/Brave browser session to fetch these pages, bypassing authentication and anti-crawler restrictions. ### Manual Commands diff --git a/VSCodeExtension/package-lock.json b/VSCodeExtension/package-lock.json index f14c771..dfb56ac 100644 --- a/VSCodeExtension/package-lock.json +++ b/VSCodeExtension/package-lock.json @@ -1,12 +1,12 @@ { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "license": "MIT", "dependencies": { "mcpbrowser": "^0.3.3" diff --git a/VSCodeExtension/package.json b/VSCodeExtension/package.json index db82675..73ddc35 100644 --- a/VSCodeExtension/package.json +++ b/VSCodeExtension/package.json @@ -2,7 +2,7 @@ "name": "mcpbrowser", "displayName": "MCPBrowser - Real Browser for AI Agents", "description": "MCP browser server extension for AI agents. Load and interact with any web page using a real browser with full JavaScript execution and login support. Handles authentication, SSO, CAPTCHA, and anti-crawler protection. Use whenever a page requires JavaScript rendering or user login. Works with GitHub Copilot, Kiro, Antigravity, and any MCP-compatible agent.", - "version": "0.3.52", + "version": "0.3.53", "publisher": "cherchyk", "icon": "icon.png", "engines": { diff --git a/VSCodeExtension/test/extension.test.js b/VSCodeExtension/test/extension.test.js index 437450f..5d3cd9f 100644 --- a/VSCodeExtension/test/extension.test.js +++ b/VSCodeExtension/test/extension.test.js @@ -541,9 +541,9 @@ describe('Extension Tests', () => { describe('getSafeVersion', () => { it('should return valid semver version from context', () => { const mockContext = { - extension: { packageJSON: { version: '0.3.52' } } + extension: { packageJSON: { version: '0.3.53' } } }; - assert.strictEqual(extension.getSafeVersion(mockContext), '0.3.52'); + assert.strictEqual(extension.getSafeVersion(mockContext), '0.3.53'); }); it('should return "latest" when version is missing', () => { @@ -596,13 +596,13 @@ describe('Extension Tests', () => { vscodeStub.window.showInformationMessage.resolves(); const mockContext = { - extension: { packageJSON: { version: '0.3.52' } } + extension: { packageJSON: { version: '0.3.53' } } }; const result = await extension.installMcpBrowser(mockContext); assert.strictEqual(result, true); - assert(execPromiseStub.calledWith('npm install -g mcpbrowser@0.3.52')); + assert(execPromiseStub.calledWith('npm install -g mcpbrowser@0.3.53')); }); it('should fall back to latest when context has invalid version', async () => { @@ -640,7 +640,7 @@ describe('Extension Tests', () => { execPromiseStub.resolves({ stdout: 'installed' }); const mockContext = { - extension: { packageJSON: { version: '0.3.52' } } + extension: { packageJSON: { version: '0.3.53' } } }; const result = await extension.installMcpBrowser(mockContext, { silent: true }); diff --git a/package-lock.json b/package-lock.json index ed675a8..dcd212c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "mcpbrowser-workspace", - "version": "0.3.52", + "version": "0.3.53", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "mcpbrowser-workspace", - "version": "0.3.52", + "version": "0.3.53", "license": "MIT", "devDependencies": { "rimraf": "^6.0.1" @@ -18,7 +18,7 @@ }, "MCPBrowser": { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "extraneous": true, "license": "MIT", "dependencies": { @@ -155,7 +155,7 @@ }, "VSCodeExtension": { "name": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", "extraneous": true, "license": "MIT", "dependencies": { diff --git a/package.json b/package.json index 8b47ff7..8d06202 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "mcpbrowser-workspace", - "version": "0.3.52", + "version": "0.3.53", "private": true, "description": "MCPBrowser workspace — MCP server and VS Code extension providing a real browser with full JavaScript execution and login/authentication support for AI agents", "scripts": { diff --git a/server.json b/server.json index af70de8..2e0d744 100644 --- a/server.json +++ b/server.json @@ -2,7 +2,8 @@ "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", "name": "io.github.cherchyk/mcpbrowser", "title": "MCP Browser", - "description": "Real Chrome/Edge browser for AI agents with JS execution, login, SSO, and anti-bot support", + "description": "Browser automation server — loads and interacts with web pages using the user's Chromium-based browser with full JavaScript execution, authentication, and anti-bot support.", + "websiteUrl": "https://github.com/cherchyk/MCPBrowser#readme", "icons": [ { "src": "https://raw.githubusercontent.com/cherchyk/MCPBrowser/main/VSCodeExtension/icon.png" @@ -10,14 +11,23 @@ ], "repository": { "url": "https://github.com/cherchyk/MCPBrowser", - "source": "github" + "source": "github", + "subfolder": "MCPBrowser" }, - "version": "0.3.52", + "version": "0.3.53", "packages": [ { "registryType": "npm", "identifier": "mcpbrowser", - "version": "0.3.52", + "version": "0.3.53", + "transport": { + "type": "stdio" + } + }, + { + "registryType": "mcpb", + "identifier": "https://github.com/cherchyk/MCPBrowser/releases/download/v0.3.53/mcpbrowser-0.3.53.mcpb", + "version": "0.3.53", "transport": { "type": "stdio" }