pagebolt-mcp 1.6.0 → 1.7.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 +2 -0
- package/package.json +1 -1
- package/src/index.mjs +105 -16
package/README.md
CHANGED
|
@@ -8,6 +8,8 @@ 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
|
+
<img width="1280" height="1279" alt="pagebolt-screenshot_1" src="https://github.com/user-attachments/assets/fd21a372-df4d-41cd-baf4-5b6dd6a9a685" />
|
|
12
|
+
|
|
11
13
|
---
|
|
12
14
|
|
|
13
15
|
## What It Does
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pagebolt-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.1",
|
|
4
4
|
"description": "MCP server for PageBolt — take screenshots, generate PDFs, create OG images, inspect pages, record demo videos with Audio Guide narration, 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
|
@@ -57,7 +57,7 @@ async function callApi(endpoint, options = {}) {
|
|
|
57
57
|
const method = options.method || 'GET';
|
|
58
58
|
const headers = {
|
|
59
59
|
'x-api-key': API_KEY,
|
|
60
|
-
'user-agent': 'pagebolt-mcp/1.
|
|
60
|
+
'user-agent': 'pagebolt-mcp/1.7.0',
|
|
61
61
|
...(options.body ? { 'Content-Type': 'application/json' } : {}),
|
|
62
62
|
};
|
|
63
63
|
|
|
@@ -146,6 +146,8 @@ PageBolt gives you 8 tools for web capture and browser automation. All tools use
|
|
|
146
146
|
| inspect_page | Get structured map of page elements with CSS selectors | 1 request |
|
|
147
147
|
| list_devices | List 25+ device presets (iPhone, iPad, MacBook, etc.) | 0 (free) |
|
|
148
148
|
| check_usage | Check current API usage and plan limits | 0 (free) |
|
|
149
|
+
| create_session | Create a persistent browser session (Starter+ only) | 0 (free to create) |
|
|
150
|
+
| destroy_session | Destroy a persistent browser session | 0 (free) |
|
|
149
151
|
|
|
150
152
|
## Key Workflow: Inspect Before You Interact
|
|
151
153
|
|
|
@@ -168,13 +170,17 @@ record_video supports polished video output:
|
|
|
168
170
|
- frame: { enabled: true, style: "macos" } — browser chrome around the video
|
|
169
171
|
- background: { enabled: true, type: "gradient", gradient: "ocean" } — gradient/glass background with padding
|
|
170
172
|
- cursor: { style: "classic", persist: true } — always-visible cursor
|
|
171
|
-
- Per-step zoom: add zoom: { enabled: true } on click steps
|
|
172
173
|
- **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
174
|
- **Audio Guide**: Add audioGuide: { enabled: true, script: "Welcome. {{1}} Click here. {{2}} Done." } for AI voice narration. Two modes: (1) Per-step — add "narration" text to individual steps. (2) Script — provide a single "script" with {{N}} markers for continuous narration synchronized to steps.
|
|
174
175
|
- Audio Guide voices: ava, andrew, emma, brian, aria, guy, jenny, davis, christopher, michelle (Azure) or alloy, echo, fable, nova, onyx, shimmer (OpenAI).
|
|
175
|
-
- **Live wait steps**: Add live: true to wait steps to capture animated content (transitions, loading spinners) instead of freezing the last frame.
|
|
176
176
|
- **Variables**: Pass variables: { "base_url": "https://example.com" } and use {{base_url}} in step URLs/values for reusable recordings.
|
|
177
177
|
|
|
178
|
+
## IMPORTANT: Video Step Best Practices
|
|
179
|
+
|
|
180
|
+
- **Do NOT add wait steps between every action.** The "pace" parameter already adds natural pauses between steps. Only use wait when: (1) the page needs time to load after navigation, or (2) you want to hold on a view for narration. A typical video should have very few wait steps.
|
|
181
|
+
- **Do NOT use zoom unless the user explicitly asks for it.** Zoom adds visual complexity and encoding time. Omit zoom entirely by default.
|
|
182
|
+
- **Keep videos concise.** A good demo has 5-15 action steps (navigate, click, fill, hover, scroll). More steps = longer encoding time and larger files.
|
|
183
|
+
|
|
178
184
|
## Common Parameters (available on most tools)
|
|
179
185
|
|
|
180
186
|
- blockBanners: true — hides cookie consent banners (GDPR popups, OneTrust, CookieBot, etc.)
|
|
@@ -213,7 +219,7 @@ Use blockBanners on almost every request to get clean captures. Combine blockAds
|
|
|
213
219
|
function createConfiguredServer() {
|
|
214
220
|
const srv = new McpServer({
|
|
215
221
|
name: 'pagebolt',
|
|
216
|
-
version: '1.
|
|
222
|
+
version: '1.7.0',
|
|
217
223
|
}, {
|
|
218
224
|
instructions: SERVER_INSTRUCTIONS,
|
|
219
225
|
});
|
|
@@ -300,6 +306,8 @@ server.tool(
|
|
|
300
306
|
extractMetadata: z.boolean().optional().describe('Extract page metadata (title, description, OG tags) alongside the screenshot'),
|
|
301
307
|
// ── Styling ──
|
|
302
308
|
style: styleSchema,
|
|
309
|
+
// ── Session ──
|
|
310
|
+
session_id: z.string().optional().describe('Persistent session ID (Starter+ only). Reuse a live browser page created with create_session — browser state (cookies, localStorage, auth) carries over from previous requests in this session.'),
|
|
303
311
|
},
|
|
304
312
|
async (params) => {
|
|
305
313
|
if (!params.url && !params.html && !params.markdown) {
|
|
@@ -479,7 +487,7 @@ server.tool(
|
|
|
479
487
|
'screenshot', 'pdf',
|
|
480
488
|
]).describe('The action to perform'),
|
|
481
489
|
url: z.string().url().optional().describe('URL to navigate to (for navigate action)'),
|
|
482
|
-
selector: z.string().optional().describe('CSS selector for the target element'),
|
|
490
|
+
selector: z.string().optional().describe('CSS selector for the target element (also used for element screenshots)'),
|
|
483
491
|
value: z.string().optional().describe('Value to type or select'),
|
|
484
492
|
ms: z.number().int().min(0).max(10000).optional().describe('Milliseconds to wait (for wait action)'),
|
|
485
493
|
timeout: z.number().int().min(0).max(15000).optional().describe('Timeout in ms for wait_for (default: 10000)'),
|
|
@@ -491,7 +499,6 @@ server.tool(
|
|
|
491
499
|
fullPage: z.boolean().optional().describe('Capture full scrollable page (for screenshot action)'),
|
|
492
500
|
fullPageScroll: z.boolean().optional().describe('Auto-scroll for lazy images (for screenshot action)'),
|
|
493
501
|
quality: z.number().int().min(1).max(100).optional().describe('JPEG/WebP quality (for screenshot action)'),
|
|
494
|
-
selector: z.string().optional().describe('Element selector (for screenshot action)'),
|
|
495
502
|
omitBackground: z.boolean().optional().describe('Transparent background (for screenshot action)'),
|
|
496
503
|
delay: z.number().int().min(0).max(10000).optional().describe('Pre-capture delay in ms (for screenshot action)'),
|
|
497
504
|
landscape: z.boolean().optional().describe('Landscape orientation (for pdf action)'),
|
|
@@ -511,6 +518,7 @@ server.tool(
|
|
|
511
518
|
blockChats: z.boolean().optional().describe('Block live chat widgets'),
|
|
512
519
|
blockTrackers: z.boolean().optional().describe('Block tracking scripts'),
|
|
513
520
|
deviceScaleFactor: z.number().min(1).max(3).optional().describe('Device pixel ratio (default: 1)'),
|
|
521
|
+
session_id: z.string().optional().describe('Persistent session ID (Starter+ only). Reuse a live browser page created with create_session — browser state (cookies, localStorage, auth) carries over from previous requests in this session.'),
|
|
514
522
|
},
|
|
515
523
|
async (params) => {
|
|
516
524
|
if (!params.steps || params.steps.length === 0) {
|
|
@@ -565,7 +573,7 @@ server.tool(
|
|
|
565
573
|
// ═══════════════════════════════════════════════════════════════════
|
|
566
574
|
server.tool(
|
|
567
575
|
'record_video',
|
|
568
|
-
'Record a professional demo video of a multi-step browser automation sequence. Produces MP4/WebM/GIF with cursor highlighting, click effects, smooth movement,
|
|
576
|
+
'Record a professional demo video of a multi-step browser automation sequence. Produces MP4/WebM/GIF with cursor highlighting, click effects, smooth movement, step notes, browser frame (macOS/Windows), gradient/glass backgrounds, and more. Costs 3 API requests. Saves to disk. BEST PRACTICE: Keep videos concise (5-15 action steps). Do NOT add wait steps between every action — the pace parameter handles timing. Only use wait for page loads or narration holds. Do NOT use zoom unless the user explicitly asks for it.',
|
|
569
577
|
{
|
|
570
578
|
steps: z.array(
|
|
571
579
|
z.object({
|
|
@@ -576,7 +584,7 @@ server.tool(
|
|
|
576
584
|
url: z.string().url().optional().describe('URL to navigate to (for navigate action)'),
|
|
577
585
|
selector: z.string().optional().describe('CSS selector for the target element'),
|
|
578
586
|
value: z.string().optional().describe('Value to type or select'),
|
|
579
|
-
ms: z.number().int().min(0).max(10000).optional().describe('Milliseconds to wait (for wait action)'),
|
|
587
|
+
ms: z.number().int().min(0).max(10000).optional().describe('Milliseconds to wait (for wait action). Only use wait steps when the page needs loading time or to hold for narration — the pace parameter handles inter-step timing automatically.'),
|
|
580
588
|
timeout: z.number().int().min(0).max(15000).optional().describe('Timeout in ms for wait_for (default: 10000)'),
|
|
581
589
|
x: z.number().optional().describe('Horizontal scroll position'),
|
|
582
590
|
y: z.number().optional().describe('Vertical scroll position'),
|
|
@@ -585,11 +593,11 @@ server.tool(
|
|
|
585
593
|
narration: z.string().max(500).optional().describe('Text to speak at this step (max 500 chars, requires audioGuide.enabled). Used in per-step mode.'),
|
|
586
594
|
live: z.boolean().optional().describe('For wait steps: true captures animated content in real-time, false freezes a single frame (default: false)'),
|
|
587
595
|
zoom: z.object({
|
|
588
|
-
enabled: z.boolean().optional().describe('Enable zoom on this step (default: false)'),
|
|
596
|
+
enabled: z.boolean().optional().describe('Enable zoom on this step (default: false). Only use when user explicitly requests zoom.'),
|
|
589
597
|
level: z.number().min(1.2).max(4).optional().describe('Zoom magnification (inherits from global zoom.level if not set)'),
|
|
590
|
-
}).optional().describe('Per-step zoom override
|
|
598
|
+
}).optional().describe('Per-step zoom override. Do NOT add zoom unless the user specifically requests it — it adds encoding time and visual complexity.'),
|
|
591
599
|
})
|
|
592
|
-
).min(1).max(50).describe('Array of steps to
|
|
600
|
+
).min(1).max(50).describe('Array of action steps to record. Keep concise: 5-15 steps is ideal. Do NOT pad with wait steps — pace handles timing.'),
|
|
593
601
|
viewport: z.object({
|
|
594
602
|
width: z.number().int().min(320).max(3840).optional().describe('Viewport width (default: 1280)'),
|
|
595
603
|
height: z.number().int().min(200).max(2160).optional().describe('Viewport height (default: 720)'),
|
|
@@ -612,8 +620,8 @@ server.tool(
|
|
|
612
620
|
level: z.number().min(1.2).max(4).optional().describe('Default zoom magnification (default: 1.5)'),
|
|
613
621
|
duration: z.number().int().min(400).max(3000).optional().describe('Zoom animation duration in ms (default: 1200)'),
|
|
614
622
|
easing: z.enum(['ease-in-out', 'linear', 'ease']).optional().describe('Zoom animation easing (default: ease-in-out)'),
|
|
615
|
-
}).optional().describe('Global zoom settings.
|
|
616
|
-
autoZoom: z.boolean().optional().describe('
|
|
623
|
+
}).optional().describe('Global zoom settings. Only use when the user explicitly requests zoom. Do NOT enable by default.'),
|
|
624
|
+
autoZoom: z.boolean().optional().describe('Enable auto-zoom on all clicks (default: false). Only use when user explicitly requests zoom.'),
|
|
617
625
|
// ── Click effects ──
|
|
618
626
|
clickEffect: z.object({
|
|
619
627
|
enabled: z.boolean().optional().describe('Show click ripple effects (default: true)'),
|
|
@@ -935,6 +943,87 @@ server.tool(
|
|
|
935
943
|
}
|
|
936
944
|
);
|
|
937
945
|
|
|
946
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
947
|
+
// Tool: create_session — Persistent browser session (Starter+ only)
|
|
948
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
949
|
+
server.tool(
|
|
950
|
+
'create_session',
|
|
951
|
+
'Create a persistent browser session (Starter+ plan required). The session keeps a live browser page open so you can reuse cookies, localStorage, and auth state across multiple take_screenshot or run_sequence calls. Pass the returned session_id to those tools. Sessions expire after 10 minutes of inactivity (hard cap: 30 minutes). Useful for AI agent workflows that log in once and then take multiple screenshots of authenticated pages.',
|
|
952
|
+
{
|
|
953
|
+
cookies: z.array(cookieSchema).optional().describe('Cookies to pre-load into the session browser page'),
|
|
954
|
+
viewport: z.object({
|
|
955
|
+
width: z.number().int().optional(),
|
|
956
|
+
height: z.number().int().optional(),
|
|
957
|
+
}).optional().describe('Viewport dimensions for the session browser page'),
|
|
958
|
+
stealth: z.boolean().optional().describe('Launch this session with stealth mode (bypasses bot detection). Note: stealth sessions use a dedicated browser and consume more memory.'),
|
|
959
|
+
},
|
|
960
|
+
async (params) => {
|
|
961
|
+
const res = await callApi('/api/v1/sessions', {
|
|
962
|
+
method: 'POST',
|
|
963
|
+
body: params,
|
|
964
|
+
});
|
|
965
|
+
const data = await res.json();
|
|
966
|
+
return {
|
|
967
|
+
content: [
|
|
968
|
+
{
|
|
969
|
+
type: 'text',
|
|
970
|
+
text:
|
|
971
|
+
`Session created.\n` +
|
|
972
|
+
` session_id: ${data.session_id}\n` +
|
|
973
|
+
` expires_at: ${data.expires_at}\n\n` +
|
|
974
|
+
`Pass session_id to take_screenshot or run_sequence to reuse this browser page.\n` +
|
|
975
|
+
`Note: ${data.note || 'Sessions do not persist across server restarts.'}`,
|
|
976
|
+
},
|
|
977
|
+
],
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
);
|
|
981
|
+
|
|
982
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
983
|
+
// Tool: list_sessions — List active persistent browser sessions
|
|
984
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
985
|
+
server.tool(
|
|
986
|
+
'list_sessions',
|
|
987
|
+
'List all active persistent browser sessions for your API key. Returns session IDs, creation times, and expiry times. Useful for checking which sessions are still alive before reusing them.',
|
|
988
|
+
{},
|
|
989
|
+
async () => {
|
|
990
|
+
const data = await callApi('/api/v1/sessions', { method: 'GET' });
|
|
991
|
+
const sessions = data.sessions || [];
|
|
992
|
+
if (sessions.length === 0) {
|
|
993
|
+
return { content: [{ type: 'text', text: 'No active sessions.' }] };
|
|
994
|
+
}
|
|
995
|
+
const lines = sessions.map(s =>
|
|
996
|
+
`• ${s.session_id} expires: ${s.expires_at} created: ${s.created_at}`
|
|
997
|
+
);
|
|
998
|
+
return {
|
|
999
|
+
content: [{ type: 'text', text: `Active sessions (${sessions.length}):\n${lines.join('\n')}` }],
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
);
|
|
1003
|
+
|
|
1004
|
+
// Tool: destroy_session — Explicitly close a persistent session
|
|
1005
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
1006
|
+
server.tool(
|
|
1007
|
+
'destroy_session',
|
|
1008
|
+
'Explicitly destroy a persistent browser session before it expires. Frees the browser page immediately. Use this when you are done with a session to free up capacity.',
|
|
1009
|
+
{
|
|
1010
|
+
session_id: z.string().describe('The session ID to destroy (returned by create_session)'),
|
|
1011
|
+
},
|
|
1012
|
+
async (params) => {
|
|
1013
|
+
await callApi(`/api/v1/sessions/${encodeURIComponent(params.session_id)}`, {
|
|
1014
|
+
method: 'DELETE',
|
|
1015
|
+
});
|
|
1016
|
+
return {
|
|
1017
|
+
content: [
|
|
1018
|
+
{
|
|
1019
|
+
type: 'text',
|
|
1020
|
+
text: `Session ${params.session_id} destroyed successfully.`,
|
|
1021
|
+
},
|
|
1022
|
+
],
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
);
|
|
1026
|
+
|
|
938
1027
|
} // end registerTools
|
|
939
1028
|
|
|
940
1029
|
// ─── Prompts ────────────────────────────────────────────────────
|
|
@@ -1039,15 +1128,15 @@ Please follow this workflow:
|
|
|
1039
1128
|
|
|
1040
1129
|
Important tips:
|
|
1041
1130
|
- Use selectors from the inspect_page results — never guess selectors
|
|
1042
|
-
-
|
|
1043
|
-
-
|
|
1131
|
+
- Do NOT add wait steps between every action — the pace parameter already handles timing between steps. Only use wait when: (1) the page needs time to load new content after navigation, or (2) you need to hold on a view for narration.
|
|
1132
|
+
- Do NOT use zoom unless I specifically ask for it
|
|
1044
1133
|
- **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:
|
|
1045
1134
|
- navigate: note: "Opening the dashboard"
|
|
1046
1135
|
- click: note: "This button creates a new project"
|
|
1047
1136
|
- fill: note: "Enter your email to get started"
|
|
1048
1137
|
- hover: note: "Hover to reveal the dropdown menu"
|
|
1049
1138
|
- The ONLY steps without notes should be wait/wait_for (pauses)
|
|
1050
|
-
- Keep to 15 steps
|
|
1139
|
+
- Keep to 5-15 action steps for best results. Fewer steps = faster encoding and smaller files.
|
|
1051
1140
|
- Each video costs 3 API requests`,
|
|
1052
1141
|
},
|
|
1053
1142
|
},
|