pagebolt-mcp 1.3.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 +0 -4
- package/package.json +1 -1
- package/src/index.mjs +316 -109
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
|
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,8 +87,50 @@ 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
|
+
|
|
78
133
|
// ─── Server Instructions ────────────────────────────────────────
|
|
79
|
-
// Sent to the AI agent on connection so it knows how to use the tools.
|
|
80
134
|
const SERVER_INSTRUCTIONS = `
|
|
81
135
|
PageBolt gives you 8 tools for web capture and browser automation. All tools use your API key automatically.
|
|
82
136
|
|
|
@@ -102,6 +156,23 @@ When building sequences or videos, ALWAYS use inspect_page first to discover rel
|
|
|
102
156
|
|
|
103
157
|
This avoids guessing selectors like "#submit" when the actual element is "#submitBtn".
|
|
104
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
|
+
|
|
105
176
|
## Common Parameters (available on most tools)
|
|
106
177
|
|
|
107
178
|
- blockBanners: true — hides cookie consent banners (GDPR popups, OneTrust, CookieBot, etc.)
|
|
@@ -119,7 +190,7 @@ Use blockBanners on almost every request to get clean captures. Combine blockAds
|
|
|
119
190
|
- extractMetadata: true on take_screenshot returns title, description, OG tags, HTTP status
|
|
120
191
|
- response_type: "json" returns base64 data instead of binary (useful for programmatic use)
|
|
121
192
|
- record_video pace presets: "fast" (0.5x), "normal" (1x), "slow" (2x), "dramatic" (3x), "cinematic" (4.5x)
|
|
122
|
-
- record_video cursor styles: "highlight", "circle", "spotlight", "dot"
|
|
193
|
+
- record_video cursor styles: "highlight", "circle", "spotlight", "dot", "classic"
|
|
123
194
|
- run_sequence requires at least 1 screenshot or pdf step as output
|
|
124
195
|
- record_video does NOT allow screenshot/pdf steps — the whole sequence IS the video
|
|
125
196
|
- Max 2 evaluate (JavaScript) steps per sequence/video
|
|
@@ -140,7 +211,7 @@ Use blockBanners on almost every request to get clean captures. Combine blockAds
|
|
|
140
211
|
function createConfiguredServer() {
|
|
141
212
|
const srv = new McpServer({
|
|
142
213
|
name: 'pagebolt',
|
|
143
|
-
version: '1.
|
|
214
|
+
version: '1.5.0',
|
|
144
215
|
}, {
|
|
145
216
|
instructions: SERVER_INSTRUCTIONS,
|
|
146
217
|
});
|
|
@@ -154,10 +225,12 @@ const server = createConfiguredServer();
|
|
|
154
225
|
|
|
155
226
|
function registerTools(server) {
|
|
156
227
|
|
|
157
|
-
//
|
|
228
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
229
|
+
// Tool: take_screenshot — COMPLETE coverage
|
|
230
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
158
231
|
server.tool(
|
|
159
232
|
'take_screenshot',
|
|
160
|
-
'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).',
|
|
161
234
|
{
|
|
162
235
|
// ── Source ──
|
|
163
236
|
url: z.string().url().optional().describe('URL to capture (required if no html/markdown)'),
|
|
@@ -169,6 +242,7 @@ server.tool(
|
|
|
169
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.'),
|
|
170
243
|
viewportMobile: z.boolean().optional().describe('Enable mobile meta viewport emulation'),
|
|
171
244
|
viewportHasTouch: z.boolean().optional().describe('Enable touch event emulation'),
|
|
245
|
+
viewportLandscape: z.boolean().optional().describe('Landscape orientation'),
|
|
172
246
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio, use 2 for retina (default: 1)'),
|
|
173
247
|
// ── Output format ──
|
|
174
248
|
format: z.enum(['png', 'jpeg', 'webp']).optional().describe('Image format (default: png)'),
|
|
@@ -177,6 +251,8 @@ server.tool(
|
|
|
177
251
|
// ── Capture region ──
|
|
178
252
|
fullPage: z.boolean().optional().describe('Capture the full scrollable page (default: false)'),
|
|
179
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)'),
|
|
180
256
|
fullPageMaxHeight: z.number().int().optional().describe('Maximum pixel height cap for full-page captures'),
|
|
181
257
|
selector: z.string().optional().describe('CSS selector to capture a specific element'),
|
|
182
258
|
clip: z.object({
|
|
@@ -186,9 +262,10 @@ server.tool(
|
|
|
186
262
|
height: z.number(),
|
|
187
263
|
}).optional().describe('Crop region { x, y, width, height } in pixels'),
|
|
188
264
|
// ── Timing ──
|
|
189
|
-
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)'),
|
|
190
266
|
waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().describe('When to consider navigation finished (default: networkidle2)'),
|
|
191
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)'),
|
|
192
269
|
// ── Emulation ──
|
|
193
270
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
194
271
|
reducedMotion: z.boolean().optional().describe('Emulate prefers-reduced-motion to disable animations'),
|
|
@@ -201,28 +278,26 @@ server.tool(
|
|
|
201
278
|
}).optional().describe('Emulate geolocation { latitude, longitude, accuracy? }'),
|
|
202
279
|
userAgent: z.string().optional().describe('Override the browser User-Agent string'),
|
|
203
280
|
// ── Auth & headers ──
|
|
204
|
-
cookies: z.array(
|
|
205
|
-
z.union([
|
|
206
|
-
z.string(),
|
|
207
|
-
z.object({
|
|
208
|
-
name: z.string(),
|
|
209
|
-
value: z.string(),
|
|
210
|
-
domain: z.string().optional(),
|
|
211
|
-
}),
|
|
212
|
-
])
|
|
213
|
-
).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'),
|
|
214
282
|
headers: z.record(z.string(), z.string()).optional().describe('Extra HTTP headers to send with the request'),
|
|
215
283
|
authorization: z.string().optional().describe('Authorization header value (e.g. "Bearer <token>")'),
|
|
216
284
|
bypassCSP: z.boolean().optional().describe('Bypass Content-Security-Policy on the page'),
|
|
217
285
|
// ── Content manipulation ──
|
|
218
286
|
hideSelectors: z.array(z.string()).optional().describe('Array of CSS selectors to hide before capture'),
|
|
219
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 ──
|
|
220
291
|
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: false)'),
|
|
221
292
|
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
222
293
|
blockChats: z.boolean().optional().describe('Block live chat widgets on the page'),
|
|
223
294
|
blockTrackers: z.boolean().optional().describe('Block tracking scripts on the page'),
|
|
224
|
-
|
|
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 ──
|
|
225
298
|
extractMetadata: z.boolean().optional().describe('Extract page metadata (title, description, OG tags) alongside the screenshot'),
|
|
299
|
+
// ── Styling ──
|
|
300
|
+
style: styleSchema,
|
|
226
301
|
},
|
|
227
302
|
async (params) => {
|
|
228
303
|
if (!params.url && !params.html && !params.markdown) {
|
|
@@ -237,35 +312,57 @@ server.tool(
|
|
|
237
312
|
const data = await res.json();
|
|
238
313
|
const format = params.format || 'png';
|
|
239
314
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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 };
|
|
253
336
|
}
|
|
254
337
|
);
|
|
255
338
|
|
|
256
|
-
//
|
|
339
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
340
|
+
// Tool: generate_pdf — COMPLETE coverage
|
|
341
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
257
342
|
server.tool(
|
|
258
343
|
'generate_pdf',
|
|
259
|
-
'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.',
|
|
260
345
|
{
|
|
261
346
|
url: z.string().url().optional().describe('URL to render as PDF (required if no html)'),
|
|
262
347
|
html: z.string().optional().describe('Raw HTML to render as PDF (required if no url)'),
|
|
263
|
-
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)'),
|
|
264
349
|
landscape: z.boolean().optional().describe('Landscape orientation (default: false)'),
|
|
265
350
|
printBackground: z.boolean().optional().describe('Include CSS backgrounds (default: true)'),
|
|
266
|
-
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 }'),
|
|
267
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"'),
|
|
268
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)'),
|
|
269
366
|
delay: z.number().int().min(0).max(10000).optional().describe('Milliseconds to wait before rendering (default: 0)'),
|
|
270
367
|
saveTo: z.string().optional().describe('Output file path (default: ./output.pdf)'),
|
|
271
368
|
},
|
|
@@ -281,18 +378,37 @@ server.tool(
|
|
|
281
378
|
});
|
|
282
379
|
|
|
283
380
|
const data = await res.json();
|
|
284
|
-
const outputPath = resolve(saveTo || './output.pdf');
|
|
285
381
|
|
|
286
|
-
//
|
|
287
|
-
|
|
288
|
-
|
|
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)`;
|
|
289
397
|
|
|
290
398
|
return {
|
|
291
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
|
+
},
|
|
292
408
|
{
|
|
293
409
|
type: 'text',
|
|
294
410
|
text: `PDF generated successfully.\n` +
|
|
295
|
-
|
|
411
|
+
`${fileNote}\n` +
|
|
296
412
|
` Size: ${data.size_bytes} bytes\n` +
|
|
297
413
|
` Duration: ${data.duration_ms}ms`,
|
|
298
414
|
},
|
|
@@ -301,13 +417,15 @@ server.tool(
|
|
|
301
417
|
}
|
|
302
418
|
);
|
|
303
419
|
|
|
304
|
-
//
|
|
420
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
421
|
+
// Tool: create_og_image — COMPLETE coverage
|
|
422
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
305
423
|
server.tool(
|
|
306
424
|
'create_og_image',
|
|
307
425
|
'Generate an Open Graph / social card image. Returns an image using built-in templates or custom HTML.',
|
|
308
426
|
{
|
|
309
427
|
template: z.enum(['default', 'minimal', 'gradient']).optional().describe('Built-in template name (default: "default")'),
|
|
310
|
-
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+)'),
|
|
311
429
|
title: z.string().optional().describe('Main title text (default: "Your Title Here")'),
|
|
312
430
|
subtitle: z.string().optional().describe('Subtitle text'),
|
|
313
431
|
logo: z.string().optional().describe('Logo image URL'),
|
|
@@ -344,7 +462,9 @@ server.tool(
|
|
|
344
462
|
}
|
|
345
463
|
);
|
|
346
464
|
|
|
347
|
-
//
|
|
465
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
466
|
+
// Tool: run_sequence — COMPLETE coverage
|
|
467
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
348
468
|
server.tool(
|
|
349
469
|
'run_sequence',
|
|
350
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.',
|
|
@@ -352,7 +472,7 @@ server.tool(
|
|
|
352
472
|
steps: z.array(
|
|
353
473
|
z.object({
|
|
354
474
|
action: z.enum([
|
|
355
|
-
'navigate', 'click', 'fill', 'select', 'hover',
|
|
475
|
+
'navigate', 'click', 'dblclick', 'fill', 'select', 'hover',
|
|
356
476
|
'scroll', 'wait', 'wait_for', 'evaluate',
|
|
357
477
|
'screenshot', 'pdf',
|
|
358
478
|
]).describe('The action to perform'),
|
|
@@ -367,11 +487,16 @@ server.tool(
|
|
|
367
487
|
name: z.string().optional().describe('Name for the output (for screenshot/pdf actions)'),
|
|
368
488
|
format: z.string().optional().describe('Image format: png, jpeg, webp (screenshot) or A4, Letter (pdf)'),
|
|
369
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)'),
|
|
370
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)'),
|
|
371
495
|
landscape: z.boolean().optional().describe('Landscape orientation (for pdf action)'),
|
|
372
496
|
printBackground: z.boolean().optional().describe('Include CSS backgrounds (for pdf action)'),
|
|
373
497
|
margin: z.string().optional().describe('CSS margin for all sides (for pdf action)'),
|
|
374
498
|
scale: z.number().min(0.1).max(2).optional().describe('Rendering scale (for pdf action)'),
|
|
499
|
+
style: styleSchema,
|
|
375
500
|
})
|
|
376
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.'),
|
|
377
502
|
viewport: z.object({
|
|
@@ -380,6 +505,9 @@ server.tool(
|
|
|
380
505
|
}).optional().describe('Browser viewport size'),
|
|
381
506
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
382
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'),
|
|
383
511
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
384
512
|
},
|
|
385
513
|
async (params) => {
|
|
@@ -430,15 +558,17 @@ server.tool(
|
|
|
430
558
|
}
|
|
431
559
|
);
|
|
432
560
|
|
|
433
|
-
//
|
|
561
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
562
|
+
// Tool: record_video — COMPLETE coverage
|
|
563
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
434
564
|
server.tool(
|
|
435
565
|
'record_video',
|
|
436
|
-
'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.',
|
|
437
567
|
{
|
|
438
568
|
steps: z.array(
|
|
439
569
|
z.object({
|
|
440
570
|
action: z.enum([
|
|
441
|
-
'navigate', 'click', 'fill', 'select', 'hover',
|
|
571
|
+
'navigate', 'click', 'dblclick', 'fill', 'select', 'hover',
|
|
442
572
|
'scroll', 'wait', 'wait_for', 'evaluate',
|
|
443
573
|
]).describe('The action to perform (no screenshot/pdf — the whole sequence is recorded as video)'),
|
|
444
574
|
url: z.string().url().optional().describe('URL to navigate to (for navigate action)'),
|
|
@@ -449,6 +579,12 @@ server.tool(
|
|
|
449
579
|
x: z.number().optional().describe('Horizontal scroll position'),
|
|
450
580
|
y: z.number().optional().describe('Vertical scroll position'),
|
|
451
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.'),
|
|
452
588
|
})
|
|
453
589
|
).min(1).max(50).describe('Array of steps to execute and record. Max steps depends on plan (10-50).'),
|
|
454
590
|
viewport: z.object({
|
|
@@ -457,31 +593,63 @@ server.tool(
|
|
|
457
593
|
}).optional().describe('Browser viewport size'),
|
|
458
594
|
format: z.enum(['mp4', 'webm', 'gif']).optional().describe('Video format (default: mp4). webm/gif require Starter+ plan.'),
|
|
459
595
|
framerate: z.number().int().optional().describe('Frames per second: 24, 30, or 60 (default: 30)'),
|
|
596
|
+
// ── Cursor ──
|
|
460
597
|
cursor: z.object({
|
|
461
598
|
visible: z.boolean().optional().describe('Show cursor overlay (default: true)'),
|
|
462
|
-
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.'),
|
|
463
600
|
color: z.string().optional().describe('Cursor color as hex, e.g. "#3B82F6" (default: blue)'),
|
|
464
601
|
size: z.number().int().min(8).max(60).optional().describe('Cursor size in pixels (default: 20)'),
|
|
465
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)'),
|
|
466
605
|
}).optional().describe('Cursor appearance settings'),
|
|
606
|
+
// ── Zoom (global defaults, per-step overrides available) ──
|
|
467
607
|
zoom: z.object({
|
|
468
|
-
enabled: z.boolean().optional().describe('
|
|
469
|
-
level: z.number().min(1.
|
|
470
|
-
duration: z.number().int().min(
|
|
471
|
-
|
|
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.'),
|
|
472
613
|
autoZoom: z.boolean().optional().describe('Shorthand: set to true to enable auto-zoom with defaults (same as zoom.enabled=true)'),
|
|
614
|
+
// ── Click effects ──
|
|
473
615
|
clickEffect: z.object({
|
|
474
616
|
enabled: z.boolean().optional().describe('Show click ripple effects (default: true)'),
|
|
475
617
|
style: z.enum(['ripple', 'pulse', 'ring']).optional().describe('Click effect style (default: ripple)'),
|
|
476
618
|
color: z.string().optional().describe('Click effect color as hex'),
|
|
477
619
|
}).optional().describe('Visual click effect settings'),
|
|
620
|
+
// ── Pace ──
|
|
478
621
|
pace: z.union([
|
|
479
622
|
z.number().min(0.25).max(6),
|
|
480
623
|
z.enum(['fast', 'normal', 'slow', 'dramatic', 'cinematic']),
|
|
481
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 ──
|
|
482
646
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
483
|
-
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'),
|
|
484
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.'),
|
|
485
653
|
saveTo: z.string().optional().describe('Output file path (default: ./recording.mp4)'),
|
|
486
654
|
},
|
|
487
655
|
async (params) => {
|
|
@@ -500,20 +668,42 @@ server.tool(
|
|
|
500
668
|
const data = await res.json();
|
|
501
669
|
const format = params.format || 'mp4';
|
|
502
670
|
const ext = format === 'gif' ? 'gif' : format;
|
|
503
|
-
const outputPath = resolve(saveTo || `./recording.${ext}`);
|
|
504
671
|
|
|
505
|
-
//
|
|
506
|
-
const
|
|
507
|
-
|
|
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
|
+
}
|
|
508
687
|
|
|
509
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`;
|
|
510
692
|
|
|
511
693
|
return {
|
|
512
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
|
+
},
|
|
513
703
|
{
|
|
514
704
|
type: 'text',
|
|
515
705
|
text: `Video recorded successfully.\n` +
|
|
516
|
-
|
|
706
|
+
fileNote +
|
|
517
707
|
` Format: ${data.format}\n` +
|
|
518
708
|
` Size: ${(data.size_bytes / 1024).toFixed(1)} KB\n` +
|
|
519
709
|
` Duration: ${durationSec}s\n` +
|
|
@@ -530,10 +720,12 @@ server.tool(
|
|
|
530
720
|
}
|
|
531
721
|
);
|
|
532
722
|
|
|
533
|
-
//
|
|
723
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
724
|
+
// Tool: inspect_page — COMPLETE coverage
|
|
725
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
534
726
|
server.tool(
|
|
535
727
|
'inspect_page',
|
|
536
|
-
'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.',
|
|
537
729
|
{
|
|
538
730
|
// ── Source ──
|
|
539
731
|
url: z.string().url().optional().describe('URL to inspect (required if no html)'),
|
|
@@ -544,36 +736,39 @@ server.tool(
|
|
|
544
736
|
viewportDevice: z.string().optional().describe('Device preset for viewport emulation (e.g. "iphone_14_pro"). Use list_devices to see all presets.'),
|
|
545
737
|
viewportMobile: z.boolean().optional().describe('Enable mobile meta viewport emulation'),
|
|
546
738
|
viewportHasTouch: z.boolean().optional().describe('Enable touch event emulation'),
|
|
739
|
+
viewportLandscape: z.boolean().optional().describe('Landscape orientation'),
|
|
547
740
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
548
741
|
// ── Timing ──
|
|
549
742
|
waitUntil: z.enum(['load', 'domcontentloaded', 'networkidle0', 'networkidle2']).optional().describe('When to consider navigation finished (default: networkidle2)'),
|
|
550
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)'),
|
|
551
745
|
// ── Emulation ──
|
|
552
746
|
darkMode: z.boolean().optional().describe('Emulate dark color scheme (default: false)'),
|
|
553
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'),
|
|
554
755
|
userAgent: z.string().optional().describe('Override the browser User-Agent string'),
|
|
555
756
|
// ── Auth & headers ──
|
|
556
|
-
cookies: z.array(
|
|
557
|
-
z.union([
|
|
558
|
-
z.string(),
|
|
559
|
-
z.object({
|
|
560
|
-
name: z.string(),
|
|
561
|
-
value: z.string(),
|
|
562
|
-
domain: z.string().optional(),
|
|
563
|
-
}),
|
|
564
|
-
])
|
|
565
|
-
).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'),
|
|
566
758
|
headers: z.record(z.string(), z.string()).optional().describe('Extra HTTP headers to send with the request'),
|
|
567
759
|
authorization: z.string().optional().describe('Authorization header value (e.g. "Bearer <token>")'),
|
|
568
760
|
bypassCSP: z.boolean().optional().describe('Bypass Content-Security-Policy on the page'),
|
|
569
761
|
// ── Content manipulation ──
|
|
570
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 ──
|
|
571
766
|
blockBanners: z.boolean().optional().describe('Hide cookie consent banners (default: false)'),
|
|
572
767
|
blockAds: z.boolean().optional().describe('Block advertisements on the page'),
|
|
573
768
|
blockChats: z.boolean().optional().describe('Block live chat widgets'),
|
|
574
769
|
blockTrackers: z.boolean().optional().describe('Block tracking scripts'),
|
|
575
|
-
|
|
576
|
-
|
|
770
|
+
blockRequests: z.array(z.string()).optional().describe('URL patterns to block'),
|
|
771
|
+
blockResources: z.array(z.string()).optional().describe('Resource types to block'),
|
|
577
772
|
},
|
|
578
773
|
async (params) => {
|
|
579
774
|
if (!params.url && !params.html) {
|
|
@@ -591,7 +786,6 @@ server.tool(
|
|
|
591
786
|
// Format as structured text for efficient LLM consumption
|
|
592
787
|
const lines = [];
|
|
593
788
|
|
|
594
|
-
// Header
|
|
595
789
|
lines.push(`Page: ${data.title || '(untitled)'} (${data.url || params.url || 'html content'})`);
|
|
596
790
|
if (data.metadata) {
|
|
597
791
|
if (data.metadata.description) lines.push(`Description: ${data.metadata.description}`);
|
|
@@ -600,7 +794,6 @@ server.tool(
|
|
|
600
794
|
}
|
|
601
795
|
lines.push('');
|
|
602
796
|
|
|
603
|
-
// Headings
|
|
604
797
|
if (data.headings && data.headings.length > 0) {
|
|
605
798
|
lines.push(`Headings (${data.headings.length}):`);
|
|
606
799
|
for (const h of data.headings) {
|
|
@@ -609,7 +802,6 @@ server.tool(
|
|
|
609
802
|
lines.push('');
|
|
610
803
|
}
|
|
611
804
|
|
|
612
|
-
// Interactive elements
|
|
613
805
|
if (data.elements && data.elements.length > 0) {
|
|
614
806
|
lines.push(`Interactive Elements (${data.elements.length}):`);
|
|
615
807
|
for (const el of data.elements) {
|
|
@@ -625,7 +817,6 @@ server.tool(
|
|
|
625
817
|
lines.push('');
|
|
626
818
|
}
|
|
627
819
|
|
|
628
|
-
// Forms
|
|
629
820
|
if (data.forms && data.forms.length > 0) {
|
|
630
821
|
lines.push(`Forms (${data.forms.length}):`);
|
|
631
822
|
for (const f of data.forms) {
|
|
@@ -639,7 +830,6 @@ server.tool(
|
|
|
639
830
|
lines.push('');
|
|
640
831
|
}
|
|
641
832
|
|
|
642
|
-
// Links
|
|
643
833
|
if (data.links && data.links.length > 0) {
|
|
644
834
|
lines.push(`Links (${data.links.length}):`);
|
|
645
835
|
for (const l of data.links) {
|
|
@@ -648,7 +838,6 @@ server.tool(
|
|
|
648
838
|
lines.push('');
|
|
649
839
|
}
|
|
650
840
|
|
|
651
|
-
// Images
|
|
652
841
|
if (data.images && data.images.length > 0) {
|
|
653
842
|
lines.push(`Images (${data.images.length}):`);
|
|
654
843
|
for (const img of data.images) {
|
|
@@ -661,12 +850,7 @@ server.tool(
|
|
|
661
850
|
lines.push(`Duration: ${data.duration_ms}ms`);
|
|
662
851
|
|
|
663
852
|
return {
|
|
664
|
-
content: [
|
|
665
|
-
{
|
|
666
|
-
type: 'text',
|
|
667
|
-
text: lines.join('\n'),
|
|
668
|
-
},
|
|
669
|
-
],
|
|
853
|
+
content: [{ type: 'text', text: lines.join('\n') }],
|
|
670
854
|
};
|
|
671
855
|
} catch (err) {
|
|
672
856
|
return { content: [{ type: 'text', text: `Inspect error: ${err.message}` }], isError: true };
|
|
@@ -674,7 +858,9 @@ server.tool(
|
|
|
674
858
|
}
|
|
675
859
|
);
|
|
676
860
|
|
|
677
|
-
//
|
|
861
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
862
|
+
// Tool: list_devices
|
|
863
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
678
864
|
server.tool(
|
|
679
865
|
'list_devices',
|
|
680
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.',
|
|
@@ -703,7 +889,9 @@ server.tool(
|
|
|
703
889
|
}
|
|
704
890
|
);
|
|
705
891
|
|
|
706
|
-
//
|
|
892
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
893
|
+
// Tool: check_usage
|
|
894
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
707
895
|
server.tool(
|
|
708
896
|
'check_usage',
|
|
709
897
|
'Check your current PageBolt API usage and plan limits.',
|
|
@@ -736,7 +924,6 @@ server.tool(
|
|
|
736
924
|
// ─── Prompts ────────────────────────────────────────────────────
|
|
737
925
|
function registerPrompts(server) {
|
|
738
926
|
|
|
739
|
-
// ── Prompt: capture-page ──────────────────────────────────────
|
|
740
927
|
server.prompt(
|
|
741
928
|
'capture-page',
|
|
742
929
|
'Capture a clean screenshot of any URL with sensible defaults. Optionally inspects the page first.',
|
|
@@ -745,11 +932,20 @@ function registerPrompts(server) {
|
|
|
745
932
|
device: z.string().optional().describe('Device preset, e.g. "iphone_14_pro" or "macbook_pro_14"'),
|
|
746
933
|
dark_mode: z.enum(['true', 'false']).optional().describe('Enable dark mode (default: false)'),
|
|
747
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.'),
|
|
748
941
|
},
|
|
749
942
|
(args) => {
|
|
750
943
|
const device = args.device ? `\n- Use device preset: ${args.device}` : '';
|
|
751
944
|
const dark = args.dark_mode === 'true' ? '\n- Enable dark mode' : '';
|
|
752
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
|
+
: '';
|
|
753
949
|
|
|
754
950
|
return {
|
|
755
951
|
messages: [
|
|
@@ -758,16 +954,15 @@ function registerPrompts(server) {
|
|
|
758
954
|
content: {
|
|
759
955
|
type: 'text',
|
|
760
956
|
text: `Take a clean screenshot of ${args.url} with these settings:
|
|
761
|
-
- Block banners, ads, chats, and trackers for a clean capture${device}${dark}${full}
|
|
957
|
+
- Block banners, ads, chats, and trackers for a clean capture${device}${dark}${full}${style}
|
|
762
958
|
- Use PNG format
|
|
763
|
-
- If the page looks complex or you need to verify elements, run inspect_page first
|
|
764
959
|
|
|
765
960
|
Call take_screenshot with:
|
|
766
961
|
url: "${args.url}"
|
|
767
962
|
blockBanners: true
|
|
768
963
|
blockAds: true
|
|
769
964
|
blockChats: true
|
|
770
|
-
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' : ''}`,
|
|
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}" }` : ''}`,
|
|
771
966
|
},
|
|
772
967
|
},
|
|
773
968
|
],
|
|
@@ -775,7 +970,6 @@ Call take_screenshot with:
|
|
|
775
970
|
}
|
|
776
971
|
);
|
|
777
972
|
|
|
778
|
-
// ── Prompt: record-demo ───────────────────────────────────────
|
|
779
973
|
server.prompt(
|
|
780
974
|
'record-demo',
|
|
781
975
|
'Record a professional demo video of a web page or flow. Generates a step sequence automatically.',
|
|
@@ -784,10 +978,21 @@ Call take_screenshot with:
|
|
|
784
978
|
description: z.string().describe('What the demo should show, e.g. "Sign in and explore the dashboard"'),
|
|
785
979
|
pace: z.enum(['fast', 'normal', 'slow', 'dramatic', 'cinematic']).optional().describe('Video pace preset (default: normal)'),
|
|
786
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)'),
|
|
787
983
|
},
|
|
788
984
|
(args) => {
|
|
789
985
|
const pace = args.pace || 'normal';
|
|
790
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
|
+
: '';
|
|
791
996
|
|
|
792
997
|
return {
|
|
793
998
|
messages: [
|
|
@@ -813,13 +1018,19 @@ Please follow this workflow:
|
|
|
813
1018
|
- format: "${format}"
|
|
814
1019
|
- pace: "${pace}"
|
|
815
1020
|
- blockBanners: true
|
|
816
|
-
- cursor: { style: "
|
|
817
|
-
- clickEffect: { style: "ripple"
|
|
1021
|
+
- cursor: { style: "classic", visible: true, persist: true }
|
|
1022
|
+
- clickEffect: { style: "ripple" }${frameConfig}${bgConfig}
|
|
818
1023
|
|
|
819
1024
|
Important tips:
|
|
820
1025
|
- Use selectors from the inspect_page results — never guess selectors
|
|
821
|
-
- Add
|
|
1026
|
+
- Add wait steps (ms: 800-1200) between interactions for visual clarity
|
|
822
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)
|
|
823
1034
|
- Keep to 15 steps or fewer for best results
|
|
824
1035
|
- Each video costs 3 API requests`,
|
|
825
1036
|
},
|
|
@@ -829,7 +1040,6 @@ Important tips:
|
|
|
829
1040
|
}
|
|
830
1041
|
);
|
|
831
1042
|
|
|
832
|
-
// ── Prompt: audit-page ────────────────────────────────────────
|
|
833
1043
|
server.prompt(
|
|
834
1044
|
'audit-page',
|
|
835
1045
|
'Inspect a page and return a structured analysis of its elements, forms, links, and interactive components.',
|
|
@@ -872,14 +1082,11 @@ Important tips:
|
|
|
872
1082
|
// ─── Resources ──────────────────────────────────────────────────
|
|
873
1083
|
function registerResources(server) {
|
|
874
1084
|
|
|
875
|
-
// ── Resource: pagebolt://api-docs ─────────────────────────────
|
|
876
1085
|
server.resource(
|
|
877
1086
|
'api-docs',
|
|
878
1087
|
'pagebolt://api-docs',
|
|
879
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' },
|
|
880
1089
|
async () => {
|
|
881
|
-
// Serve a comprehensive API reference. In production this is baked in;
|
|
882
|
-
// we could also fetch /llms-full.txt but embedding avoids a network call.
|
|
883
1090
|
try {
|
|
884
1091
|
const res = await fetch(`${BASE_URL}/llms-full.txt`);
|
|
885
1092
|
if (res.ok) {
|
|
@@ -901,7 +1108,7 @@ function registerResources(server) {
|
|
|
901
1108
|
|
|
902
1109
|
} // end registerResources
|
|
903
1110
|
|
|
904
|
-
// ─── Smithery sandbox export
|
|
1111
|
+
// ─── Smithery sandbox export ─────────────────────────────────────
|
|
905
1112
|
export function createSandboxServer() {
|
|
906
1113
|
return createConfiguredServer();
|
|
907
1114
|
}
|