💬 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:
@@ -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"
|
||||||
|
}
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"mcpServers": {
|
"mcpServers": {
|
||||||
"claude-browse": {
|
"claude-browse": {
|
||||||
"command": "npx",
|
"command": "node",
|
||||||
"args": ["claude-browse-mcp"]
|
"args": ["${CLAUDE_PLUGIN_ROOT}/dist/mcp.js"],
|
||||||
|
"env": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -85,9 +85,45 @@ curl -X POST localhost:3000 -d '{"cmd":"wait","ms":2000}'
|
|||||||
curl -X POST localhost:3000 -d '{"cmd":"close"}'
|
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
|
```bash
|
||||||
# Run the MCP server
|
# 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
|
## Programmatic Usage
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -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.)
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -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.
|
||||||
@@ -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`.
|
||||||
@@ -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.
|
||||||
@@ -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.
|
||||||
@@ -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
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@aladac/claude-browse",
|
"name": "@aladac/claude-browse",
|
||||||
"version": "0.1.2",
|
"version": "0.2.0",
|
||||||
"description": "Headless browser automation for Claude Code using Playwright WebKit",
|
"description": "Headless browser automation for Claude Code using Playwright WebKit",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
|
|||||||
@@ -44,6 +44,16 @@ export class ClaudeBrowser {
|
|||||||
return this.page;
|
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 }> {
|
async goto(url: string): Promise<{ url: string; title: string }> {
|
||||||
const page = this.ensurePage();
|
const page = this.ensurePage();
|
||||||
await page.goto(url, { waitUntil: 'networkidle' });
|
await page.goto(url, { waitUntil: 'networkidle' });
|
||||||
|
|||||||
+440
-1
@@ -1,5 +1,5 @@
|
|||||||
#!/usr/bin/env node
|
#!/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 { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import { ClaudeBrowser } from './browser.js';
|
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 });
|
const browser = new ClaudeBrowser({ headless: true, width: 1280, height: 800 });
|
||||||
let launched = false;
|
let launched = false;
|
||||||
|
let currentScreenshotBuffer: Buffer | null = null;
|
||||||
|
|
||||||
async function ensureLaunched(): Promise<void> {
|
async function ensureLaunched(): Promise<void> {
|
||||||
if (!launched) {
|
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
|
// Image processing
|
||||||
server.tool(
|
server.tool(
|
||||||
'favicon',
|
'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
|
// Start server
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
async function main(): Promise<void> {
|
async function main(): Promise<void> {
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
await server.connect(transport);
|
await server.connect(transport);
|
||||||
|
|||||||
Reference in New Issue
Block a user