@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.
Files changed (48) hide show
  1. package/LICENSE +15 -0
  2. package/README.md +423 -0
  3. package/browserControl.js +113 -0
  4. package/cli.js +187 -0
  5. package/docs/api/tool-reference.md +317 -0
  6. package/docs/api_tools_usage.md +477 -0
  7. package/docs/development/adding-tools.md +274 -0
  8. package/docs/development/configuration.md +332 -0
  9. package/docs/examples/authentication.md +124 -0
  10. package/docs/examples/basic-automation.md +105 -0
  11. package/docs/getting-started.md +214 -0
  12. package/docs/index.md +61 -0
  13. package/mcpServer.js +280 -0
  14. package/package.json +83 -0
  15. package/run-server.js +140 -0
  16. package/src/config/environments/api-only.js +53 -0
  17. package/src/config/environments/development.js +54 -0
  18. package/src/config/environments/production.js +69 -0
  19. package/src/config/index.js +341 -0
  20. package/src/config/server.js +41 -0
  21. package/src/config/tools/api.js +67 -0
  22. package/src/config/tools/browser.js +90 -0
  23. package/src/config/tools/default.js +32 -0
  24. package/src/services/browserService.js +325 -0
  25. package/src/tools/api/api-request.js +641 -0
  26. package/src/tools/api/api-session-report.js +1262 -0
  27. package/src/tools/api/api-session-status.js +395 -0
  28. package/src/tools/base/ToolBase.js +230 -0
  29. package/src/tools/base/ToolRegistry.js +269 -0
  30. package/src/tools/browser/advanced/browser-console.js +384 -0
  31. package/src/tools/browser/advanced/browser-dialog.js +319 -0
  32. package/src/tools/browser/advanced/browser-evaluate.js +337 -0
  33. package/src/tools/browser/advanced/browser-file.js +480 -0
  34. package/src/tools/browser/advanced/browser-keyboard.js +343 -0
  35. package/src/tools/browser/advanced/browser-mouse.js +332 -0
  36. package/src/tools/browser/advanced/browser-network.js +421 -0
  37. package/src/tools/browser/advanced/browser-pdf.js +407 -0
  38. package/src/tools/browser/advanced/browser-tabs.js +497 -0
  39. package/src/tools/browser/advanced/browser-wait.js +378 -0
  40. package/src/tools/browser/click.js +168 -0
  41. package/src/tools/browser/close.js +60 -0
  42. package/src/tools/browser/dom.js +70 -0
  43. package/src/tools/browser/launch.js +67 -0
  44. package/src/tools/browser/navigate.js +270 -0
  45. package/src/tools/browser/screenshot.js +351 -0
  46. package/src/tools/browser/type.js +174 -0
  47. package/src/tools/index.js +95 -0
  48. 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;