💬 Commit message: Update 2026-02-08 03:05:28, 17 files, 662 lines

📁 Files changed: 17
📝 Lines changed: 662

  • plugin.json
  • .mcp.json
  • README.md
  • analyze.md
  • compare.md
  • end.md
  • extract.md
  • fill.md
  • goto.md
  • restore.md
  • save.md
  • scrape.md
  • screenshot.md
  • start.md
  • package.json
  • browser.ts
  • mcp.ts
This commit is contained in:
Adam Ladachowski
2026-02-08 03:05:28 +01:00
parent 67b6dc3a79
commit ab03140803
17 changed files with 655 additions and 7 deletions
+22
View File
@@ -0,0 +1,22 @@
{
"name": "browse",
"description": "Browser automation and image processing tools for Claude Code using Playwright WebKit",
"version": "0.2.0",
"author": {
"name": "aladac",
"url": "https://github.com/aladac"
},
"repository": "https://github.com/aladac/claude-browse",
"license": "MIT",
"keywords": [
"browser",
"automation",
"playwright",
"webkit",
"screenshot",
"scraping",
"image-processing"
],
"mcpServers": "./.mcp.json",
"commands": "./commands"
}
+3 -2
View File
@@ -1,8 +1,9 @@
{
"mcpServers": {
"claude-browse": {
"command": "npx",
"args": ["claude-browse-mcp"]
"command": "node",
"args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp.js"],
"env": {}
}
}
}
+41 -3
View File
@@ -85,9 +85,45 @@ curl -X POST localhost:3000 -d '{"cmd":"wait","ms":2000}'
curl -X POST localhost:3000 -d '{"cmd":"close"}'
```
## MCP Server (Model Context Protocol)
## Claude Code Plugin (Recommended)
Use with Claude Code or any MCP-compatible client:
Install as a Claude Code plugin for the best integration:
```bash
# Install the plugin
claude plugin install https://github.com/aladac/claude-browse
# Or load temporarily during development
claude --plugin-dir /path/to/claude-browse
```
### Plugin Features
**Slash Commands:**
| Command | Description |
|---------|-------------|
| `/browse:start` | Start an interactive browsing session |
| `/browse:goto <url>` | Navigate to URL and describe findings |
| `/browse:screenshot` | Take a screenshot of the current page |
| `/browse:scrape <url>` | Scrape content from a webpage |
| `/browse:analyze` | Analyze current page content and structure |
| `/browse:extract [selector]` | Extract structured data from page |
| `/browse:fill [data]` | Help fill out forms |
| `/browse:compare [action]` | Compare page states before/after action |
**MCP Resources (@ mentions):**
| Resource | Description |
|----------|-------------|
| `@claude-browse:browser://state` | Browser state (URL, title, launched) |
| `@claude-browse:browser://html` | Page HTML (truncated to 10KB) |
| `@claude-browse:browser://html/full` | Complete page HTML |
| `@claude-browse:browser://screenshot` | Page screenshot as base64 PNG |
## MCP Server (Standalone)
Use with any MCP-compatible client:
```bash
# Run the MCP server
@@ -106,7 +142,9 @@ Add to Claude Code's MCP config (`~/.claude/settings.json`):
}
```
Available tools: `goto`, `click`, `type`, `query`, `screenshot`, `url`, `html`, `back`, `forward`, `reload`, `wait`, `eval`
**Available Tools:** `goto`, `click`, `type`, `query`, `screenshot`, `url`, `html`, `back`, `forward`, `reload`, `wait`, `eval`
**Image Processing Tools:** `favicon`, `convert`, `resize`, `crop`, `compress`, `thumbnail`
## Programmatic Usage
+13
View File
@@ -0,0 +1,13 @@
---
description: Analyze the current page content and structure
---
Use the analyze_page MCP prompt to analyze the current browser page.
If the browser is not launched, first navigate to a URL using the goto tool, then analyze the page structure, content, and interactive elements.
Provide:
1. A summary of the page purpose
2. Key interactive elements (forms, buttons, links)
3. Notable structure or patterns
4. Suggestions for useful actions
+13
View File
@@ -0,0 +1,13 @@
---
description: Compare page states with screenshots before and after an action
---
Compare page states:
1. Take an initial screenshot of the current page
2. Ask what action to perform (click, navigate, submit, etc.)
3. Perform the requested action
4. Take another screenshot
5. Describe the visual and structural differences
What action would you like to perform between screenshots? $ARGUMENTS
+12
View File
@@ -0,0 +1,12 @@
---
description: End the current browsing session and close the browser
---
End the current browsing session.
Use the `close` tool to close the browser. This will:
1. Close all browser pages
2. Clear the browser state
3. Free up system resources
The browser can be relaunched with any navigation command (goto, start, etc.)
+13
View File
@@ -0,0 +1,13 @@
---
description: Extract structured data from the current page
---
Extract structured data from the current browser page.
Selector (optional): $ARGUMENTS
Steps:
1. If a selector is provided, use the query tool to find matching elements
2. Analyze the page structure to identify data patterns
3. Extract and structure the data in a useful format (JSON, table, etc.)
4. Identify patterns that could help with similar pages
+13
View File
@@ -0,0 +1,13 @@
---
description: Help fill out a form on the current page
---
Fill form with data: $ARGUMENTS
Steps:
1. Use the query tool to find form inputs (input, textarea, select)
2. Identify required fields and their types
3. If data is provided, parse it and fill the matching fields using the type tool
4. Report what was filled and any issues encountered
If no data provided, describe the form fields found and ask what to fill in.
+12
View File
@@ -0,0 +1,12 @@
---
description: Navigate to a URL and describe what you find
---
Navigate to: $ARGUMENTS
Steps:
1. Use the goto tool to navigate to the URL
2. Take a screenshot to see the page
3. Describe what you see
4. Identify the main interactive elements
5. Suggest what actions might be useful
+13
View File
@@ -0,0 +1,13 @@
---
description: Restore a previously saved browsing session
---
Restore session from: $ARGUMENTS
Use the `session_restore` tool to restore a previously saved session:
- Navigate to the saved URL
- Restore all cookies
- Restore localStorage data
- Restore sessionStorage data
If no path is specified, restore from `session.json` in the current directory.
+15
View File
@@ -0,0 +1,15 @@
---
description: Save the current browsing session state to a file
---
Save the current session state to: $ARGUMENTS
Use the `session_save` tool to save:
- Current URL and page title
- All cookies
- localStorage data
- sessionStorage data
If no path is specified, save to `session.json` in the current directory.
This allows you to restore the session later with `/claude-browse:restore`.
+14
View File
@@ -0,0 +1,14 @@
---
description: Scrape content from a webpage
---
Scrape and extract content from: $ARGUMENTS
Steps:
1. Navigate to the URL using `goto`
2. Wait for the page to load
3. Query the page structure to understand the layout
4. Extract the relevant content using `query` and `eval` tools
5. Return the structured data
Focus on extracting meaningful content (text, links, data) rather than raw HTML.
+9
View File
@@ -0,0 +1,9 @@
---
description: Take a screenshot of the current page
---
Take a screenshot of the current browser page and analyze its contents.
If the browser hasn't been started yet, ask for a URL first, navigate there, then take the screenshot.
Use the `screenshot` tool with fullPage=$ARGUMENTS if specified (true/false), otherwise use default viewport.
+11
View File
@@ -0,0 +1,11 @@
---
description: Start an interactive browsing session
---
Start an interactive browser session. Use the browser tools to:
1. Navigate to URLs with `goto`
2. Take screenshots to see page content
3. Query elements with CSS selectors
4. Click and type to interact with pages
The browser is headless WebKit (Safari engine). Start by asking what URL to visit.
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@aladac/claude-browse",
"version": "0.1.2",
"version": "0.2.0",
"description": "Headless browser automation for Claude Code using Playwright WebKit",
"type": "module",
"main": "dist/index.js",
+10
View File
@@ -44,6 +44,16 @@ export class ClaudeBrowser {
return this.page;
}
/** Get the current page instance (for advanced usage) */
getPage(): Page | null {
return this.page;
}
/** Get the browser context (for advanced usage like cookies) */
getContext(): BrowserContext | null {
return this.context;
}
async goto(url: string): Promise<{ url: string; title: string }> {
const page = this.ensurePage();
await page.goto(url, { waitUntil: 'networkidle' });
+440 -1
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { McpServer, ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
import { ClaudeBrowser } from './browser.js';
@@ -8,6 +8,7 @@ import { type CommandLike, type ResultLike, stderrLogger as log } from './logger
const browser = new ClaudeBrowser({ headless: true, width: 1280, height: 800 });
let launched = false;
let currentScreenshotBuffer: Buffer | null = null;
async function ensureLaunched(): Promise<void> {
if (!launched) {
@@ -188,6 +189,124 @@ server.tool(
})
);
// Session management
server.tool(
'close',
'Close the browser and end the current session',
{},
withLogging('close', async () => {
if (launched) {
await browser.close();
launched = false;
}
return textResult(JSON.stringify({ ok: true, message: 'Browser closed' }));
})
);
server.tool(
'session_save',
'Save the current session state (URL, cookies, localStorage, sessionStorage) to a JSON file',
{
path: z.string().optional().default('session.json').describe('Path to save session file'),
},
withLogging('session_save', async ({ path }) => {
await ensureLaunched();
const { writeFile } = await import('node:fs/promises');
const { resolve } = await import('node:path');
const page = browser.getPage();
const context = browser.getContext();
if (!page || !context) {
return textResult(JSON.stringify({ ok: false, error: 'No active page' }));
}
const url = page.url();
const title = await page.title();
const cookies = await context.cookies();
// Get localStorage and sessionStorage (runs in browser context)
const storage = await page.evaluate(`({
localStorage: Object.fromEntries(
Array.from({ length: localStorage.length }, (_, i) => localStorage.key(i))
.filter(k => k !== null)
.map(k => [k, localStorage.getItem(k) || ''])
),
sessionStorage: Object.fromEntries(
Array.from({ length: sessionStorage.length }, (_, i) => sessionStorage.key(i))
.filter(k => k !== null)
.map(k => [k, sessionStorage.getItem(k) || ''])
),
})`) as { localStorage: Record<string, string>; sessionStorage: Record<string, string> };
const sessionData = {
url,
title,
cookies,
localStorage: storage.localStorage,
sessionStorage: storage.sessionStorage,
savedAt: new Date().toISOString(),
};
const resolvedPath = resolve(path);
await writeFile(resolvedPath, JSON.stringify(sessionData, null, 2));
return textResult(
JSON.stringify({ ok: true, path: resolvedPath, url, cookieCount: cookies.length })
);
})
);
server.tool(
'session_restore',
'Restore a previously saved session state from a JSON file',
{
path: z.string().optional().default('session.json').describe('Path to session file'),
},
withLogging('session_restore', async ({ path }) => {
await ensureLaunched();
const { readFile } = await import('node:fs/promises');
const { resolve } = await import('node:path');
const resolvedPath = resolve(path);
const data = JSON.parse(await readFile(resolvedPath, 'utf-8'));
const page = browser.getPage();
const context = browser.getContext();
if (!page || !context) {
return textResult(JSON.stringify({ ok: false, error: 'No active page' }));
}
// Restore cookies first
if (data.cookies?.length > 0) {
await context.addCookies(data.cookies);
}
// Navigate to saved URL
if (data.url) {
await page.goto(data.url, { waitUntil: 'networkidle' });
}
// Restore storage (runs in browser context)
const local = data.localStorage || {};
const session = data.sessionStorage || {};
await page.evaluate(
`((data) => {
for (const [k, v] of Object.entries(data.local)) localStorage.setItem(k, v);
for (const [k, v] of Object.entries(data.session)) sessionStorage.setItem(k, v);
})(${JSON.stringify({ local, session })})`
);
return textResult(
JSON.stringify({
ok: true,
url: data.url,
title: data.title,
cookiesRestored: data.cookies?.length || 0,
savedAt: data.savedAt,
})
);
})
);
// Image processing
server.tool(
'favicon',
@@ -290,7 +409,327 @@ server.tool(
})
);
// ============================================================================
// MCP Resources - Browser state accessible via @ mentions
// ============================================================================
// Resource: browser://state - Current browser state (URL, title, launched status)
server.resource(
'Browser State',
'browser://state',
{
description: 'Current browser state including URL, title, and status',
mimeType: 'application/json',
},
async () => {
if (!launched) {
return {
contents: [
{
uri: 'browser://state',
mimeType: 'application/json',
text: JSON.stringify({ launched: false, url: null, title: null }),
},
],
};
}
const state = await browser.getUrl();
return {
contents: [
{
uri: 'browser://state',
mimeType: 'application/json',
text: JSON.stringify({ launched: true, ...state }),
},
],
};
}
);
// Resource: browser://html - Current page HTML content
server.resource(
'Page HTML',
'browser://html',
{ description: 'HTML content of the current page (truncated to 10KB)', mimeType: 'text/html' },
async () => {
if (!launched) {
return {
contents: [
{
uri: 'browser://html',
mimeType: 'text/plain',
text: 'Browser not launched. Use goto tool first.',
},
],
};
}
const html = await browser.getHtml(false);
return {
contents: [
{
uri: 'browser://html',
mimeType: 'text/html',
text: html,
},
],
};
}
);
// Resource: browser://html/full - Full page HTML content
server.resource(
'Full Page HTML',
'browser://html/full',
{ description: 'Complete HTML content of the current page', mimeType: 'text/html' },
async () => {
if (!launched) {
return {
contents: [
{
uri: 'browser://html/full',
mimeType: 'text/plain',
text: 'Browser not launched. Use goto tool first.',
},
],
};
}
const html = await browser.getHtml(true);
return {
contents: [
{
uri: 'browser://html/full',
mimeType: 'text/html',
text: html,
},
],
};
}
);
// Resource: browser://screenshot - Current page screenshot (base64)
server.resource(
'Page Screenshot',
'browser://screenshot',
{ description: 'Screenshot of the current page as base64 PNG', mimeType: 'image/png' },
async () => {
if (!launched) {
return {
contents: [
{
uri: 'browser://screenshot',
mimeType: 'text/plain',
text: 'Browser not launched. Use goto tool first.',
},
],
};
}
const result = await browser.screenshot(undefined, false);
currentScreenshotBuffer = result.buffer || null;
return {
contents: [
{
uri: 'browser://screenshot',
mimeType: 'image/png',
blob: result.buffer?.toString('base64') || '',
},
],
};
}
);
// ============================================================================
// MCP Prompts - Common workflows accessible via / commands
// ============================================================================
// Prompt: Analyze current page
server.prompt('analyze_page', 'Analyze the current page content and structure', async () => {
if (!launched) {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: 'The browser is not launched yet. Please use the goto tool to navigate to a URL first, then I can analyze the page.',
},
},
],
};
}
const state = await browser.getUrl();
const html = await browser.getHtml(false);
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Analyze the following webpage:
URL: ${state.url}
Title: ${state.title}
HTML Content (truncated):
\`\`\`html
${html}
\`\`\`
Please provide:
1. A summary of the page purpose and content
2. Key interactive elements (forms, buttons, links)
3. Any notable structure or patterns
4. Suggestions for what actions might be useful`,
},
},
],
};
});
// Prompt: Extract data from page
server.prompt(
'extract_data',
'Extract structured data from the current page',
{ selector: z.string().optional().describe('CSS selector to focus extraction (optional)') },
async ({ selector }) => {
if (!launched) {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: 'The browser is not launched yet. Please use the goto tool to navigate to a URL first.',
},
},
],
};
}
const state = await browser.getUrl();
let elements: { tag: string; text: string; attributes: Record<string, string> }[] = [];
if (selector) {
elements = await browser.query(selector);
}
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Extract structured data from this webpage:
URL: ${state.url}
Title: ${state.title}
${selector ? `\nSelector: ${selector}\nMatched Elements: ${elements.length}\n\nElements:\n${JSON.stringify(elements, null, 2)}` : ''}
Please:
1. Use the query tool to find relevant data elements
2. Extract and structure the data in a useful format (JSON, table, etc.)
3. Identify patterns that could help with similar pages`,
},
},
],
};
}
);
// Prompt: Navigate and interact
server.prompt(
'navigate_to',
'Navigate to a URL and describe what you find',
{ url: z.string().url().describe('URL to navigate to') },
async ({ url }) => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Please navigate to ${url} and:
1. Use the goto tool to navigate there
2. Take a screenshot to see the page
3. Describe what you see
4. Identify the main interactive elements
5. Suggest what actions might be useful
Start by navigating to the URL.`,
},
},
],
};
}
);
// Prompt: Fill form
server.prompt(
'fill_form',
'Help fill out a form on the current page',
{ formData: z.string().optional().describe('JSON object with field names and values to fill') },
async ({ formData }) => {
if (!launched) {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: 'The browser is not launched yet. Please use the goto tool to navigate to a page with a form first.',
},
},
],
};
}
const state = await browser.getUrl();
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `Help fill out a form on this page:
URL: ${state.url}
Title: ${state.title}
${formData ? `\nData to fill: ${formData}` : ''}
Please:
1. Use the query tool to find form inputs (input, textarea, select)
2. Identify required fields and their types
3. Use the type tool to fill in each field
4. Report what was filled and any issues encountered`,
},
},
],
};
}
);
// Prompt: Screenshot comparison
server.prompt('compare_screenshots', 'Take screenshots and compare changes', async () => {
return {
messages: [
{
role: 'user',
content: {
type: 'text',
text: `I'll help you compare page states:
1. Take an initial screenshot
2. Perform some action (click, navigate, etc.)
3. Take another screenshot
4. Describe the differences
What action would you like me to perform between screenshots?`,
},
},
],
};
});
// ============================================================================
// Start server
// ============================================================================
async function main(): Promise<void> {
const transport = new StdioServerTransport();
await server.connect(transport);