pagebolt-mcp 1.2.0 → 1.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +32 -4
- package/package.json +1 -1
- package/src/index.mjs +535 -95
package/README.md
CHANGED
|
@@ -8,10 +8,6 @@ Take screenshots, generate PDFs, create OG images, inspect pages, and record dem
|
|
|
8
8
|
|
|
9
9
|
**Works with Claude Desktop, Cursor, Windsurf, Cline, and any MCP-compatible client.**
|
|
10
10
|
|
|
11
|
-
<p align="center">
|
|
12
|
-
<img src="https://pagebolt.dev/og-image-default.png" alt="PageBolt" width="600" />
|
|
13
|
-
</p>
|
|
14
|
-
|
|
15
11
|
---
|
|
16
12
|
|
|
17
13
|
## What It Does
|
|
@@ -212,6 +208,38 @@ Check your current API usage and plan limits.
|
|
|
212
208
|
|
|
213
209
|
---
|
|
214
210
|
|
|
211
|
+
## Prompts
|
|
212
|
+
|
|
213
|
+
Pre-built prompt templates for common workflows. In clients that support MCP prompts, these appear as slash commands.
|
|
214
|
+
|
|
215
|
+
### `/capture-page`
|
|
216
|
+
|
|
217
|
+
Capture a clean screenshot of any URL with sensible defaults (blocks banners, ads, chats, trackers).
|
|
218
|
+
|
|
219
|
+
**Arguments:** `url` (required), `device`, `dark_mode`, `full_page`
|
|
220
|
+
|
|
221
|
+
### `/record-demo`
|
|
222
|
+
|
|
223
|
+
Record a professional demo video. The agent inspects the page first to discover selectors, then builds a video recording sequence.
|
|
224
|
+
|
|
225
|
+
**Arguments:** `url` (required), `description` (required — what the demo should show), `pace`, `format`
|
|
226
|
+
|
|
227
|
+
### `/audit-page`
|
|
228
|
+
|
|
229
|
+
Inspect a page and get a structured analysis of its elements, forms, links, headings, and potential issues.
|
|
230
|
+
|
|
231
|
+
**Arguments:** `url` (required)
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Resources
|
|
236
|
+
|
|
237
|
+
### `pagebolt://api-docs`
|
|
238
|
+
|
|
239
|
+
The full PageBolt API reference as a text resource. AI agents that support MCP resources can read this for detailed parameter documentation beyond what fits in tool descriptions. Content is fetched from the live `llms-full.txt` endpoint.
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
215
243
|
## Configuration
|
|
216
244
|
|
|
217
245
|
| Environment Variable | Required | Default | Description |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagebolt-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.1",
|
|
4
4
|
"description": "MCP server for PageBolt — take screenshots, generate PDFs, create OG images, inspect pages, and record demo videos from AI coding assistants like Claude, Cursor, and Windsurf.",
|
|
5
5
|
"main": "src/index.mjs",
|
|
6
6
|
"module": "src/index.mjs",
|
package/src/index.mjs
CHANGED
|
@@ -1,29 +1,41 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* PageBolt MCP Server
|
|
4
|
+
* PageBolt MCP Server — COMPLETE API coverage
|
|
5
5
|
*
|
|
6
|
-
* A Model Context Protocol (MCP) server that exposes PageBolt's
|
|
7
|
-
*
|
|
8
|
-
*
|
|
6
|
+
* A Model Context Protocol (MCP) server that exposes 100% of PageBolt's
|
|
7
|
+
* API as tools for AI coding assistants (Claude, Cursor, Windsurf, Cline).
|
|
8
|
+
*
|
|
9
|
+
* Every parameter from every endpoint is exposed. Nothing is hidden.
|
|
9
10
|
*
|
|
10
11
|
* Get your free API key at https://pagebolt.dev
|
|
11
12
|
*
|
|
12
13
|
* Configuration (environment variables):
|
|
13
14
|
* PAGEBOLT_API_KEY — Required. Your PageBolt API key.
|
|
14
15
|
* PAGEBOLT_BASE_URL — Optional. Defaults to https://pagebolt.dev
|
|
15
|
-
*
|
|
16
|
-
* Usage:
|
|
17
|
-
* npx pagebolt-mcp
|
|
18
|
-
* # or after global install:
|
|
19
|
-
* pagebolt-mcp
|
|
20
16
|
*/
|
|
21
17
|
|
|
22
18
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
19
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
24
20
|
import { z } from 'zod';
|
|
25
21
|
import { writeFileSync } from 'node:fs';
|
|
26
|
-
import { resolve } from 'node:path';
|
|
22
|
+
import { resolve, relative, isAbsolute } from 'node:path';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Validate that a saveTo path stays within the current working directory.
|
|
26
|
+
* Prevents path traversal attacks (e.g., saveTo: "/etc/cron.d/malicious").
|
|
27
|
+
*/
|
|
28
|
+
function safePath(userPath, defaultName) {
|
|
29
|
+
const resolved = resolve(userPath || defaultName);
|
|
30
|
+
const rel = relative(process.cwd(), resolved);
|
|
31
|
+
if (isAbsolute(rel) || rel.startsWith('..')) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`saveTo path must be within the current working directory. ` +
|
|
34
|
+
`Got "${userPath}", which resolves outside CWD (${process.cwd()}).`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return resolved;
|
|
38
|
+
}
|
|
27
39
|
|
|
28
40
|
// ─── Configuration ───────────────────────────────────────────────
|
|
29
41
|
const API_KEY = process.env.PAGEBOLT_API_KEY;
|
|
@@ -45,7 +57,7 @@ async function callApi(endpoint, options = {}) {
|
|
|
45
57
|
const method = options.method || 'GET';
|
|
46
58
|
const headers = {
|
|
47
59
|
'x-api-key': API_KEY,
|
|
48
|
-
'user-agent': 'pagebolt-mcp/1.
|
|
60
|
+
'user-agent': 'pagebolt-mcp/1.5.1',
|
|
49
61
|
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
50
62
|
};
|
|
51
63
|
|
|
@@ -75,13 +87,137 @@ function imageMimeType(format) {
|
|
|
75
87
|
return map[format] || 'image/png';
|
|
76
88
|
}
|
|
77
89
|
|
|
90
|
+
// ─── Reusable Zod schemas ────────────────────────────────────────
|
|
91
|
+
// These are shared across multiple tools.
|
|
92
|
+
|
|
93
|
+
const cookieSchema = z.union([
|
|
94
|
+
z.string(),
|
|
95
|
+
z.object({
|
|
96
|
+
name: z.string(),
|
|
97
|
+
value: z.string(),
|
|
98
|
+
domain: z.string().optional(),
|
|
99
|
+
}),
|
|
100
|
+
]);
|
|
101
|
+
|
|
102
|
+
/** Screenshot style / theme options (frame, background, shadow, etc.) */
|
|
103
|
+
const styleSchema = z.object({
|
|
104
|
+
theme: z.enum([
|
|
105
|
+
'notion', 'paper', 'vercel', 'glass', 'ocean', 'sunset',
|
|
106
|
+
'linear', 'arc', 'glassDark', 'glassWarm', 'spotlight',
|
|
107
|
+
'neonBlue', 'neonPurple', 'neonGreen', 'lavender', 'ember', 'dots', 'grid',
|
|
108
|
+
]).optional().describe(
|
|
109
|
+
'One-click theme preset. Applies curated frame + background + shadow + padding. ' +
|
|
110
|
+
'Free themes: notion, paper, vercel, glass, ocean, sunset. ' +
|
|
111
|
+
'Paid (Starter+): linear, arc, glassDark, glassWarm, spotlight, neonBlue, neonPurple, neonGreen, lavender, ember, dots, grid. ' +
|
|
112
|
+
'Individual properties below override the theme defaults.'
|
|
113
|
+
),
|
|
114
|
+
frame: z.enum(['macos', 'windows', 'minimal', 'none']).optional().describe('Window chrome style. macos = traffic lights, windows = min/max/close, minimal = dots only, none = no frame.'),
|
|
115
|
+
frameTheme: z.enum(['light', 'dark', 'auto']).optional().describe('Frame color theme (default: auto)'),
|
|
116
|
+
background: z.enum([
|
|
117
|
+
'ocean', 'sunset', 'forest', 'midnight', 'aurora', 'lavender', 'peach', 'arctic', 'ember', 'slate', 'neon',
|
|
118
|
+
'glass', 'solid', 'spotlight', 'dots', 'grid', 'noise', 'none',
|
|
119
|
+
]).optional().describe(
|
|
120
|
+
'Background style. Gradients: ocean, sunset, forest, midnight, aurora, lavender, peach, arctic, ember, slate, neon. ' +
|
|
121
|
+
'Special: glass (frosted glass effect), solid, spotlight, dots, grid, noise. none = transparent.'
|
|
122
|
+
),
|
|
123
|
+
bgColor: z.string().optional().describe('Background color as hex (e.g. "#1e3a5f"). Used for solid backgrounds or as base for patterns.'),
|
|
124
|
+
bgColors: z.array(z.string()).optional().describe('Array of 2 hex colors for custom gradient (e.g. ["#1e3a5f", "#7c3aed"])'),
|
|
125
|
+
padding: z.number().int().min(0).max(120).optional().describe('Padding around screenshot in pixels (default: 40)'),
|
|
126
|
+
borderRadius: z.number().int().min(0).max(40).optional().describe('Corner radius in pixels (default: 12)'),
|
|
127
|
+
shadow: z.enum(['none', 'xs', 'sm', 'md', 'lg', 'xl', '2xl']).optional().describe('Drop shadow size (default: md)'),
|
|
128
|
+
}).optional().describe(
|
|
129
|
+
'Screenshot styling options — add a macOS/Windows frame, gradient/glass background, shadow, and rounded corners. ' +
|
|
130
|
+
'Use the "theme" shortcut for one-click presets, or customize individual properties.'
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
// ─── Server Instructions ────────────────────────────────────────
|
|
134
|
+
const SERVER_INSTRUCTIONS = `
|
|
135
|
+
PageBolt gives you 8 tools for web capture and browser automation. All tools use your API key automatically.
|
|
136
|
+
|
|
137
|
+
## Tools Overview
|
|
138
|
+
|
|
139
|
+
| Tool | What it does | Cost |
|
|
140
|
+
|------|-------------|------|
|
|
141
|
+
| take_screenshot | Capture a URL, HTML, or Markdown as PNG/JPEG/WebP | 1 request |
|
|
142
|
+
| generate_pdf | Convert a URL or HTML to PDF, saves to disk | 1 request |
|
|
143
|
+
| create_og_image | Generate social card images from templates or custom HTML | 1 request |
|
|
144
|
+
| run_sequence | Multi-step browser automation with multiple screenshot/PDF outputs | 1 request per output |
|
|
145
|
+
| record_video | Record browser automation as MP4/WebM/GIF with cursor effects | 3 requests |
|
|
146
|
+
| inspect_page | Get structured map of page elements with CSS selectors | 1 request |
|
|
147
|
+
| list_devices | List 25+ device presets (iPhone, iPad, MacBook, etc.) | 0 (free) |
|
|
148
|
+
| check_usage | Check current API usage and plan limits | 0 (free) |
|
|
149
|
+
|
|
150
|
+
## Key Workflow: Inspect Before You Interact
|
|
151
|
+
|
|
152
|
+
When building sequences or videos, ALWAYS use inspect_page first to discover reliable CSS selectors:
|
|
153
|
+
|
|
154
|
+
1. inspect_page — returns buttons, inputs, forms, links, headings with unique selectors
|
|
155
|
+
2. run_sequence or record_video — use the selectors from step 1
|
|
156
|
+
|
|
157
|
+
This avoids guessing selectors like "#submit" when the actual element is "#submitBtn".
|
|
158
|
+
|
|
159
|
+
## Styling Screenshots
|
|
160
|
+
|
|
161
|
+
Use the "style" parameter on take_screenshot for beautiful styled captures:
|
|
162
|
+
- Quick: style.theme = "glass" or "ocean" or "linear" for one-click presets
|
|
163
|
+
- Custom: style.frame = "macos", style.background = "glass", style.shadow = "lg"
|
|
164
|
+
|
|
165
|
+
## Video Recording Features
|
|
166
|
+
|
|
167
|
+
record_video supports polished video output:
|
|
168
|
+
- frame: { enabled: true, style: "macos" } — browser chrome around the video
|
|
169
|
+
- background: { enabled: true, type: "gradient", gradient: "ocean" } — gradient/glass background with padding
|
|
170
|
+
- cursor: { style: "classic", persist: true } — always-visible cursor
|
|
171
|
+
- Per-step zoom: add zoom: { enabled: true } on click steps
|
|
172
|
+
- **Step notes (IMPORTANT)**: Add a "note" field to EVERY action step for guided-tour-style tooltip annotations. Notes appear as beautiful styled tooltips near the element being interacted with. Example: { action: "click", selector: "#btn", note: "Click here to open settings" }. The only steps that should NOT have notes are wait/wait_for pauses.
|
|
173
|
+
- **Live wait steps**: Add live: true to wait steps to capture animated content (transitions, loading spinners) instead of freezing the last frame.
|
|
174
|
+
- **Variables**: Pass variables: { "base_url": "https://example.com" } and use {{base_url}} in step URLs/values for reusable recordings.
|
|
175
|
+
|
|
176
|
+
## Common Parameters (available on most tools)
|
|
177
|
+
|
|
178
|
+
- blockBanners: true — hides cookie consent banners (GDPR popups, OneTrust, CookieBot, etc.)
|
|
179
|
+
- blockAds: true — blocks advertisements
|
|
180
|
+
- blockChats: true — blocks live chat widgets (Intercom, Crisp, Drift)
|
|
181
|
+
- blockTrackers: true — blocks analytics trackers (GA, Hotjar, Segment)
|
|
182
|
+
- darkMode: true — emulates dark color scheme (prefers-color-scheme: dark)
|
|
183
|
+
- viewportDevice: "iphone_14_pro" — emulates a specific device (use list_devices to see all 25+)
|
|
184
|
+
|
|
185
|
+
Use blockBanners on almost every request to get clean captures. Combine blockAds + blockChats + blockTrackers for completely clean screenshots.
|
|
186
|
+
|
|
187
|
+
## Tips
|
|
188
|
+
|
|
189
|
+
- For screenshots of pages behind auth: use cookies, headers, or authorization params
|
|
190
|
+
- extractMetadata: true on take_screenshot returns title, description, OG tags, HTTP status
|
|
191
|
+
- response_type: "json" returns base64 data instead of binary (useful for programmatic use)
|
|
192
|
+
- record_video pace presets: "fast" (0.5x), "normal" (1x), "slow" (2x), "dramatic" (3x), "cinematic" (4.5x)
|
|
193
|
+
- record_video cursor styles: "highlight", "circle", "spotlight", "dot", "classic"
|
|
194
|
+
- run_sequence requires at least 1 screenshot or pdf step as output
|
|
195
|
+
- record_video does NOT allow screenshot/pdf steps — the whole sequence IS the video
|
|
196
|
+
- Max 2 evaluate (JavaScript) steps per sequence/video
|
|
197
|
+
- fullPage: true on screenshots captures the entire scrollable page
|
|
198
|
+
- fullPageScroll: true triggers lazy-loaded images before capture
|
|
199
|
+
|
|
200
|
+
## Cost Summary
|
|
201
|
+
|
|
202
|
+
| Action | Cost |
|
|
203
|
+
|--------|------|
|
|
204
|
+
| Screenshot, PDF, OG image, Inspect | 1 request each |
|
|
205
|
+
| Sequence | 1 request per output (screenshot/pdf) |
|
|
206
|
+
| Video recording | 3 requests flat |
|
|
207
|
+
| list_devices, check_usage | Free |
|
|
208
|
+
`.trim();
|
|
209
|
+
|
|
78
210
|
// ─── Create MCP Server ──────────────────────────────────────────
|
|
79
211
|
function createConfiguredServer() {
|
|
80
212
|
const srv = new McpServer({
|
|
81
213
|
name: 'pagebolt',
|
|
82
|
-
version: '1.
|
|
214
|
+
version: '1.5.0',
|
|
215
|
+
}, {
|
|
216
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
83
217
|
});
|
|
84
218
|
registerTools(srv);
|
|
219
|
+
registerPrompts(srv);
|
|
220
|
+
registerResources(srv);
|
|
85
221
|
return srv;
|
|
86
222
|
}
|
|
87
223
|
|
|
@@ -89,10 +225,12 @@ const server = createConfiguredServer();
|
|
|
89
225
|
|
|
90
226
|
function registerTools(server) {
|
|
91
227
|
|
|
92
|
-
//
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
229
|
+
// Tool: take_screenshot — COMPLETE coverage
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
93
231
|
server.tool(
|
|
94
232
|
'take_screenshot',
|
|
95
|
-
'Capture a screenshot of a URL, HTML, or Markdown content.
|
|
233
|
+
'Capture a screenshot of a URL, HTML, or Markdown content. Supports device emulation, ad/chat/tracker blocking, metadata extraction, geolocation, timezone, styling (macOS/Windows frames, gradient/glass backgrounds, shadows), and more. Returns an image (PNG, JPEG, or WebP).',
|
|
96
234
|
{
|
|
97
235
|
// ── Source ──
|
|
98
236
|
url: z.string().url().optional().describe('URL to capture (required if no html/markdown)'),
|
|
@@ -104,6 +242,7 @@ server.tool(
|
|
|
104
242
|
viewportDevice: z.string().optional().describe('Device preset for viewport emulation (e.g. "iphone_14_pro", "macbook_pro_14"). Use list_devices to see all presets.'),
|
|
105
243
|
viewportMobile: z.boolean().optional().describe('Enable mobile meta viewport emulation'),
|
|
106
244
|
viewportHasTouch: z.boolean().optional().describe('Enable touch event emulation'),
|
|
245
|
+
viewportLandscape: z.boolean().optional().describe('Landscape orientation'),
|
|
107
246
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio, use 2 for retina (default: 1)'),
|
|
108
247
|
// ── Output format ──
|
|
109
248
|
format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Image format (default: png)'),
|
|
@@ -112,6 +251,8 @@ server.tool(
|
|
|
112
251
|
// ── Capture region ──
|
|
113
252
|
fullPage: z.boolean().optional().describe('Capture the full scrollable page (default: false)'),
|
|
114
253
|
fullPageScroll: z.boolean().optional().describe('Auto-scroll page before capture to trigger lazy-loaded images'),
|
|
254
|
+
fullPageScrollDelay: z.number().int().min(0).max(2000).optional().describe('Delay between scroll steps in ms (default: 400)'),
|
|
255
|
+
fullPageScrollBy: z.number().int().optional().describe('Pixels to scroll per step (default: viewport height)'),
|
|
115
256
|
fullPageMaxHeight: z.number().int().optional().describe('Maximum pixel height cap for full-page captures'),
|
|
116
257
|
selector: z.string().optional().describe('CSS selector to capture a specific element'),
|
|
117
258
|
clip: z.object({
|
|
@@ -121,9 +262,10 @@ server.tool(
|
|
|
121
262
|
height: z.number(),
|
|
122
263
|
}).optional().describe('Crop region { x, y, width, height } in pixels'),
|
|
123
264
|
// ── Timing ──
|
|
124
|
-
delay: z.number().int().min(0).max(
|
|
265
|
+
delay: z.number().int().min(0).max(30000).optional().describe('Milliseconds to wait before capture (default: 0)'),
|
|
125
266
|
waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().describe('When to consider navigation finished (default: networkidle2)'),
|
|
126
267
|
waitForSelector: z.string().optional().describe('Wait for this CSS selector to appear before capturing'),
|
|
268
|
+
navigationTimeout: z.number().int().min(0).max(30000).optional().describe('Navigation timeout in ms (default: 25000)'),
|
|
127
269
|
// ── Emulation ──
|
|
128
270
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
129
271
|
reducedMotion: z.boolean().optional().describe('Emulate prefers-reduced-motion to disable animations'),
|
|
@@ -136,28 +278,26 @@ server.tool(
|
|
|
136
278
|
}).optional().describe('Emulate geolocation { latitude, longitude, accuracy? }'),
|
|
137
279
|
userAgent: z.string().optional().describe('Override the browser User-Agent string'),
|
|
138
280
|
// ── Auth & headers ──
|
|
139
|
-
cookies: z.array(
|
|
140
|
-
z.union([
|
|
141
|
-
z.string(),
|
|
142
|
-
z.object({
|
|
143
|
-
name: z.string(),
|
|
144
|
-
value: z.string(),
|
|
145
|
-
domain: z.string().optional(),
|
|
146
|
-
}),
|
|
147
|
-
])
|
|
148
|
-
).optional().describe('Cookies to set — array of "name=value" strings or { name, value, domain? } objects'),
|
|
281
|
+
cookies: z.array(cookieSchema).optional().describe('Cookies to set — array of "name=value" strings or { name, value, domain? } objects'),
|
|
149
282
|
headers: z.record(z.string(), z.string()).optional().describe('Extra HTTP headers to send with the request'),
|
|
150
283
|
authorization: z.string().optional().describe('Authorization header value (e.g. "Bearer <token>")'),
|
|
151
284
|
bypassCSP: z.boolean().optional().describe('Bypass Content-Security-Policy on the page'),
|
|
152
285
|
// ── Content manipulation ──
|
|
153
286
|
hideSelectors: z.array(z.string()).optional().describe('Array of CSS selectors to hide before capture'),
|
|
154
287
|
click: z.string().optional().describe('CSS selector to click before capturing the screenshot'),
|
|
288
|
+
injectCss: z.string().optional().describe('Custom CSS to inject before capturing (max 50KB)'),
|
|
289
|
+
injectJs: z.string().optional().describe('Custom JavaScript to execute before capturing (max 50KB)'),
|
|
290
|
+
// ── Blocking ──
|
|
155
291
|
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: false)'),
|
|
156
292
|
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
157
293
|
blockChats: z.boolean().optional().describe('Block live chat widgets on the page'),
|
|
158
294
|
blockTrackers: z.boolean().optional().describe('Block tracking scripts on the page'),
|
|
159
|
-
|
|
295
|
+
blockRequests: z.array(z.string()).optional().describe('URL patterns to block (array of strings)'),
|
|
296
|
+
blockResources: z.array(z.string()).optional().describe('Resource types to block (e.g. ["image", "font"])'),
|
|
297
|
+
// ── Metadata ──
|
|
160
298
|
extractMetadata: z.boolean().optional().describe('Extract page metadata (title, description, OG tags) alongside the screenshot'),
|
|
299
|
+
// ── Styling ──
|
|
300
|
+
style: styleSchema,
|
|
161
301
|
},
|
|
162
302
|
async (params) => {
|
|
163
303
|
if (!params.url && !params.html && !params.markdown) {
|
|
@@ -172,35 +312,57 @@ server.tool(
|
|
|
172
312
|
const data = await res.json();
|
|
173
313
|
const format = params.format || 'png';
|
|
174
314
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
315
|
+
const content = [
|
|
316
|
+
{
|
|
317
|
+
type: 'image',
|
|
318
|
+
data: data.data,
|
|
319
|
+
mimeType: imageMimeType(format),
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
type: 'text',
|
|
323
|
+
text: `Screenshot captured successfully. Format: ${format}, Size: ${data.size_bytes} bytes, Duration: ${data.duration_ms}ms`,
|
|
324
|
+
},
|
|
325
|
+
];
|
|
326
|
+
|
|
327
|
+
// Include metadata if extracted
|
|
328
|
+
if (data.metadata) {
|
|
329
|
+
content.push({
|
|
330
|
+
type: 'text',
|
|
331
|
+
text: `Metadata:\n${JSON.stringify(data.metadata, null, 2)}`,
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return { content };
|
|
188
336
|
}
|
|
189
337
|
);
|
|
190
338
|
|
|
191
|
-
//
|
|
339
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
340
|
+
// Tool: generate_pdf — COMPLETE coverage
|
|
341
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
192
342
|
server.tool(
|
|
193
343
|
'generate_pdf',
|
|
194
|
-
'Generate a PDF from a URL or HTML content. Saves the PDF to disk and returns the file path.',
|
|
344
|
+
'Generate a PDF from a URL or HTML content. Supports custom margins, headers/footers, page ranges, and scaling. Saves the PDF to disk and returns the file path.',
|
|
195
345
|
{
|
|
196
346
|
url: z.string().url().optional().describe('URL to render as PDF (required if no html)'),
|
|
197
347
|
html: z.string().optional().describe('Raw HTML to render as PDF (required if no url)'),
|
|
198
|
-
format: z.string().optional().describe('Paper format: A4, Letter, Legal, Tabloid (default: A4)'),
|
|
348
|
+
format: z.string().optional().describe('Paper format: A4, Letter, Legal, Tabloid, A3, A5 (default: A4)'),
|
|
199
349
|
landscape: z.boolean().optional().describe('Landscape orientation (default: false)'),
|
|
200
350
|
printBackground: z.boolean().optional().describe('Include CSS backgrounds (default: true)'),
|
|
201
|
-
margin: z.
|
|
351
|
+
margin: z.union([
|
|
352
|
+
z.string(),
|
|
353
|
+
z.object({
|
|
354
|
+
top: z.string().optional(),
|
|
355
|
+
right: z.string().optional(),
|
|
356
|
+
bottom: z.string().optional(),
|
|
357
|
+
left: z.string().optional(),
|
|
358
|
+
}),
|
|
359
|
+
]).optional().describe('CSS margin — string for all sides (e.g. "1cm") or object { top, right, bottom, left }'),
|
|
202
360
|
scale: z.number().min(0.1).max(2).optional().describe('Rendering scale 0.1-2 (default: 1)'),
|
|
361
|
+
width: z.string().optional().describe('Page width (overrides format) — CSS value like "8.5in"'),
|
|
203
362
|
pageRanges: z.string().optional().describe('Page ranges to include, e.g. "1-5, 8"'),
|
|
363
|
+
headerTemplate: z.string().optional().describe('HTML template for page header (uses Chromium templating)'),
|
|
364
|
+
footerTemplate: z.string().optional().describe('HTML template for page footer'),
|
|
365
|
+
displayHeaderFooter: z.boolean().optional().describe('Show header and footer (default: false)'),
|
|
204
366
|
delay: z.number().int().min(0).max(10000).optional().describe('Milliseconds to wait before rendering (default: 0)'),
|
|
205
367
|
saveTo: z.string().optional().describe('Output file path (default: ./output.pdf)'),
|
|
206
368
|
},
|
|
@@ -216,18 +378,37 @@ server.tool(
|
|
|
216
378
|
});
|
|
217
379
|
|
|
218
380
|
const data = await res.json();
|
|
219
|
-
const outputPath = resolve(saveTo || './output.pdf');
|
|
220
381
|
|
|
221
|
-
//
|
|
222
|
-
|
|
223
|
-
|
|
382
|
+
// Best-effort save to disk (may fail in hosted/sandboxed environments)
|
|
383
|
+
let savedPath = null;
|
|
384
|
+
try {
|
|
385
|
+
const outputPath = safePath(saveTo, './output.pdf');
|
|
386
|
+
const buffer = Buffer.from(data.data, 'base64');
|
|
387
|
+
writeFileSync(outputPath, buffer);
|
|
388
|
+
savedPath = outputPath;
|
|
389
|
+
} catch (_diskErr) {
|
|
390
|
+
// Disk write failed (e.g. hosted environment, read-only FS) — data is
|
|
391
|
+
// still returned as an embedded resource below, so the client gets it.
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const fileNote = savedPath
|
|
395
|
+
? ` File: ${savedPath}`
|
|
396
|
+
: ` File: (not saved to disk — use the embedded resource data below)`;
|
|
224
397
|
|
|
225
398
|
return {
|
|
226
399
|
content: [
|
|
400
|
+
{
|
|
401
|
+
type: 'resource',
|
|
402
|
+
resource: {
|
|
403
|
+
uri: 'pagebolt://pdf/output.pdf',
|
|
404
|
+
mimeType: 'application/pdf',
|
|
405
|
+
blob: data.data, // base64-encoded PDF — always delivered to client
|
|
406
|
+
},
|
|
407
|
+
},
|
|
227
408
|
{
|
|
228
409
|
type: 'text',
|
|
229
410
|
text: `PDF generated successfully.\n` +
|
|
230
|
-
|
|
411
|
+
`${fileNote}\n` +
|
|
231
412
|
` Size: ${data.size_bytes} bytes\n` +
|
|
232
413
|
` Duration: ${data.duration_ms}ms`,
|
|
233
414
|
},
|
|
@@ -236,13 +417,15 @@ server.tool(
|
|
|
236
417
|
}
|
|
237
418
|
);
|
|
238
419
|
|
|
239
|
-
//
|
|
420
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
421
|
+
// Tool: create_og_image — COMPLETE coverage
|
|
422
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
240
423
|
server.tool(
|
|
241
424
|
'create_og_image',
|
|
242
425
|
'Generate an Open Graph / social card image. Returns an image using built-in templates or custom HTML.',
|
|
243
426
|
{
|
|
244
427
|
template: z.enum(['default', 'minimal', 'gradient']).optional().describe('Built-in template name (default: "default")'),
|
|
245
|
-
html: z.string().optional().describe('Custom HTML template (overrides template parameter)'),
|
|
428
|
+
html: z.string().optional().describe('Custom HTML template (overrides template parameter, Growth plan+)'),
|
|
246
429
|
title: z.string().optional().describe('Main title text (default: "Your Title Here")'),
|
|
247
430
|
subtitle: z.string().optional().describe('Subtitle text'),
|
|
248
431
|
logo: z.string().optional().describe('Logo image URL'),
|
|
@@ -279,7 +462,9 @@ server.tool(
|
|
|
279
462
|
}
|
|
280
463
|
);
|
|
281
464
|
|
|
282
|
-
//
|
|
465
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
466
|
+
// Tool: run_sequence — COMPLETE coverage
|
|
467
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
283
468
|
server.tool(
|
|
284
469
|
'run_sequence',
|
|
285
470
|
'Execute a multi-step browser automation sequence. Navigate pages, interact with elements (click, fill, select), and capture multiple screenshots/PDFs in a single browser session. Each output counts as 1 API request.',
|
|
@@ -287,7 +472,7 @@ server.tool(
|
|
|
287
472
|
steps: z.array(
|
|
288
473
|
z.object({
|
|
289
474
|
action: z.enum([
|
|
290
|
-
'navigate', 'click', 'fill', 'select', 'hover',
|
|
475
|
+
'navigate', 'click', 'dblclick', 'fill', 'select', 'hover',
|
|
291
476
|
'scroll', 'wait', 'wait_for', 'evaluate',
|
|
292
477
|
'screenshot', 'pdf',
|
|
293
478
|
]).describe('The action to perform'),
|
|
@@ -302,11 +487,16 @@ server.tool(
|
|
|
302
487
|
name: z.string().optional().describe('Name for the output (for screenshot/pdf actions)'),
|
|
303
488
|
format: z.string().optional().describe('Image format: png, jpeg, webp (screenshot) or A4, Letter (pdf)'),
|
|
304
489
|
fullPage: z.boolean().optional().describe('Capture full scrollable page (for screenshot action)'),
|
|
490
|
+
fullPageScroll: z.boolean().optional().describe('Auto-scroll for lazy images (for screenshot action)'),
|
|
305
491
|
quality: z.number().int().min(1).max(100).optional().describe('JPEG/WebP quality (for screenshot action)'),
|
|
492
|
+
selector: z.string().optional().describe('Element selector (for screenshot action)'),
|
|
493
|
+
omitBackground: z.boolean().optional().describe('Transparent background (for screenshot action)'),
|
|
494
|
+
delay: z.number().int().min(0).max(10000).optional().describe('Pre-capture delay in ms (for screenshot action)'),
|
|
306
495
|
landscape: z.boolean().optional().describe('Landscape orientation (for pdf action)'),
|
|
307
496
|
printBackground: z.boolean().optional().describe('Include CSS backgrounds (for pdf action)'),
|
|
308
497
|
margin: z.string().optional().describe('CSS margin for all sides (for pdf action)'),
|
|
309
498
|
scale: z.number().min(0.1).max(2).optional().describe('Rendering scale (for pdf action)'),
|
|
499
|
+
style: styleSchema,
|
|
310
500
|
})
|
|
311
501
|
).min(1).max(20).describe('Array of steps to execute in order. Must include at least one screenshot or pdf step. Max 20 steps, max 5 outputs.'),
|
|
312
502
|
viewport: z.object({
|
|
@@ -315,6 +505,9 @@ server.tool(
|
|
|
315
505
|
}).optional().describe('Browser viewport size'),
|
|
316
506
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
317
507
|
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: false)'),
|
|
508
|
+
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
509
|
+
blockChats: z.boolean().optional().describe('Block live chat widgets'),
|
|
510
|
+
blockTrackers: z.boolean().optional().describe('Block tracking scripts'),
|
|
318
511
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
319
512
|
},
|
|
320
513
|
async (params) => {
|
|
@@ -365,15 +558,17 @@ server.tool(
|
|
|
365
558
|
}
|
|
366
559
|
);
|
|
367
560
|
|
|
368
|
-
//
|
|
561
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
562
|
+
// Tool: record_video — COMPLETE coverage
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
369
564
|
server.tool(
|
|
370
565
|
'record_video',
|
|
371
|
-
'Record a professional demo video of a multi-step browser automation sequence. Produces MP4/WebM/GIF with
|
|
566
|
+
'Record a professional demo video of a multi-step browser automation sequence. Produces MP4/WebM/GIF with cursor highlighting, click effects, smooth movement, per-step zoom, step notes, browser frame (macOS/Windows), gradient/glass backgrounds, and more. Costs 3 API requests. Saves to disk.',
|
|
372
567
|
{
|
|
373
568
|
steps: z.array(
|
|
374
569
|
z.object({
|
|
375
570
|
action: z.enum([
|
|
376
|
-
'navigate', 'click', 'fill', 'select', 'hover',
|
|
571
|
+
'navigate', 'click', 'dblclick', 'fill', 'select', 'hover',
|
|
377
572
|
'scroll', 'wait', 'wait_for', 'evaluate',
|
|
378
573
|
]).describe('The action to perform (no screenshot/pdf — the whole sequence is recorded as video)'),
|
|
379
574
|
url: z.string().url().optional().describe('URL to navigate to (for navigate action)'),
|
|
@@ -384,6 +579,12 @@ server.tool(
|
|
|
384
579
|
x: z.number().optional().describe('Horizontal scroll position'),
|
|
385
580
|
y: z.number().optional().describe('Vertical scroll position'),
|
|
386
581
|
script: z.string().max(5000).optional().describe('JavaScript to execute in page context (for evaluate action)'),
|
|
582
|
+
note: z.string().max(200).optional().describe('Tooltip annotation text shown during this step (max 200 chars)'),
|
|
583
|
+
live: z.boolean().optional().describe('For wait steps: true captures animated content in real-time, false freezes a single frame (default: false)'),
|
|
584
|
+
zoom: z.object({
|
|
585
|
+
enabled: z.boolean().optional().describe('Enable zoom on this step (default: false)'),
|
|
586
|
+
level: z.number().min(1.2).max(4).optional().describe('Zoom magnification (inherits from global zoom.level if not set)'),
|
|
587
|
+
}).optional().describe('Per-step zoom override (for click/dblclick steps). Overrides global zoom settings.'),
|
|
387
588
|
})
|
|
388
589
|
).min(1).max(50).describe('Array of steps to execute and record. Max steps depends on plan (10-50).'),
|
|
389
590
|
viewport: z.object({
|
|
@@ -392,31 +593,63 @@ server.tool(
|
|
|
392
593
|
}).optional().describe('Browser viewport size'),
|
|
393
594
|
format: z.enum(['mp4', 'webm', 'gif']).optional().describe('Video format (default: mp4). webm/gif require Starter+ plan.'),
|
|
394
595
|
framerate: z.number().int().optional().describe('Frames per second: 24, 30, or 60 (default: 30)'),
|
|
596
|
+
// ── Cursor ──
|
|
395
597
|
cursor: z.object({
|
|
396
598
|
visible: z.boolean().optional().describe('Show cursor overlay (default: true)'),
|
|
397
|
-
style: z.enum(['highlight', 'circle', 'spotlight', 'dot']).optional().describe('Cursor style (default: highlight)'),
|
|
599
|
+
style: z.enum(['highlight', 'circle', 'spotlight', 'dot', 'classic']).optional().describe('Cursor style (default: highlight). classic = natural arrow cursor.'),
|
|
398
600
|
color: z.string().optional().describe('Cursor color as hex, e.g. "#3B82F6" (default: blue)'),
|
|
399
601
|
size: z.number().int().min(8).max(60).optional().describe('Cursor size in pixels (default: 20)'),
|
|
400
602
|
smoothing: z.boolean().optional().describe('Smooth animated cursor movement (default: true)'),
|
|
603
|
+
opacity: z.number().min(0.1).max(1.0).optional().describe('Cursor opacity 0.1-1.0 (default: 1.0)'),
|
|
604
|
+
persist: z.boolean().optional().describe('Keep cursor visible between actions, not just during them (default: false)'),
|
|
401
605
|
}).optional().describe('Cursor appearance settings'),
|
|
606
|
+
// ── Zoom (global defaults, per-step overrides available) ──
|
|
402
607
|
zoom: z.object({
|
|
403
|
-
enabled: z.boolean().optional().describe('
|
|
404
|
-
level: z.number().min(1.
|
|
405
|
-
duration: z.number().int().min(
|
|
406
|
-
|
|
608
|
+
enabled: z.boolean().optional().describe('Enable auto-zoom on clicks (default: false — use per-step zoom instead)'),
|
|
609
|
+
level: z.number().min(1.2).max(4).optional().describe('Default zoom magnification (default: 1.5)'),
|
|
610
|
+
duration: z.number().int().min(400).max(3000).optional().describe('Zoom animation duration in ms (default: 1200)'),
|
|
611
|
+
easing: z.enum(['ease-in-out', 'linear', 'ease']).optional().describe('Zoom animation easing (default: ease-in-out)'),
|
|
612
|
+
}).optional().describe('Global zoom settings. Per-step zoom on click/dblclick steps overrides these.'),
|
|
407
613
|
autoZoom: z.boolean().optional().describe('Shorthand: set to true to enable auto-zoom with defaults (same as zoom.enabled=true)'),
|
|
614
|
+
// ── Click effects ──
|
|
408
615
|
clickEffect: z.object({
|
|
409
616
|
enabled: z.boolean().optional().describe('Show click ripple effects (default: true)'),
|
|
410
617
|
style: z.enum(['ripple', 'pulse', 'ring']).optional().describe('Click effect style (default: ripple)'),
|
|
411
618
|
color: z.string().optional().describe('Click effect color as hex'),
|
|
412
619
|
}).optional().describe('Visual click effect settings'),
|
|
620
|
+
// ── Pace ──
|
|
413
621
|
pace: z.union([
|
|
414
622
|
z.number().min(0.25).max(6),
|
|
415
623
|
z.enum(['fast', 'normal', 'slow', 'dramatic', 'cinematic']),
|
|
416
624
|
]).optional().describe('Controls how deliberate the video feels. Number (0.25–6.0, higher = slower) or preset: "fast" (0.5×), "normal" (1×), "slow" (2×), "dramatic" (3×), "cinematic" (4.5×). Default: "normal".'),
|
|
625
|
+
// ── Frame (browser chrome) ──
|
|
626
|
+
frame: z.object({
|
|
627
|
+
enabled: z.boolean().optional().describe('Enable browser frame around the video (default: false)'),
|
|
628
|
+
style: z.enum(['macos', 'windows', 'minimal']).optional().describe('Frame style: macos (traffic lights), windows (min/max/close), minimal (dots only). Default: macos.'),
|
|
629
|
+
theme: z.enum(['light', 'dark', 'auto']).optional().describe('Frame color theme (default: auto)'),
|
|
630
|
+
showUrl: z.boolean().optional().describe('Show URL in the frame bar (default: true)'),
|
|
631
|
+
}).optional().describe('Browser chrome frame around the video. Adds a macOS/Windows-style title bar.'),
|
|
632
|
+
// ── Background ──
|
|
633
|
+
background: z.object({
|
|
634
|
+
enabled: z.boolean().optional().describe('Enable styled background (default: false)'),
|
|
635
|
+
type: z.enum(['solid', 'gradient']).optional().describe('Background type (default: gradient)'),
|
|
636
|
+
gradient: z.enum([
|
|
637
|
+
'ocean', 'sunset', 'forest', 'midnight', 'aurora',
|
|
638
|
+
'lavender', 'peach', 'arctic', 'ember', 'slate', 'neon', 'custom',
|
|
639
|
+
]).optional().describe('Gradient preset name. 12 built-in presets, or "custom" to use colors array. Default: ocean.'),
|
|
640
|
+
color: z.string().optional().describe('Solid background color as hex (e.g. "#1e3a5f"). Used when type is "solid" or gradient is "custom".'),
|
|
641
|
+
colors: z.array(z.string()).optional().describe('Array of 2 hex colors for custom gradient (e.g. ["#1e3a5f", "#7c3aed"]). Only used when gradient is "custom".'),
|
|
642
|
+
padding: z.number().int().min(0).max(120).optional().describe('Padding around the video in pixels (default: 40)'),
|
|
643
|
+
borderRadius: z.number().int().min(0).max(40).optional().describe('Corner radius in pixels (default: 12)'),
|
|
644
|
+
}).optional().describe('Styled background behind the video. Adds gradient/solid background with padding and rounded corners — creates a "floating window" effect.'),
|
|
645
|
+
// ── Blocking ──
|
|
417
646
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
418
|
-
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: true)'),
|
|
647
|
+
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: true for videos)'),
|
|
648
|
+
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
649
|
+
blockChats: z.boolean().optional().describe('Block live chat widgets'),
|
|
650
|
+
blockTrackers: z.boolean().optional().describe('Block tracking scripts'),
|
|
419
651
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
652
|
+
variables: z.record(z.string()).optional().describe('Key-value map for variable substitution in step URLs/values. E.g. { "base_url": "https://example.com" } replaces {{base_url}} in steps.'),
|
|
420
653
|
saveTo: z.string().optional().describe('Output file path (default: ./recording.mp4)'),
|
|
421
654
|
},
|
|
422
655
|
async (params) => {
|
|
@@ -435,20 +668,42 @@ server.tool(
|
|
|
435
668
|
const data = await res.json();
|
|
436
669
|
const format = params.format || 'mp4';
|
|
437
670
|
const ext = format === 'gif' ? 'gif' : format;
|
|
438
|
-
const outputPath = resolve(saveTo || `./recording.${ext}`);
|
|
439
671
|
|
|
440
|
-
//
|
|
441
|
-
const
|
|
442
|
-
|
|
672
|
+
// Determine video MIME type
|
|
673
|
+
const videoMimeTypes = { mp4: 'video/mp4', webm: 'video/webm', gif: 'image/gif' };
|
|
674
|
+
const mimeType = videoMimeTypes[ext] || 'video/mp4';
|
|
675
|
+
|
|
676
|
+
// Best-effort save to disk (may fail in hosted/sandboxed environments)
|
|
677
|
+
let savedPath = null;
|
|
678
|
+
try {
|
|
679
|
+
const outputPath = safePath(saveTo, `./recording.${ext}`);
|
|
680
|
+
const buffer = Buffer.from(data.data, 'base64');
|
|
681
|
+
writeFileSync(outputPath, buffer);
|
|
682
|
+
savedPath = outputPath;
|
|
683
|
+
} catch (_diskErr) {
|
|
684
|
+
// Disk write failed (e.g. hosted environment, read-only FS) — data is
|
|
685
|
+
// still returned as an embedded resource below, so the client gets it.
|
|
686
|
+
}
|
|
443
687
|
|
|
444
688
|
const durationSec = (data.duration_ms / 1000).toFixed(1);
|
|
689
|
+
const fileNote = savedPath
|
|
690
|
+
? ` File: ${savedPath}\n`
|
|
691
|
+
: ` File: (not saved to disk — use the embedded resource data below)\n`;
|
|
445
692
|
|
|
446
693
|
return {
|
|
447
694
|
content: [
|
|
695
|
+
{
|
|
696
|
+
type: 'resource',
|
|
697
|
+
resource: {
|
|
698
|
+
uri: `pagebolt://video/recording.${ext}`,
|
|
699
|
+
mimeType,
|
|
700
|
+
blob: data.data, // base64-encoded video — always delivered to client
|
|
701
|
+
},
|
|
702
|
+
},
|
|
448
703
|
{
|
|
449
704
|
type: 'text',
|
|
450
705
|
text: `Video recorded successfully.\n` +
|
|
451
|
-
|
|
706
|
+
fileNote +
|
|
452
707
|
` Format: ${data.format}\n` +
|
|
453
708
|
` Size: ${(data.size_bytes / 1024).toFixed(1)} KB\n` +
|
|
454
709
|
` Duration: ${durationSec}s\n` +
|
|
@@ -465,10 +720,12 @@ server.tool(
|
|
|
465
720
|
}
|
|
466
721
|
);
|
|
467
722
|
|
|
468
|
-
//
|
|
723
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
724
|
+
// Tool: inspect_page — COMPLETE coverage
|
|
725
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
469
726
|
server.tool(
|
|
470
727
|
'inspect_page',
|
|
471
|
-
'Inspect a web page and get a structured map of all interactive elements, headings, forms, links, and images — each with a unique CSS selector. Use this BEFORE run_sequence to discover what elements exist on the page and get reliable selectors. Returns text (not an image), so it is fast and cheap. Costs 1 API request.',
|
|
728
|
+
'Inspect a web page and get a structured map of all interactive elements, headings, forms, links, and images — each with a unique CSS selector. Use this BEFORE run_sequence or record_video to discover what elements exist on the page and get reliable selectors. Returns text (not an image), so it is fast and cheap. Costs 1 API request.',
|
|
472
729
|
{
|
|
473
730
|
// ── Source ──
|
|
474
731
|
url: z.string().url().optional().describe('URL to inspect (required if no html)'),
|
|
@@ -479,36 +736,39 @@ server.tool(
|
|
|
479
736
|
viewportDevice: z.string().optional().describe('Device preset for viewport emulation (e.g. "iphone_14_pro"). Use list_devices to see all presets.'),
|
|
480
737
|
viewportMobile: z.boolean().optional().describe('Enable mobile meta viewport emulation'),
|
|
481
738
|
viewportHasTouch: z.boolean().optional().describe('Enable touch event emulation'),
|
|
739
|
+
viewportLandscape: z.boolean().optional().describe('Landscape orientation'),
|
|
482
740
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
483
741
|
// ── Timing ──
|
|
484
742
|
waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().describe('When to consider navigation finished (default: networkidle2)'),
|
|
485
743
|
waitForSelector: z.string().optional().describe('Wait for this CSS selector to appear before inspecting'),
|
|
744
|
+
navigationTimeout: z.number().int().min(0).max(30000).optional().describe('Navigation timeout in ms (default: 25000)'),
|
|
486
745
|
// ── Emulation ──
|
|
487
746
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
488
747
|
reducedMotion: z.boolean().optional().describe('Emulate prefers-reduced-motion'),
|
|
748
|
+
mediaType: z.enum(['screen', 'print']).optional().describe('Emulate CSS media type'),
|
|
749
|
+
timeZone: z.string().optional().describe('Override browser timezone'),
|
|
750
|
+
geolocation: z.object({
|
|
751
|
+
latitude: z.number(),
|
|
752
|
+
longitude: z.number(),
|
|
753
|
+
accuracy: z.number().optional(),
|
|
754
|
+
}).optional().describe('Emulate geolocation'),
|
|
489
755
|
userAgent: z.string().optional().describe('Override the browser User-Agent string'),
|
|
490
756
|
// ── Auth & headers ──
|
|
491
|
-
cookies: z.array(
|
|
492
|
-
z.union([
|
|
493
|
-
z.string(),
|
|
494
|
-
z.object({
|
|
495
|
-
name: z.string(),
|
|
496
|
-
value: z.string(),
|
|
497
|
-
domain: z.string().optional(),
|
|
498
|
-
}),
|
|
499
|
-
])
|
|
500
|
-
).optional().describe('Cookies to set — array of "name=value" strings or { name, value, domain? } objects'),
|
|
757
|
+
cookies: z.array(cookieSchema).optional().describe('Cookies to set — array of "name=value" strings or { name, value, domain? } objects'),
|
|
501
758
|
headers: z.record(z.string(), z.string()).optional().describe('Extra HTTP headers to send with the request'),
|
|
502
759
|
authorization: z.string().optional().describe('Authorization header value (e.g. "Bearer <token>")'),
|
|
503
760
|
bypassCSP: z.boolean().optional().describe('Bypass Content-Security-Policy on the page'),
|
|
504
761
|
// ── Content manipulation ──
|
|
505
762
|
hideSelectors: z.array(z.string()).optional().describe('Array of CSS selectors to hide before inspecting'),
|
|
763
|
+
injectCss: z.string().optional().describe('Custom CSS to inject before inspecting'),
|
|
764
|
+
injectJs: z.string().optional().describe('Custom JavaScript to execute before inspecting'),
|
|
765
|
+
// ── Blocking ──
|
|
506
766
|
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: false)'),
|
|
507
767
|
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
508
768
|
blockChats: z.boolean().optional().describe('Block live chat widgets'),
|
|
509
769
|
blockTrackers: z.boolean().optional().describe('Block tracking scripts'),
|
|
510
|
-
|
|
511
|
-
|
|
770
|
+
blockRequests: z.array(z.string()).optional().describe('URL patterns to block'),
|
|
771
|
+
blockResources: z.array(z.string()).optional().describe('Resource types to block'),
|
|
512
772
|
},
|
|
513
773
|
async (params) => {
|
|
514
774
|
if (!params.url && !params.html) {
|
|
@@ -526,7 +786,6 @@ server.tool(
|
|
|
526
786
|
// Format as structured text for efficient LLM consumption
|
|
527
787
|
const lines = [];
|
|
528
788
|
|
|
529
|
-
// Header
|
|
530
789
|
lines.push(`Page: ${data.title || '(untitled)'} (${data.url || params.url || 'html content'})`);
|
|
531
790
|
if (data.metadata) {
|
|
532
791
|
if (data.metadata.description) lines.push(`Description: ${data.metadata.description}`);
|
|
@@ -535,7 +794,6 @@ server.tool(
|
|
|
535
794
|
}
|
|
536
795
|
lines.push('');
|
|
537
796
|
|
|
538
|
-
// Headings
|
|
539
797
|
if (data.headings && data.headings.length > 0) {
|
|
540
798
|
lines.push(`Headings (${data.headings.length}):`);
|
|
541
799
|
for (const h of data.headings) {
|
|
@@ -544,7 +802,6 @@ server.tool(
|
|
|
544
802
|
lines.push('');
|
|
545
803
|
}
|
|
546
804
|
|
|
547
|
-
// Interactive elements
|
|
548
805
|
if (data.elements && data.elements.length > 0) {
|
|
549
806
|
lines.push(`Interactive Elements (${data.elements.length}):`);
|
|
550
807
|
for (const el of data.elements) {
|
|
@@ -560,7 +817,6 @@ server.tool(
|
|
|
560
817
|
lines.push('');
|
|
561
818
|
}
|
|
562
819
|
|
|
563
|
-
// Forms
|
|
564
820
|
if (data.forms && data.forms.length > 0) {
|
|
565
821
|
lines.push(`Forms (${data.forms.length}):`);
|
|
566
822
|
for (const f of data.forms) {
|
|
@@ -574,7 +830,6 @@ server.tool(
|
|
|
574
830
|
lines.push('');
|
|
575
831
|
}
|
|
576
832
|
|
|
577
|
-
// Links
|
|
578
833
|
if (data.links && data.links.length > 0) {
|
|
579
834
|
lines.push(`Links (${data.links.length}):`);
|
|
580
835
|
for (const l of data.links) {
|
|
@@ -583,7 +838,6 @@ server.tool(
|
|
|
583
838
|
lines.push('');
|
|
584
839
|
}
|
|
585
840
|
|
|
586
|
-
// Images
|
|
587
841
|
if (data.images && data.images.length > 0) {
|
|
588
842
|
lines.push(`Images (${data.images.length}):`);
|
|
589
843
|
for (const img of data.images) {
|
|
@@ -596,12 +850,7 @@ server.tool(
|
|
|
596
850
|
lines.push(`Duration: ${data.duration_ms}ms`);
|
|
597
851
|
|
|
598
852
|
return {
|
|
599
|
-
content: [
|
|
600
|
-
{
|
|
601
|
-
type: 'text',
|
|
602
|
-
text: lines.join('\n'),
|
|
603
|
-
},
|
|
604
|
-
],
|
|
853
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
605
854
|
};
|
|
606
855
|
} catch (err) {
|
|
607
856
|
return { content: [{ type: 'text', text: `Inspect error: ${err.message}` }], isError: true };
|
|
@@ -609,7 +858,9 @@ server.tool(
|
|
|
609
858
|
}
|
|
610
859
|
);
|
|
611
860
|
|
|
612
|
-
//
|
|
861
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
862
|
+
// Tool: list_devices
|
|
863
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
613
864
|
server.tool(
|
|
614
865
|
'list_devices',
|
|
615
866
|
'List all available device presets for viewport emulation (e.g. iphone_14_pro, macbook_pro_14). Use the returned device names with the viewportDevice parameter in take_screenshot.',
|
|
@@ -638,7 +889,9 @@ server.tool(
|
|
|
638
889
|
}
|
|
639
890
|
);
|
|
640
891
|
|
|
641
|
-
//
|
|
892
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
893
|
+
// Tool: check_usage
|
|
894
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
642
895
|
server.tool(
|
|
643
896
|
'check_usage',
|
|
644
897
|
'Check your current PageBolt API usage and plan limits.',
|
|
@@ -668,7 +921,194 @@ server.tool(
|
|
|
668
921
|
|
|
669
922
|
} // end registerTools
|
|
670
923
|
|
|
671
|
-
// ───
|
|
924
|
+
// ─── Prompts ────────────────────────────────────────────────────
|
|
925
|
+
function registerPrompts(server) {
|
|
926
|
+
|
|
927
|
+
server.prompt(
|
|
928
|
+
'capture-page',
|
|
929
|
+
'Capture a clean screenshot of any URL with sensible defaults. Optionally inspects the page first.',
|
|
930
|
+
{
|
|
931
|
+
url: z.string().describe('The URL to capture'),
|
|
932
|
+
device: z.string().optional().describe('Device preset, e.g. "iphone_14_pro" or "macbook_pro_14"'),
|
|
933
|
+
dark_mode: z.enum(['true', 'false']).optional().describe('Enable dark mode (default: false)'),
|
|
934
|
+
full_page: z.enum(['true', 'false']).optional().describe('Capture the full scrollable page (default: false)'),
|
|
935
|
+
style_theme: z.enum([
|
|
936
|
+
'notion', 'paper', 'vercel', 'glass', 'ocean', 'sunset',
|
|
937
|
+
'linear', 'arc', 'glassDark', 'glassWarm', 'spotlight',
|
|
938
|
+
'neonBlue', 'neonPurple', 'neonGreen', 'lavender', 'ember', 'dots', 'grid',
|
|
939
|
+
'none',
|
|
940
|
+
]).optional().describe('Screenshot style theme (default: none). Use "glass" for frosted glass, "ocean" for gradient, "linear" for Linear-style dark.'),
|
|
941
|
+
},
|
|
942
|
+
(args) => {
|
|
943
|
+
const device = args.device ? `\n- Use device preset: ${args.device}` : '';
|
|
944
|
+
const dark = args.dark_mode === 'true' ? '\n- Enable dark mode' : '';
|
|
945
|
+
const full = args.full_page === 'true' ? '\n- Capture the full scrollable page' : '';
|
|
946
|
+
const style = args.style_theme && args.style_theme !== 'none'
|
|
947
|
+
? `\n- Apply style theme: "${args.style_theme}" (adds frame, background, shadow)`
|
|
948
|
+
: '';
|
|
949
|
+
|
|
950
|
+
return {
|
|
951
|
+
messages: [
|
|
952
|
+
{
|
|
953
|
+
role: 'user',
|
|
954
|
+
content: {
|
|
955
|
+
type: 'text',
|
|
956
|
+
text: `Take a clean screenshot of ${args.url} with these settings:
|
|
957
|
+
- Block banners, ads, chats, and trackers for a clean capture${device}${dark}${full}${style}
|
|
958
|
+
- Use PNG format
|
|
959
|
+
|
|
960
|
+
Call take_screenshot with:
|
|
961
|
+
url: "${args.url}"
|
|
962
|
+
blockBanners: true
|
|
963
|
+
blockAds: true
|
|
964
|
+
blockChats: true
|
|
965
|
+
blockTrackers: true${args.device ? `\n viewportDevice: "${args.device}"` : ''}${args.dark_mode === 'true' ? '\n darkMode: true' : ''}${args.full_page === 'true' ? '\n fullPage: true\n fullPageScroll: true' : ''}${args.style_theme && args.style_theme !== 'none' ? `\n style: { theme: "${args.style_theme}" }` : ''}`,
|
|
966
|
+
},
|
|
967
|
+
},
|
|
968
|
+
],
|
|
969
|
+
};
|
|
970
|
+
}
|
|
971
|
+
);
|
|
972
|
+
|
|
973
|
+
server.prompt(
|
|
974
|
+
'record-demo',
|
|
975
|
+
'Record a professional demo video of a web page or flow. Generates a step sequence automatically.',
|
|
976
|
+
{
|
|
977
|
+
url: z.string().describe('The starting URL to record'),
|
|
978
|
+
description: z.string().describe('What the demo should show, e.g. "Sign in and explore the dashboard"'),
|
|
979
|
+
pace: z.enum(['fast', 'normal', 'slow', 'dramatic', 'cinematic']).optional().describe('Video pace preset (default: normal)'),
|
|
980
|
+
format: z.enum(['mp4', 'webm', 'gif']).optional().describe('Output format (default: mp4)'),
|
|
981
|
+
frame: z.enum(['macos', 'windows', 'minimal', 'none']).optional().describe('Browser frame style (default: none)'),
|
|
982
|
+
background: z.enum(['ocean', 'sunset', 'midnight', 'glass', 'none']).optional().describe('Background style (default: none)'),
|
|
983
|
+
},
|
|
984
|
+
(args) => {
|
|
985
|
+
const pace = args.pace || 'normal';
|
|
986
|
+
const format = args.format || 'mp4';
|
|
987
|
+
const frame = args.frame || 'none';
|
|
988
|
+
const bg = args.background || 'none';
|
|
989
|
+
|
|
990
|
+
const frameConfig = frame !== 'none'
|
|
991
|
+
? `\n - frame: { enabled: true, style: "${frame}", theme: "dark" }`
|
|
992
|
+
: '';
|
|
993
|
+
const bgConfig = bg !== 'none'
|
|
994
|
+
? `\n - background: { enabled: true, type: "gradient", gradient: "${bg}", padding: 40, borderRadius: 12 }`
|
|
995
|
+
: '';
|
|
996
|
+
|
|
997
|
+
return {
|
|
998
|
+
messages: [
|
|
999
|
+
{
|
|
1000
|
+
role: 'user',
|
|
1001
|
+
content: {
|
|
1002
|
+
type: 'text',
|
|
1003
|
+
text: `Record a professional demo video. Here's what I need:
|
|
1004
|
+
|
|
1005
|
+
**Starting URL:** ${args.url}
|
|
1006
|
+
**What to demo:** ${args.description}
|
|
1007
|
+
**Pace:** ${pace}
|
|
1008
|
+
**Format:** ${format}
|
|
1009
|
+
|
|
1010
|
+
Please follow this workflow:
|
|
1011
|
+
|
|
1012
|
+
1. First, call inspect_page on ${args.url} (with blockBanners: true) to discover the page structure and get reliable CSS selectors.
|
|
1013
|
+
|
|
1014
|
+
2. Based on the inspection results and the description above, plan a sequence of steps (navigate, click, fill, scroll, wait, etc.) that demonstrates the described flow.
|
|
1015
|
+
|
|
1016
|
+
3. Call record_video with:
|
|
1017
|
+
- The planned steps array
|
|
1018
|
+
- format: "${format}"
|
|
1019
|
+
- pace: "${pace}"
|
|
1020
|
+
- blockBanners: true
|
|
1021
|
+
- cursor: { style: "classic", visible: true, persist: true }
|
|
1022
|
+
- clickEffect: { style: "ripple" }${frameConfig}${bgConfig}
|
|
1023
|
+
|
|
1024
|
+
Important tips:
|
|
1025
|
+
- Use selectors from the inspect_page results — never guess selectors
|
|
1026
|
+
- Add wait steps (ms: 800-1200) between interactions for visual clarity
|
|
1027
|
+
- Use wait_for after navigation to ensure the page loads
|
|
1028
|
+
- **ALWAYS add a "note" field on every meaningful step** — notes render as styled tooltip annotations that explain what's happening, creating a guided tour experience. Examples:
|
|
1029
|
+
- navigate: note: "Opening the dashboard"
|
|
1030
|
+
- click: note: "This button creates a new project"
|
|
1031
|
+
- fill: note: "Enter your email to get started"
|
|
1032
|
+
- hover: note: "Hover to reveal the dropdown menu"
|
|
1033
|
+
- The ONLY steps without notes should be wait/wait_for (pauses)
|
|
1034
|
+
- Keep to 15 steps or fewer for best results
|
|
1035
|
+
- Each video costs 3 API requests`,
|
|
1036
|
+
},
|
|
1037
|
+
},
|
|
1038
|
+
],
|
|
1039
|
+
};
|
|
1040
|
+
}
|
|
1041
|
+
);
|
|
1042
|
+
|
|
1043
|
+
server.prompt(
|
|
1044
|
+
'audit-page',
|
|
1045
|
+
'Inspect a page and return a structured analysis of its elements, forms, links, and interactive components.',
|
|
1046
|
+
{
|
|
1047
|
+
url: z.string().describe('The URL to audit'),
|
|
1048
|
+
},
|
|
1049
|
+
(args) => {
|
|
1050
|
+
return {
|
|
1051
|
+
messages: [
|
|
1052
|
+
{
|
|
1053
|
+
role: 'user',
|
|
1054
|
+
content: {
|
|
1055
|
+
type: 'text',
|
|
1056
|
+
text: `Perform a structured audit of ${args.url}.
|
|
1057
|
+
|
|
1058
|
+
1. Call inspect_page with:
|
|
1059
|
+
- url: "${args.url}"
|
|
1060
|
+
- blockBanners: true
|
|
1061
|
+
- blockAds: true
|
|
1062
|
+
|
|
1063
|
+
2. Analyze the results and provide a clear summary:
|
|
1064
|
+
- **Page overview:** Title, description, language, HTTP status
|
|
1065
|
+
- **Navigation:** List all nav links with their destinations
|
|
1066
|
+
- **Forms:** List all forms with their fields and actions
|
|
1067
|
+
- **Interactive elements:** Buttons, dropdowns, toggles with their selectors
|
|
1068
|
+
- **Headings:** Document outline (h1-h6 hierarchy)
|
|
1069
|
+
- **Images:** Count and list images missing alt text
|
|
1070
|
+
- **Potential issues:** Missing form labels, broken links, accessibility concerns
|
|
1071
|
+
|
|
1072
|
+
3. If this page will be used for automation (sequence/video), list the most useful CSS selectors the user should know about.`,
|
|
1073
|
+
},
|
|
1074
|
+
},
|
|
1075
|
+
],
|
|
1076
|
+
};
|
|
1077
|
+
}
|
|
1078
|
+
);
|
|
1079
|
+
|
|
1080
|
+
} // end registerPrompts
|
|
1081
|
+
|
|
1082
|
+
// ─── Resources ──────────────────────────────────────────────────
|
|
1083
|
+
function registerResources(server) {
|
|
1084
|
+
|
|
1085
|
+
server.resource(
|
|
1086
|
+
'api-docs',
|
|
1087
|
+
'pagebolt://api-docs',
|
|
1088
|
+
{ description: 'Complete PageBolt API reference with all endpoints, parameters, examples, and plan limits. Read this for detailed documentation beyond tool descriptions.', mimeType: 'text/plain' },
|
|
1089
|
+
async () => {
|
|
1090
|
+
try {
|
|
1091
|
+
const res = await fetch(`${BASE_URL}/llms-full.txt`);
|
|
1092
|
+
if (res.ok) {
|
|
1093
|
+
const text = await res.text();
|
|
1094
|
+
return { contents: [{ uri: 'pagebolt://api-docs', text, mimeType: 'text/plain' }] };
|
|
1095
|
+
}
|
|
1096
|
+
} catch (_) {
|
|
1097
|
+
// fall through to embedded fallback
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
contents: [{
|
|
1101
|
+
uri: 'pagebolt://api-docs',
|
|
1102
|
+
text: 'Full API docs available at https://pagebolt.dev/docs or https://pagebolt.dev/llms-full.txt',
|
|
1103
|
+
mimeType: 'text/plain',
|
|
1104
|
+
}],
|
|
1105
|
+
};
|
|
1106
|
+
}
|
|
1107
|
+
);
|
|
1108
|
+
|
|
1109
|
+
} // end registerResources
|
|
1110
|
+
|
|
1111
|
+
// ─── Smithery sandbox export ─────────────────────────────────────
|
|
672
1112
|
export function createSandboxServer() {
|
|
673
1113
|
return createConfiguredServer();
|
|
674
1114
|
}
|