@mcp-b/smart-dom-reader-server 5.1.0 → 6.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.
@@ -128,30 +128,20 @@ var ContentDetection = class ContentDetection {
128
128
  * Detect page landmarks
129
129
  */
130
130
  static detectLandmarks(doc) {
131
- const landmarks = {
132
- navigation: [],
133
- main: [],
134
- complementary: [],
135
- contentinfo: [],
136
- banner: [],
137
- search: [],
138
- form: [],
139
- region: [],
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 = typeof opts.maxElements === 'number' ? opts.maxElements : list.items.length;
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 = typeof opts.maxElements === 'number' ? opts.maxElements : t.rows.length;
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 = typeof opts.maxElements === 'number' ? opts.maxElements : content.media.length;
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 if (current.className && typeof current.className === 'string') {
651
- const firstClass = current.className.split(' ')[0];
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 (typeof value === 'string') {
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
- htmlElement.onclick ||
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
- htmlElement.onchange ||
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 (htmlElement.onsubmit || element.getAttribute('onsubmit') || element.matches('form'))
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 = element.form || element.closest('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
- if (element.matches('input, textarea')) {
920
- const input = element;
921
- return input.value || input.placeholder || '';
922
- }
923
- if (element.matches('img')) return element.alt || '';
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": "5.1.0",
3
+ "version": "6.0.0",
4
4
  "description": "MCP server for smart-dom-reader backed by Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "22.20.1",
28
28
  "typescript": "^5.8.3",
29
- "@mcp-b/smart-dom-reader": "5.1.0"
29
+ "@mcp-b/smart-dom-reader": "6.0.0"
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 = (createRequire(import.meta.url)('../package.json') as { version: string })
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 LibraryOperation = 'structure' | 'region' | 'content' | 'interactive';
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: Partial<Record<NodeJS.Platform, readonly string[]>> = {
88
- darwin: [
89
- '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
90
- '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',
91
- '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
92
- '/Applications/Chromium.app/Contents/MacOS/Chromium',
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
- linux: [
95
- '/usr/bin/google-chrome',
96
- '/usr/bin/google-chrome-stable',
97
- '/usr/bin/chromium-browser',
98
- '/usr/bin/chromium',
99
- '/snap/bin/chromium',
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
- win32: [
102
- ...(process.env.PROGRAMFILES
103
- ? [join(process.env.PROGRAMFILES, 'Google/Chrome/Application/chrome.exe')]
104
- : []),
105
- ...(process.env['ProgramFiles(x86)']
106
- ? [join(process.env['ProgramFiles(x86)'], 'Google/Chrome/Application/chrome.exe')]
107
- : []),
108
- ...(process.env.LOCALAPPDATA
109
- ? [join(process.env.LOCALAPPDATA, 'Google/Chrome/Application/chrome.exe')]
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 COMMON_TOOL_OPTIONS_SHAPE = {
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 | undefined): Promise<boolean> {
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
- ...COMMON_TOOL_OPTIONS_SHAPE,
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
- ...COMMON_TOOL_OPTIONS_SHAPE,
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 as string), source: `env:${envVar}` };
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[process.platform] ?? [];
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
- return page.evaluate<TResult, { code: string; operation: LibraryOperation; args: TArgs }>(
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 as StructureOperationArgs;
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) as TResult;
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 as RegionOperationArgs;
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) as TResult;
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 as ContentOperationArgs;
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}` as unknown as TResult;
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) as TResult;
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 as InteractiveOperationArgs;
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) as TResult;
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) as TResult;
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, operation, args }
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(error: unknown, fallbackMessage: string): never {
806
- if (error instanceof ProtocolError) {
807
- throw error;
798
+ private handleToolError(cause: unknown, fallbackMessage: string): never {
799
+ if (cause instanceof ProtocolError) {
800
+ throw cause;
808
801
  }
809
802
 
810
- if (error instanceof Error) {
811
- console.error(fallbackMessage, error);
812
- throw new ProtocolError(ProtocolErrorCode.InternalError, error.message);
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((error) => {
821
- console.error('Smart DOM Reader server failed to start', error);
813
+ server.run().catch((cause) => {
814
+ console.error('Smart DOM Reader server failed to start', cause);
822
815
  process.exitCode = 1;
823
816
  });