@democratize-quality/mcp-server 1.0.0
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/LICENSE +15 -0
- package/README.md +423 -0
- package/browserControl.js +113 -0
- package/cli.js +187 -0
- package/docs/api/tool-reference.md +317 -0
- package/docs/api_tools_usage.md +477 -0
- package/docs/development/adding-tools.md +274 -0
- package/docs/development/configuration.md +332 -0
- package/docs/examples/authentication.md +124 -0
- package/docs/examples/basic-automation.md +105 -0
- package/docs/getting-started.md +214 -0
- package/docs/index.md +61 -0
- package/mcpServer.js +280 -0
- package/package.json +83 -0
- package/run-server.js +140 -0
- package/src/config/environments/api-only.js +53 -0
- package/src/config/environments/development.js +54 -0
- package/src/config/environments/production.js +69 -0
- package/src/config/index.js +341 -0
- package/src/config/server.js +41 -0
- package/src/config/tools/api.js +67 -0
- package/src/config/tools/browser.js +90 -0
- package/src/config/tools/default.js +32 -0
- package/src/services/browserService.js +325 -0
- package/src/tools/api/api-request.js +641 -0
- package/src/tools/api/api-session-report.js +1262 -0
- package/src/tools/api/api-session-status.js +395 -0
- package/src/tools/base/ToolBase.js +230 -0
- package/src/tools/base/ToolRegistry.js +269 -0
- package/src/tools/browser/advanced/browser-console.js +384 -0
- package/src/tools/browser/advanced/browser-dialog.js +319 -0
- package/src/tools/browser/advanced/browser-evaluate.js +337 -0
- package/src/tools/browser/advanced/browser-file.js +480 -0
- package/src/tools/browser/advanced/browser-keyboard.js +343 -0
- package/src/tools/browser/advanced/browser-mouse.js +332 -0
- package/src/tools/browser/advanced/browser-network.js +421 -0
- package/src/tools/browser/advanced/browser-pdf.js +407 -0
- package/src/tools/browser/advanced/browser-tabs.js +497 -0
- package/src/tools/browser/advanced/browser-wait.js +378 -0
- package/src/tools/browser/click.js +168 -0
- package/src/tools/browser/close.js +60 -0
- package/src/tools/browser/dom.js +70 -0
- package/src/tools/browser/launch.js +67 -0
- package/src/tools/browser/navigate.js +270 -0
- package/src/tools/browser/screenshot.js +351 -0
- package/src/tools/browser/type.js +174 -0
- package/src/tools/index.js +95 -0
- package/src/utils/browserHelpers.js +83 -0
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
const ToolBase = require('../../base/ToolBase');
|
|
2
|
+
const browserService = require('../../../services/browserService');
|
|
3
|
+
const fs = require('fs').promises;
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* PDF Tool - Generate PDF files from web pages
|
|
8
|
+
* Inspired by Playwright MCP PDF capabilities
|
|
9
|
+
*/
|
|
10
|
+
class BrowserPdfTool extends ToolBase {
|
|
11
|
+
static definition = {
|
|
12
|
+
name: "browser_pdf",
|
|
13
|
+
description: "Generate PDF files from web pages with comprehensive formatting options and page control.",
|
|
14
|
+
input_schema: {
|
|
15
|
+
type: "object",
|
|
16
|
+
properties: {
|
|
17
|
+
browserId: {
|
|
18
|
+
type: "string",
|
|
19
|
+
description: "The ID of the browser instance"
|
|
20
|
+
},
|
|
21
|
+
filePath: {
|
|
22
|
+
type: "string",
|
|
23
|
+
description: "Path where the PDF file will be saved"
|
|
24
|
+
},
|
|
25
|
+
options: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {
|
|
28
|
+
format: {
|
|
29
|
+
type: "string",
|
|
30
|
+
enum: ["A3", "A4", "A5", "Legal", "Letter", "Tabloid"],
|
|
31
|
+
default: "A4",
|
|
32
|
+
description: "Paper format"
|
|
33
|
+
},
|
|
34
|
+
width: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Paper width (e.g., '8.5in', '215.9mm')"
|
|
37
|
+
},
|
|
38
|
+
height: {
|
|
39
|
+
type: "string",
|
|
40
|
+
description: "Paper height (e.g., '11in', '279.4mm')"
|
|
41
|
+
},
|
|
42
|
+
landscape: {
|
|
43
|
+
type: "boolean",
|
|
44
|
+
default: false,
|
|
45
|
+
description: "Paper orientation"
|
|
46
|
+
},
|
|
47
|
+
margin: {
|
|
48
|
+
type: "object",
|
|
49
|
+
properties: {
|
|
50
|
+
top: { type: "string", description: "Top margin (e.g., '1in', '2.54cm')" },
|
|
51
|
+
bottom: { type: "string", description: "Bottom margin" },
|
|
52
|
+
left: { type: "string", description: "Left margin" },
|
|
53
|
+
right: { type: "string", description: "Right margin" }
|
|
54
|
+
},
|
|
55
|
+
description: "Page margins"
|
|
56
|
+
},
|
|
57
|
+
printBackground: {
|
|
58
|
+
type: "boolean",
|
|
59
|
+
default: true,
|
|
60
|
+
description: "Whether to print background graphics"
|
|
61
|
+
},
|
|
62
|
+
scale: {
|
|
63
|
+
type: "number",
|
|
64
|
+
minimum: 0.1,
|
|
65
|
+
maximum: 2,
|
|
66
|
+
default: 1,
|
|
67
|
+
description: "Scale factor for the PDF"
|
|
68
|
+
},
|
|
69
|
+
pageRanges: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Paper ranges to print (e.g., '1-5, 8, 11-13')"
|
|
72
|
+
},
|
|
73
|
+
headerTemplate: {
|
|
74
|
+
type: "string",
|
|
75
|
+
description: "HTML template for page header"
|
|
76
|
+
},
|
|
77
|
+
footerTemplate: {
|
|
78
|
+
type: "string",
|
|
79
|
+
description: "HTML template for page footer"
|
|
80
|
+
},
|
|
81
|
+
displayHeaderFooter: {
|
|
82
|
+
type: "boolean",
|
|
83
|
+
default: false,
|
|
84
|
+
description: "Whether to display header and footer"
|
|
85
|
+
},
|
|
86
|
+
preferCSSPageSize: {
|
|
87
|
+
type: "boolean",
|
|
88
|
+
default: false,
|
|
89
|
+
description: "Give preference to CSS page size"
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
description: "PDF generation options"
|
|
93
|
+
},
|
|
94
|
+
waitForSelector: {
|
|
95
|
+
type: "string",
|
|
96
|
+
description: "CSS selector to wait for before generating PDF"
|
|
97
|
+
},
|
|
98
|
+
waitForTimeout: {
|
|
99
|
+
type: "number",
|
|
100
|
+
default: 5000,
|
|
101
|
+
description: "Timeout for waiting in milliseconds"
|
|
102
|
+
},
|
|
103
|
+
generateBase64: {
|
|
104
|
+
type: "boolean",
|
|
105
|
+
default: false,
|
|
106
|
+
description: "Whether to return PDF as base64 string"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
required: ["browserId"]
|
|
110
|
+
},
|
|
111
|
+
output_schema: {
|
|
112
|
+
type: "object",
|
|
113
|
+
properties: {
|
|
114
|
+
success: { type: "boolean", description: "Whether PDF generation was successful" },
|
|
115
|
+
filePath: { type: "string", description: "Path where PDF was saved" },
|
|
116
|
+
size: { type: "number", description: "PDF file size in bytes" },
|
|
117
|
+
base64: { type: "string", description: "PDF content as base64 (if requested)" },
|
|
118
|
+
pages: { type: "number", description: "Number of pages in PDF" },
|
|
119
|
+
options: { type: "object", description: "PDF generation options used" },
|
|
120
|
+
browserId: { type: "string", description: "Browser instance ID" }
|
|
121
|
+
},
|
|
122
|
+
required: ["success", "browserId"]
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
async execute(parameters) {
|
|
127
|
+
const {
|
|
128
|
+
browserId,
|
|
129
|
+
filePath,
|
|
130
|
+
options = {},
|
|
131
|
+
waitForSelector,
|
|
132
|
+
waitForTimeout = 5000,
|
|
133
|
+
generateBase64 = false
|
|
134
|
+
} = parameters;
|
|
135
|
+
|
|
136
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
137
|
+
if (!browser) {
|
|
138
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const client = browser.client;
|
|
142
|
+
|
|
143
|
+
// Enable Page domain
|
|
144
|
+
await client.Page.enable();
|
|
145
|
+
|
|
146
|
+
// Wait for specific element if requested
|
|
147
|
+
if (waitForSelector) {
|
|
148
|
+
await this.waitForSelector(client, waitForSelector, waitForTimeout);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Prepare PDF options
|
|
152
|
+
const pdfOptions = this.preparePdfOptions(options);
|
|
153
|
+
|
|
154
|
+
// Generate filename if not provided
|
|
155
|
+
const outputPath = filePath || this.generateDefaultPath();
|
|
156
|
+
|
|
157
|
+
// Ensure output directory exists
|
|
158
|
+
await this.ensureDirectoryExists(outputPath);
|
|
159
|
+
|
|
160
|
+
try {
|
|
161
|
+
// Generate PDF
|
|
162
|
+
const pdfData = await client.Page.printToPDF(pdfOptions);
|
|
163
|
+
|
|
164
|
+
// Save to file
|
|
165
|
+
const buffer = Buffer.from(pdfData.data, 'base64');
|
|
166
|
+
await fs.writeFile(outputPath, buffer);
|
|
167
|
+
|
|
168
|
+
// Get file stats
|
|
169
|
+
const stats = await fs.stat(outputPath);
|
|
170
|
+
|
|
171
|
+
const result = {
|
|
172
|
+
success: true,
|
|
173
|
+
filePath: outputPath,
|
|
174
|
+
size: stats.size,
|
|
175
|
+
options: pdfOptions,
|
|
176
|
+
browserId: browserId
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// Add base64 if requested
|
|
180
|
+
if (generateBase64) {
|
|
181
|
+
result.base64 = pdfData.data;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Estimate page count (rough calculation)
|
|
185
|
+
result.pages = this.estimatePageCount(stats.size);
|
|
186
|
+
|
|
187
|
+
return result;
|
|
188
|
+
} catch (error) {
|
|
189
|
+
throw new Error(`PDF generation failed: ${error.message}`);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Prepare PDF options for Chrome DevTools Protocol
|
|
195
|
+
*/
|
|
196
|
+
preparePdfOptions(options) {
|
|
197
|
+
const pdfOptions = {
|
|
198
|
+
printBackground: options.printBackground !== undefined ? options.printBackground : true,
|
|
199
|
+
landscape: options.landscape || false,
|
|
200
|
+
scale: options.scale || 1,
|
|
201
|
+
preferCSSPageSize: options.preferCSSPageSize || false,
|
|
202
|
+
displayHeaderFooter: options.displayHeaderFooter || false
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
// Handle paper format or custom dimensions
|
|
206
|
+
if (options.format) {
|
|
207
|
+
pdfOptions.format = options.format;
|
|
208
|
+
} else if (options.width || options.height) {
|
|
209
|
+
if (options.width) pdfOptions.paperWidth = this.convertToInches(options.width);
|
|
210
|
+
if (options.height) pdfOptions.paperHeight = this.convertToInches(options.height);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle margins
|
|
214
|
+
if (options.margin) {
|
|
215
|
+
if (options.margin.top) pdfOptions.marginTop = this.convertToInches(options.margin.top);
|
|
216
|
+
if (options.margin.bottom) pdfOptions.marginBottom = this.convertToInches(options.margin.bottom);
|
|
217
|
+
if (options.margin.left) pdfOptions.marginLeft = this.convertToInches(options.margin.left);
|
|
218
|
+
if (options.margin.right) pdfOptions.marginRight = this.convertToInches(options.margin.right);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Handle page ranges
|
|
222
|
+
if (options.pageRanges) {
|
|
223
|
+
pdfOptions.pageRanges = options.pageRanges;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Handle header/footer templates
|
|
227
|
+
if (options.headerTemplate) {
|
|
228
|
+
pdfOptions.headerTemplate = options.headerTemplate;
|
|
229
|
+
}
|
|
230
|
+
if (options.footerTemplate) {
|
|
231
|
+
pdfOptions.footerTemplate = options.footerTemplate;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
return pdfOptions;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Convert various units to inches for CDP
|
|
239
|
+
*/
|
|
240
|
+
convertToInches(value) {
|
|
241
|
+
if (typeof value === 'number') return value;
|
|
242
|
+
|
|
243
|
+
const numValue = parseFloat(value);
|
|
244
|
+
const unit = value.replace(/[\d.-]/g, '').toLowerCase();
|
|
245
|
+
|
|
246
|
+
switch (unit) {
|
|
247
|
+
case 'in':
|
|
248
|
+
case 'inch':
|
|
249
|
+
case 'inches':
|
|
250
|
+
return numValue;
|
|
251
|
+
case 'cm':
|
|
252
|
+
return numValue / 2.54;
|
|
253
|
+
case 'mm':
|
|
254
|
+
return numValue / 25.4;
|
|
255
|
+
case 'pt':
|
|
256
|
+
return numValue / 72;
|
|
257
|
+
case 'px':
|
|
258
|
+
return numValue / 96; // 96 DPI assumption
|
|
259
|
+
default:
|
|
260
|
+
return numValue; // Assume inches if no unit
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Wait for a specific selector to appear
|
|
266
|
+
*/
|
|
267
|
+
async waitForSelector(client, selector, timeout) {
|
|
268
|
+
const startTime = Date.now();
|
|
269
|
+
|
|
270
|
+
while (Date.now() - startTime < timeout) {
|
|
271
|
+
const result = await client.Runtime.evaluate({
|
|
272
|
+
expression: `document.querySelector('${selector}') !== null`
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
if (result.result.value === true) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
throw new Error(`Selector '${selector}' not found within ${timeout}ms`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Generate default PDF filename
|
|
287
|
+
*/
|
|
288
|
+
generateDefaultPath() {
|
|
289
|
+
const os = require('os');
|
|
290
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
291
|
+
|
|
292
|
+
// Use output directory from environment or default to user home directory
|
|
293
|
+
const defaultOutputDir = process.env.HOME
|
|
294
|
+
? path.join(process.env.HOME, '.mcp-browser-control')
|
|
295
|
+
: path.join(os.tmpdir(), 'mcp-browser-control');
|
|
296
|
+
const outputDir = process.env.OUTPUT_DIR || defaultOutputDir;
|
|
297
|
+
|
|
298
|
+
return path.join(outputDir, `page-${timestamp}.pdf`);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Ensure output directory exists
|
|
303
|
+
*/
|
|
304
|
+
async ensureDirectoryExists(filePath) {
|
|
305
|
+
const dir = path.dirname(filePath);
|
|
306
|
+
try {
|
|
307
|
+
await fs.access(dir);
|
|
308
|
+
} catch {
|
|
309
|
+
await fs.mkdir(dir, { recursive: true });
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Estimate page count from file size (rough calculation)
|
|
315
|
+
*/
|
|
316
|
+
estimatePageCount(fileSize) {
|
|
317
|
+
// Very rough estimation: ~50KB per page average
|
|
318
|
+
return Math.max(1, Math.round(fileSize / 50000));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Create PDF with custom CSS for print
|
|
323
|
+
*/
|
|
324
|
+
async createPdfWithPrintCSS(client, css, pdfOptions) {
|
|
325
|
+
// Inject print-specific CSS
|
|
326
|
+
await client.Runtime.evaluate({
|
|
327
|
+
expression: `
|
|
328
|
+
(function() {
|
|
329
|
+
const style = document.createElement('style');
|
|
330
|
+
style.textContent = \`@media print { ${css} }\`;
|
|
331
|
+
document.head.appendChild(style);
|
|
332
|
+
})()
|
|
333
|
+
`
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// Generate PDF
|
|
337
|
+
return await client.Page.printToPDF(pdfOptions);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Get default header/footer templates
|
|
342
|
+
*/
|
|
343
|
+
getDefaultTemplates() {
|
|
344
|
+
return {
|
|
345
|
+
header: `
|
|
346
|
+
<div style="font-size: 10px; width: 100%; text-align: center; margin: 0;">
|
|
347
|
+
<span class="title"></span>
|
|
348
|
+
</div>
|
|
349
|
+
`,
|
|
350
|
+
footer: `
|
|
351
|
+
<div style="font-size: 10px; width: 100%; text-align: center; margin: 0;">
|
|
352
|
+
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
|
|
353
|
+
</div>
|
|
354
|
+
`
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Generate PDF with table of contents
|
|
360
|
+
*/
|
|
361
|
+
async generatePdfWithTOC(client, pdfOptions) {
|
|
362
|
+
// Extract headings for TOC
|
|
363
|
+
const headings = await client.Runtime.evaluate({
|
|
364
|
+
expression: `
|
|
365
|
+
Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')).map(h => ({
|
|
366
|
+
level: parseInt(h.tagName.slice(1)),
|
|
367
|
+
text: h.textContent.trim(),
|
|
368
|
+
id: h.id || ''
|
|
369
|
+
}))
|
|
370
|
+
`
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// Generate TOC HTML
|
|
374
|
+
const tocHtml = this.generateTOCHtml(headings.result.value);
|
|
375
|
+
|
|
376
|
+
// Insert TOC at beginning of document
|
|
377
|
+
await client.Runtime.evaluate({
|
|
378
|
+
expression: `
|
|
379
|
+
(function() {
|
|
380
|
+
const toc = document.createElement('div');
|
|
381
|
+
toc.innerHTML = \`${tocHtml}\`;
|
|
382
|
+
toc.style.pageBreakAfter = 'always';
|
|
383
|
+
document.body.insertBefore(toc, document.body.firstChild);
|
|
384
|
+
})()
|
|
385
|
+
`
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
return await client.Page.printToPDF(pdfOptions);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Generate Table of Contents HTML
|
|
393
|
+
*/
|
|
394
|
+
generateTOCHtml(headings) {
|
|
395
|
+
let tocHtml = '<div class="table-of-contents"><h2>Table of Contents</h2><ul>';
|
|
396
|
+
|
|
397
|
+
headings.forEach((heading, index) => {
|
|
398
|
+
const indent = ' '.repeat(heading.level - 1);
|
|
399
|
+
tocHtml += `${indent}<li><a href="#${heading.id || `heading-${index}`}">${heading.text}</a></li>`;
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
tocHtml += '</ul></div>';
|
|
403
|
+
return tocHtml;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
module.exports = BrowserPdfTool;
|