@mcp-b/smart-dom-reader-server 5.0.3 → 6.0.0-beta.20261001010549
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/lib/smart-dom-reader.bundle.js +52 -54
- package/package.json +5 -5
- package/src/index.ts +79 -86
|
@@ -128,30 +128,20 @@ var ContentDetection = class ContentDetection {
|
|
|
128
128
|
* Detect page landmarks
|
|
129
129
|
*/
|
|
130
130
|
static detectLandmarks(doc) {
|
|
131
|
-
|
|
132
|
-
navigation: [],
|
|
133
|
-
main: [],
|
|
134
|
-
complementary: [],
|
|
135
|
-
contentinfo: [],
|
|
136
|
-
banner: [],
|
|
137
|
-
search: [],
|
|
138
|
-
form:
|
|
139
|
-
|
|
131
|
+
return {
|
|
132
|
+
navigation: Array.from(doc.querySelectorAll('nav, [role="navigation"]')),
|
|
133
|
+
main: Array.from(doc.querySelectorAll('main, [role="main"]')),
|
|
134
|
+
complementary: Array.from(doc.querySelectorAll('aside, [role="complementary"]')),
|
|
135
|
+
contentinfo: Array.from(doc.querySelectorAll('footer, [role="contentinfo"]')),
|
|
136
|
+
banner: Array.from(doc.querySelectorAll('header, [role="banner"]')),
|
|
137
|
+
search: Array.from(doc.querySelectorAll('[role="search"]')),
|
|
138
|
+
form: Array.from(
|
|
139
|
+
doc.querySelectorAll('form[aria-label], form[aria-labelledby], [role="form"]')
|
|
140
|
+
),
|
|
141
|
+
region: Array.from(
|
|
142
|
+
doc.querySelectorAll('section[aria-label], section[aria-labelledby], [role="region"]')
|
|
143
|
+
),
|
|
140
144
|
};
|
|
141
|
-
for (const [landmark, selector] of Object.entries({
|
|
142
|
-
navigation: 'nav, [role="navigation"]',
|
|
143
|
-
main: 'main, [role="main"]',
|
|
144
|
-
complementary: 'aside, [role="complementary"]',
|
|
145
|
-
contentinfo: 'footer, [role="contentinfo"]',
|
|
146
|
-
banner: 'header, [role="banner"]',
|
|
147
|
-
search: '[role="search"]',
|
|
148
|
-
form: 'form[aria-label], form[aria-labelledby], [role="form"]',
|
|
149
|
-
region: 'section[aria-label], section[aria-labelledby], [role="region"]',
|
|
150
|
-
})) {
|
|
151
|
-
const elements = doc.querySelectorAll(selector);
|
|
152
|
-
landmarks[landmark] = Array.from(elements);
|
|
153
|
-
}
|
|
154
|
-
return landmarks;
|
|
155
145
|
}
|
|
156
146
|
};
|
|
157
147
|
//#endregion
|
|
@@ -243,8 +233,7 @@ function selectorQualitySummary(inter) {
|
|
|
243
233
|
}
|
|
244
234
|
function renderInteractive(inter, opts) {
|
|
245
235
|
const parts = [];
|
|
246
|
-
const limit = (arr) =>
|
|
247
|
-
typeof opts?.maxElements === 'number' ? arr.slice(0, opts.maxElements) : arr;
|
|
236
|
+
const limit = (arr) => arr.slice(0, opts?.maxElements ?? arr.length);
|
|
248
237
|
if (inter.buttons.length) {
|
|
249
238
|
parts.push('Buttons:');
|
|
250
239
|
for (const el of limit(inter.buttons)) parts.push(elementLine(el, opts));
|
|
@@ -377,8 +366,7 @@ var MarkdownFormatter = class {
|
|
|
377
366
|
lines.push('');
|
|
378
367
|
}
|
|
379
368
|
if (content.text.paragraphs?.length) {
|
|
380
|
-
const limit =
|
|
381
|
-
typeof opts.maxElements === 'number' ? opts.maxElements : content.text.paragraphs.length;
|
|
369
|
+
const limit = opts.maxElements ?? content.text.paragraphs.length;
|
|
382
370
|
lines.push('Paragraphs:');
|
|
383
371
|
for (const p of content.text.paragraphs.slice(0, limit))
|
|
384
372
|
lines.push(`- ${truncate(p, opts.maxTextLength ?? 200)}`);
|
|
@@ -388,7 +376,7 @@ var MarkdownFormatter = class {
|
|
|
388
376
|
lines.push('Lists:');
|
|
389
377
|
for (const list of content.text.lists) {
|
|
390
378
|
lines.push(`- ${list.type.toUpperCase()}:`);
|
|
391
|
-
const limit =
|
|
379
|
+
const limit = opts.maxElements ?? list.items.length;
|
|
392
380
|
for (const item of list.items.slice(0, limit))
|
|
393
381
|
lines.push(` - ${truncate(item, opts.maxTextLength ?? 120)}`);
|
|
394
382
|
}
|
|
@@ -398,14 +386,14 @@ var MarkdownFormatter = class {
|
|
|
398
386
|
lines.push('Tables:');
|
|
399
387
|
for (const t of content.tables) {
|
|
400
388
|
lines.push(`- Headers: ${t.headers.join(' | ')}`);
|
|
401
|
-
const limit =
|
|
389
|
+
const limit = opts.maxElements ?? t.rows.length;
|
|
402
390
|
for (const row of t.rows.slice(0, limit)) lines.push(` - ${row.join(' | ')}`);
|
|
403
391
|
}
|
|
404
392
|
lines.push('');
|
|
405
393
|
}
|
|
406
394
|
if (content.media?.length) {
|
|
407
395
|
lines.push('Media:');
|
|
408
|
-
const limit =
|
|
396
|
+
const limit = opts.maxElements ?? content.media.length;
|
|
409
397
|
for (const m of content.media.slice(0, limit))
|
|
410
398
|
lines.push(`- ${m.type.toUpperCase()}: ${m.alt ?? ''} ${m.src ? `→ ${m.src}` : ''}`.trim());
|
|
411
399
|
lines.push('');
|
|
@@ -647,8 +635,8 @@ var SelectorGenerator = class SelectorGenerator {
|
|
|
647
635
|
const tag = current.nodeName.toLowerCase();
|
|
648
636
|
let descriptor = tag;
|
|
649
637
|
if (current.id) descriptor = `${tag}#${current.id}`;
|
|
650
|
-
else
|
|
651
|
-
const firstClass = current.
|
|
638
|
+
else {
|
|
639
|
+
const firstClass = current.getAttribute('class')?.split(' ')[0];
|
|
652
640
|
if (firstClass) descriptor = `${tag}.${firstClass}`;
|
|
653
641
|
}
|
|
654
642
|
const role = current.getAttribute('role');
|
|
@@ -743,11 +731,9 @@ var DOMTraversal = class DOMTraversal {
|
|
|
743
731
|
for (const [attr, value] of Object.entries(filter.attributeValues)) {
|
|
744
732
|
const attrValue = element.getAttribute(attr);
|
|
745
733
|
if (!attrValue) return false;
|
|
746
|
-
if (
|
|
747
|
-
if (attrValue !== value) return false;
|
|
748
|
-
} else if (value instanceof RegExp) {
|
|
734
|
+
if (value instanceof RegExp) {
|
|
749
735
|
if (!value.test(attrValue)) return false;
|
|
750
|
-
}
|
|
736
|
+
} else if (attrValue !== value) return false;
|
|
751
737
|
}
|
|
752
738
|
if (filter.withinSelectors?.length) {
|
|
753
739
|
let isWithin = false;
|
|
@@ -884,21 +870,25 @@ var DOMTraversal = class DOMTraversal {
|
|
|
884
870
|
* Get interaction information for an element (compact format)
|
|
885
871
|
*/
|
|
886
872
|
static getInteractionInfo(element) {
|
|
887
|
-
const htmlElement = element;
|
|
888
873
|
const interaction = {};
|
|
874
|
+
const view = element.ownerDocument.defaultView ?? window;
|
|
889
875
|
if (
|
|
890
|
-
|
|
876
|
+
('onclick' in element && element.onclick) ||
|
|
891
877
|
element.getAttribute('onclick') ||
|
|
892
878
|
element.matches('button, a[href], [role="button"], [tabindex]:not([tabindex="-1"])')
|
|
893
879
|
)
|
|
894
880
|
interaction.click = true;
|
|
895
881
|
if (
|
|
896
|
-
|
|
882
|
+
('onchange' in element && element.onchange) ||
|
|
897
883
|
element.getAttribute('onchange') ||
|
|
898
884
|
element.matches('input, select, textarea')
|
|
899
885
|
)
|
|
900
886
|
interaction.change = true;
|
|
901
|
-
if (
|
|
887
|
+
if (
|
|
888
|
+
('onsubmit' in element && element.onsubmit) ||
|
|
889
|
+
element.getAttribute('onsubmit') ||
|
|
890
|
+
element.matches('form')
|
|
891
|
+
)
|
|
902
892
|
interaction.submit = true;
|
|
903
893
|
if (element.matches('a[href], button[type="submit"]')) interaction.nav = true;
|
|
904
894
|
if (element.hasAttribute('disabled') || element.getAttribute('aria-disabled') === 'true')
|
|
@@ -907,7 +897,13 @@ var DOMTraversal = class DOMTraversal {
|
|
|
907
897
|
const ariaRole = element.getAttribute('role');
|
|
908
898
|
if (ariaRole) interaction.role = ariaRole;
|
|
909
899
|
if (element.matches('input, textarea, select, button')) {
|
|
910
|
-
const form =
|
|
900
|
+
const form =
|
|
901
|
+
(element instanceof view.HTMLInputElement ||
|
|
902
|
+
element instanceof view.HTMLTextAreaElement ||
|
|
903
|
+
element instanceof view.HTMLSelectElement ||
|
|
904
|
+
element instanceof view.HTMLButtonElement
|
|
905
|
+
? element.form
|
|
906
|
+
: null) || element.closest('form');
|
|
911
907
|
if (form) interaction.form = SelectorGenerator.generateSelectors(form).css;
|
|
912
908
|
}
|
|
913
909
|
return interaction;
|
|
@@ -916,11 +912,13 @@ var DOMTraversal = class DOMTraversal {
|
|
|
916
912
|
* Get text content of an element (limited length)
|
|
917
913
|
*/
|
|
918
914
|
static getElementText(element, options) {
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
return
|
|
922
|
-
|
|
923
|
-
|
|
915
|
+
const view = element.ownerDocument.defaultView ?? window;
|
|
916
|
+
if (element.matches('input, textarea'))
|
|
917
|
+
return element instanceof view.HTMLInputElement || element instanceof view.HTMLTextAreaElement
|
|
918
|
+
? element.value || element.placeholder || ''
|
|
919
|
+
: '';
|
|
920
|
+
if (element.matches('img'))
|
|
921
|
+
return element instanceof view.HTMLImageElement ? element.alt || '' : '';
|
|
924
922
|
const text = element.textContent?.trim() || '';
|
|
925
923
|
const maxLength = options?.textTruncateLength;
|
|
926
924
|
if (maxLength && text.length > maxLength) return `${text.substring(0, maxLength)}...`;
|
|
@@ -1031,13 +1029,13 @@ var SmartDOMReader = class SmartDOMReader {
|
|
|
1031
1029
|
extractLandmarks(doc) {
|
|
1032
1030
|
const detected = ContentDetection.detectLandmarks(doc);
|
|
1033
1031
|
return {
|
|
1034
|
-
navigation: this.elementsToSelectors(detected.navigation
|
|
1035
|
-
main: this.elementsToSelectors(detected.main
|
|
1036
|
-
forms: this.elementsToSelectors(detected.form
|
|
1037
|
-
headers: this.elementsToSelectors(detected.banner
|
|
1038
|
-
footers: this.elementsToSelectors(detected.contentinfo
|
|
1039
|
-
articles: this.elementsToSelectors(detected.region
|
|
1040
|
-
sections: this.elementsToSelectors(detected.region
|
|
1032
|
+
navigation: this.elementsToSelectors(detected.navigation),
|
|
1033
|
+
main: this.elementsToSelectors(detected.main),
|
|
1034
|
+
forms: this.elementsToSelectors(detected.form),
|
|
1035
|
+
headers: this.elementsToSelectors(detected.banner),
|
|
1036
|
+
footers: this.elementsToSelectors(detected.contentinfo),
|
|
1037
|
+
articles: this.elementsToSelectors(detected.region),
|
|
1038
|
+
sections: this.elementsToSelectors(detected.region),
|
|
1041
1039
|
};
|
|
1042
1040
|
}
|
|
1043
1041
|
/**
|
|
@@ -1321,7 +1319,7 @@ var ProgressiveExtractor = class ProgressiveExtractor {
|
|
|
1321
1319
|
if (options.includeLists !== false) {
|
|
1322
1320
|
const lists = element.querySelectorAll('ul, ol');
|
|
1323
1321
|
result.text.lists = Array.from(lists).map((list) => ({
|
|
1324
|
-
type: list.tagName.toLowerCase(),
|
|
1322
|
+
type: list.tagName.toLowerCase() === 'ul' ? 'ul' : 'ol',
|
|
1325
1323
|
items: Array.from(list.querySelectorAll('li')).map((li) =>
|
|
1326
1324
|
ProgressiveExtractor.getTextContent(li, options.maxTextLength)
|
|
1327
1325
|
),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mcp-b/smart-dom-reader-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "6.0.0-beta.20261001010549",
|
|
4
4
|
"description": "MCP server for smart-dom-reader backed by Playwright",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
"main": "src/index.ts",
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"@modelcontextprotocol/server": "2.0.0",
|
|
22
|
-
"playwright": "^1.
|
|
23
|
-
"tsx": "^4.
|
|
22
|
+
"playwright": "^1.62.1",
|
|
23
|
+
"tsx": "^4.23.12",
|
|
24
24
|
"zod": "4.4.3"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
|
-
"@types/node": "22.
|
|
27
|
+
"@types/node": "22.20.1",
|
|
28
28
|
"typescript": "^5.8.3",
|
|
29
|
-
"@mcp-b/smart-dom-reader": "
|
|
29
|
+
"@mcp-b/smart-dom-reader": "6.0.0-beta.20261001010549"
|
|
30
30
|
},
|
|
31
31
|
"engines": {
|
|
32
32
|
"node": ">=20"
|
package/src/index.ts
CHANGED
|
@@ -19,8 +19,9 @@ const { F_OK } = fsConstants;
|
|
|
19
19
|
|
|
20
20
|
const __filename = fileURLToPath(import.meta.url);
|
|
21
21
|
const __dirname = dirname(__filename);
|
|
22
|
-
const packageVersion =
|
|
23
|
-
.version
|
|
22
|
+
const packageVersion = z
|
|
23
|
+
.object({ version: z.string() })
|
|
24
|
+
.parse(createRequire(import.meta.url)('../package.json')).version;
|
|
24
25
|
|
|
25
26
|
const EMBEDDED_LIBRARY_RELATIVE_PATH = join('..', 'lib', 'smart-dom-reader.bundle.js');
|
|
26
27
|
const DEFAULT_LAUNCH_ARGS = ['--no-sandbox', '--disable-setuid-sandbox'] as const;
|
|
@@ -61,7 +62,11 @@ type ScreenshotArgs = {
|
|
|
61
62
|
fullPage: boolean;
|
|
62
63
|
};
|
|
63
64
|
|
|
64
|
-
type
|
|
65
|
+
type LibraryRequest =
|
|
66
|
+
| [operation: 'structure', args: StructureOperationArgs & { format: FormatOptions }]
|
|
67
|
+
| [operation: 'region', args: RegionOperationArgs & { format: FormatOptions }]
|
|
68
|
+
| [operation: 'content', args: ContentOperationArgs & { format: FormatOptions }]
|
|
69
|
+
| [operation: 'interactive', args: InteractiveOperationArgs & { format: FormatOptions }];
|
|
65
70
|
|
|
66
71
|
type StructureOperationArgs = {
|
|
67
72
|
selector: string | null;
|
|
@@ -84,32 +89,41 @@ type InteractiveOperationArgs = {
|
|
|
84
89
|
|
|
85
90
|
const CHROME_ENV_VARS = ['CHROME_PATH', 'GOOGLE_CHROME_SHIM', 'BROWSER_PATH'] as const;
|
|
86
91
|
|
|
87
|
-
const DEFAULT_CHROME_LOCATIONS
|
|
88
|
-
|
|
89
|
-
'
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
92
|
+
const DEFAULT_CHROME_LOCATIONS = new Map<NodeJS.Platform, readonly string[]>([
|
|
93
|
+
[
|
|
94
|
+
'darwin',
|
|
95
|
+
[
|
|
96
|
+
'/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
|
|
97
|
+
'/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
|
|
98
|
+
'/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
|
|
99
|
+
'/Applications/Chromium.app/Contents/MacOS/Chromium',
|
|
100
|
+
],
|
|
93
101
|
],
|
|
94
|
-
|
|
95
|
-
'
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
102
|
+
[
|
|
103
|
+
'linux',
|
|
104
|
+
[
|
|
105
|
+
'/usr/bin/google-chrome',
|
|
106
|
+
'/usr/bin/google-chrome-stable',
|
|
107
|
+
'/usr/bin/chromium-browser',
|
|
108
|
+
'/usr/bin/chromium',
|
|
109
|
+
'/snap/bin/chromium',
|
|
110
|
+
],
|
|
100
111
|
],
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
112
|
+
[
|
|
113
|
+
'win32',
|
|
114
|
+
[
|
|
115
|
+
...(process.env.PROGRAMFILES
|
|
116
|
+
? [join(process.env.PROGRAMFILES, 'Google/Chrome/Application/chrome.exe')]
|
|
117
|
+
: []),
|
|
118
|
+
...(process.env['ProgramFiles(x86)']
|
|
119
|
+
? [join(process.env['ProgramFiles(x86)'], 'Google/Chrome/Application/chrome.exe')]
|
|
120
|
+
: []),
|
|
121
|
+
...(process.env.LOCALAPPDATA
|
|
122
|
+
? [join(process.env.LOCALAPPDATA, 'Google/Chrome/Application/chrome.exe')]
|
|
123
|
+
: []),
|
|
124
|
+
],
|
|
111
125
|
],
|
|
112
|
-
|
|
126
|
+
]);
|
|
113
127
|
|
|
114
128
|
interface ProgressiveExtractorRegionConfig {
|
|
115
129
|
mode?: 'interactive' | 'full' | undefined;
|
|
@@ -135,7 +149,7 @@ interface FormatOptions {
|
|
|
135
149
|
maxElements?: number | undefined;
|
|
136
150
|
}
|
|
137
151
|
|
|
138
|
-
const
|
|
152
|
+
const COMMON_TOOL_OPTIONS = {
|
|
139
153
|
maxDepth: z
|
|
140
154
|
.number()
|
|
141
155
|
.int()
|
|
@@ -167,11 +181,7 @@ type LibraryCache = {
|
|
|
167
181
|
mtimeMs: number;
|
|
168
182
|
};
|
|
169
183
|
|
|
170
|
-
async function pathExists(candidate: string
|
|
171
|
-
if (!candidate) {
|
|
172
|
-
return false;
|
|
173
|
-
}
|
|
174
|
-
|
|
184
|
+
async function pathExists(candidate: string): Promise<boolean> {
|
|
175
185
|
try {
|
|
176
186
|
await access(candidate, F_OK);
|
|
177
187
|
return true;
|
|
@@ -325,7 +335,7 @@ class SmartDomReaderServer {
|
|
|
325
335
|
.boolean()
|
|
326
336
|
.describe('Include elements that are hidden/offscreen. Default: false.')
|
|
327
337
|
.optional(),
|
|
328
|
-
...
|
|
338
|
+
...COMMON_TOOL_OPTIONS,
|
|
329
339
|
})
|
|
330
340
|
.optional(),
|
|
331
341
|
}),
|
|
@@ -402,7 +412,7 @@ class SmartDomReaderServer {
|
|
|
402
412
|
.boolean()
|
|
403
413
|
.default(false)
|
|
404
414
|
.describe('Only include elements currently within the viewport. Default: false.'),
|
|
405
|
-
...
|
|
415
|
+
...COMMON_TOOL_OPTIONS,
|
|
406
416
|
})
|
|
407
417
|
.optional(),
|
|
408
418
|
}),
|
|
@@ -500,12 +510,12 @@ class SmartDomReaderServer {
|
|
|
500
510
|
|
|
501
511
|
for (const envVar of CHROME_ENV_VARS) {
|
|
502
512
|
const envPath = process.env[envVar];
|
|
503
|
-
if (await pathExists(envPath)) {
|
|
504
|
-
return { path: resolve(envPath
|
|
513
|
+
if (envPath && (await pathExists(envPath))) {
|
|
514
|
+
return { path: resolve(envPath), source: `env:${envVar}` };
|
|
505
515
|
}
|
|
506
516
|
}
|
|
507
517
|
|
|
508
|
-
const candidates = DEFAULT_CHROME_LOCATIONS
|
|
518
|
+
const candidates = DEFAULT_CHROME_LOCATIONS.get(process.platform) ?? [];
|
|
509
519
|
for (const candidate of candidates) {
|
|
510
520
|
if (await pathExists(candidate)) {
|
|
511
521
|
return { path: resolve(candidate), source: 'default' };
|
|
@@ -529,10 +539,7 @@ class SmartDomReaderServer {
|
|
|
529
539
|
private async extractStructure(args: OptionalSelectorArgs): Promise<CallToolResult> {
|
|
530
540
|
const started = Date.now();
|
|
531
541
|
try {
|
|
532
|
-
const text = await this.runLibraryOperation
|
|
533
|
-
string,
|
|
534
|
-
StructureOperationArgs & { format: FormatOptions }
|
|
535
|
-
>('structure', {
|
|
542
|
+
const text = await this.runLibraryOperation('structure', {
|
|
536
543
|
selector: args.selector && args.selector.trim().length > 0 ? args.selector : null,
|
|
537
544
|
format: {
|
|
538
545
|
detail: args.detail ?? 'summary',
|
|
@@ -550,10 +557,7 @@ class SmartDomReaderServer {
|
|
|
550
557
|
private async extractRegion(args: RegionArgs): Promise<CallToolResult> {
|
|
551
558
|
const started = Date.now();
|
|
552
559
|
try {
|
|
553
|
-
const text = await this.runLibraryOperation
|
|
554
|
-
string,
|
|
555
|
-
RegionOperationArgs & { format: FormatOptions }
|
|
556
|
-
>('region', {
|
|
560
|
+
const text = await this.runLibraryOperation('region', {
|
|
557
561
|
selector: args.selector,
|
|
558
562
|
options: args.options ?? {},
|
|
559
563
|
format: {
|
|
@@ -574,10 +578,7 @@ class SmartDomReaderServer {
|
|
|
574
578
|
private async extractContent(args: ContentArgs): Promise<CallToolResult> {
|
|
575
579
|
const started = Date.now();
|
|
576
580
|
try {
|
|
577
|
-
const text = await this.runLibraryOperation
|
|
578
|
-
string,
|
|
579
|
-
ContentOperationArgs & { format: FormatOptions }
|
|
580
|
-
>('content', {
|
|
581
|
+
const text = await this.runLibraryOperation('content', {
|
|
581
582
|
selector: args.selector,
|
|
582
583
|
options: args.options ?? {},
|
|
583
584
|
format: {
|
|
@@ -598,10 +599,7 @@ class SmartDomReaderServer {
|
|
|
598
599
|
private async extractInteractive(args: InteractiveArgs): Promise<CallToolResult> {
|
|
599
600
|
const started = Date.now();
|
|
600
601
|
try {
|
|
601
|
-
const text = await this.runLibraryOperation
|
|
602
|
-
string,
|
|
603
|
-
InteractiveOperationArgs & { format: FormatOptions }
|
|
604
|
-
>('interactive', {
|
|
602
|
+
const text = await this.runLibraryOperation('interactive', {
|
|
605
603
|
selector: args.selector && args.selector.trim().length > 0 ? args.selector : null,
|
|
606
604
|
options: args.options ?? {},
|
|
607
605
|
format: {
|
|
@@ -661,15 +659,12 @@ class SmartDomReaderServer {
|
|
|
661
659
|
* All formatting happens in-page via the bundled library; the server never
|
|
662
660
|
* imports it, so there are no server-side dynamic imports.
|
|
663
661
|
*/
|
|
664
|
-
private async runLibraryOperation<
|
|
665
|
-
TResult,
|
|
666
|
-
TArgs extends Record<string, unknown> & { format: FormatOptions },
|
|
667
|
-
>(operation: LibraryOperation, args: TArgs): Promise<TResult> {
|
|
662
|
+
private async runLibraryOperation(...request: LibraryRequest): Promise<string> {
|
|
668
663
|
const page = this.getActivePage();
|
|
669
664
|
const code = await this.readLibraryFile(resolve(__dirname, EMBEDDED_LIBRARY_RELATIVE_PATH));
|
|
670
665
|
|
|
671
|
-
|
|
672
|
-
async ({ code, operation, args }) => {
|
|
666
|
+
const markdown = await page.evaluate<unknown, { code: string; request: LibraryRequest }>(
|
|
667
|
+
async ({ code, request: [operation, args] }) => {
|
|
673
668
|
const blob = new Blob([code], { type: 'text/javascript' });
|
|
674
669
|
const url = URL.createObjectURL(blob);
|
|
675
670
|
|
|
@@ -687,12 +682,12 @@ class SmartDomReaderServer {
|
|
|
687
682
|
throw new Error('ProgressiveExtractor export is unavailable.');
|
|
688
683
|
}
|
|
689
684
|
|
|
690
|
-
const { selector } = args
|
|
685
|
+
const { selector } = args;
|
|
691
686
|
const target = selector ? document.querySelector(selector) : document;
|
|
692
687
|
if (!target) throw new Error(`No matching element for selector ${selector}`);
|
|
693
688
|
const overview = ProgressiveExtractor.extractStructure(target);
|
|
694
689
|
if (!MarkdownFormatter) throw new Error('MarkdownFormatter export is unavailable.');
|
|
695
|
-
return MarkdownFormatter.structure(overview, fmt, meta)
|
|
690
|
+
return MarkdownFormatter.structure(overview, fmt, meta);
|
|
696
691
|
}
|
|
697
692
|
|
|
698
693
|
case 'region': {
|
|
@@ -700,12 +695,11 @@ class SmartDomReaderServer {
|
|
|
700
695
|
throw new Error('ProgressiveExtractor export is unavailable.');
|
|
701
696
|
}
|
|
702
697
|
|
|
703
|
-
const { selector, options } = args
|
|
698
|
+
const { selector, options } = args;
|
|
704
699
|
const result = ProgressiveExtractor.extractRegion(selector, document, options ?? {});
|
|
705
|
-
if (!result)
|
|
706
|
-
return `No matching region for selector ${selector}` as unknown as TResult;
|
|
700
|
+
if (!result) return `No matching region for selector ${selector}`;
|
|
707
701
|
if (!MarkdownFormatter) throw new Error('MarkdownFormatter export is unavailable.');
|
|
708
|
-
return MarkdownFormatter.region(result, fmt, meta)
|
|
702
|
+
return MarkdownFormatter.region(result, fmt, meta);
|
|
709
703
|
}
|
|
710
704
|
|
|
711
705
|
case 'content': {
|
|
@@ -713,15 +707,15 @@ class SmartDomReaderServer {
|
|
|
713
707
|
throw new Error('ProgressiveExtractor export is unavailable.');
|
|
714
708
|
}
|
|
715
709
|
|
|
716
|
-
const { selector, options } = args
|
|
710
|
+
const { selector, options } = args;
|
|
717
711
|
const content = ProgressiveExtractor.extractContent(
|
|
718
712
|
selector,
|
|
719
713
|
document,
|
|
720
714
|
options ?? {}
|
|
721
715
|
);
|
|
722
|
-
if (!content) return `No content for selector ${selector}
|
|
716
|
+
if (!content) return `No content for selector ${selector}`;
|
|
723
717
|
if (!MarkdownFormatter) throw new Error('MarkdownFormatter export is unavailable.');
|
|
724
|
-
return MarkdownFormatter.content(content, fmt, meta)
|
|
718
|
+
return MarkdownFormatter.content(content, fmt, meta);
|
|
725
719
|
}
|
|
726
720
|
|
|
727
721
|
case 'interactive': {
|
|
@@ -729,20 +723,20 @@ class SmartDomReaderServer {
|
|
|
729
723
|
throw new Error('SmartDOMReader export is unavailable.');
|
|
730
724
|
}
|
|
731
725
|
|
|
732
|
-
const { selector, options } = args
|
|
726
|
+
const { selector, options } = args;
|
|
733
727
|
const target = selector ? document.querySelector(selector) : document;
|
|
734
728
|
if (!target) throw new Error(`No matching element for selector ${selector}`);
|
|
735
729
|
|
|
736
730
|
if (typeof SmartDOMReader.extractInteractive === 'function') {
|
|
737
731
|
const result = SmartDOMReader.extractInteractive(target, options ?? {});
|
|
738
732
|
if (!MarkdownFormatter) throw new Error('MarkdownFormatter export is unavailable.');
|
|
739
|
-
return MarkdownFormatter.region(result, fmt, meta)
|
|
733
|
+
return MarkdownFormatter.region(result, fmt, meta);
|
|
740
734
|
}
|
|
741
735
|
|
|
742
736
|
const reader = new SmartDOMReader({ ...options, mode: 'interactive' });
|
|
743
737
|
const result = reader.extract(target, options ?? {});
|
|
744
738
|
if (!MarkdownFormatter) throw new Error('MarkdownFormatter export is unavailable.');
|
|
745
|
-
return MarkdownFormatter.region(result, fmt, meta)
|
|
739
|
+
return MarkdownFormatter.region(result, fmt, meta);
|
|
746
740
|
}
|
|
747
741
|
|
|
748
742
|
default: {
|
|
@@ -753,8 +747,12 @@ class SmartDomReaderServer {
|
|
|
753
747
|
URL.revokeObjectURL(url);
|
|
754
748
|
}
|
|
755
749
|
},
|
|
756
|
-
{ code,
|
|
750
|
+
{ code, request }
|
|
757
751
|
);
|
|
752
|
+
if (typeof markdown !== 'string') {
|
|
753
|
+
throw new TypeError('The embedded library returned a non-string result.');
|
|
754
|
+
}
|
|
755
|
+
return markdown;
|
|
758
756
|
}
|
|
759
757
|
|
|
760
758
|
private getActivePage(): Page {
|
|
@@ -773,12 +771,7 @@ class SmartDomReaderServer {
|
|
|
773
771
|
try {
|
|
774
772
|
file = await open(resolvedPath, 'r');
|
|
775
773
|
} catch (error) {
|
|
776
|
-
if (
|
|
777
|
-
typeof error === 'object' &&
|
|
778
|
-
error !== null &&
|
|
779
|
-
'code' in error &&
|
|
780
|
-
error.code === 'ENOENT'
|
|
781
|
-
) {
|
|
774
|
+
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
782
775
|
throw new ProtocolError(
|
|
783
776
|
ProtocolErrorCode.InvalidParams,
|
|
784
777
|
`Embedded library file not found at ${resolvedPath}. Ensure the bundled file exists.`
|
|
@@ -802,14 +795,14 @@ class SmartDomReaderServer {
|
|
|
802
795
|
}
|
|
803
796
|
}
|
|
804
797
|
|
|
805
|
-
private handleToolError(
|
|
806
|
-
if (
|
|
807
|
-
throw
|
|
798
|
+
private handleToolError(cause: unknown, fallbackMessage: string): never {
|
|
799
|
+
if (cause instanceof ProtocolError) {
|
|
800
|
+
throw cause;
|
|
808
801
|
}
|
|
809
802
|
|
|
810
|
-
if (
|
|
811
|
-
console.error(fallbackMessage,
|
|
812
|
-
throw new ProtocolError(ProtocolErrorCode.InternalError,
|
|
803
|
+
if (cause instanceof Error) {
|
|
804
|
+
console.error(fallbackMessage, cause);
|
|
805
|
+
throw new ProtocolError(ProtocolErrorCode.InternalError, cause.message);
|
|
813
806
|
}
|
|
814
807
|
|
|
815
808
|
throw new ProtocolError(ProtocolErrorCode.InternalError, fallbackMessage);
|
|
@@ -817,7 +810,7 @@ class SmartDomReaderServer {
|
|
|
817
810
|
}
|
|
818
811
|
|
|
819
812
|
const server = new SmartDomReaderServer();
|
|
820
|
-
server.run().catch((
|
|
821
|
-
console.error('Smart DOM Reader server failed to start',
|
|
813
|
+
server.run().catch((cause) => {
|
|
814
|
+
console.error('Smart DOM Reader server failed to start', cause);
|
|
822
815
|
process.exitCode = 1;
|
|
823
816
|
});
|