@arabold/docs-mcp-server 1.22.1 → 1.23.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/dist/index.js CHANGED
@@ -433,24 +433,22 @@ class PostHogClient {
433
433
  };
434
434
  constructor(enabled) {
435
435
  this.enabled = enabled;
436
- if (this.enabled) {
437
- try {
438
- this.client = new PostHog("phc_g7pXZZdUiAQXdnwUANjloQWMvO0amEDTBaeDSWgXgrQ", {
439
- host: PostHogClient.CONFIG.host,
440
- flushAt: PostHogClient.CONFIG.flushAt,
441
- flushInterval: PostHogClient.CONFIG.flushInterval,
442
- disableGeoip: PostHogClient.CONFIG.disableGeoip
443
- });
444
- logger.debug("PostHog client initialized");
445
- } catch (error) {
446
- logger.debug(
447
- `PostHog initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`
448
- );
449
- this.enabled = false;
450
- }
451
- } else {
436
+ if (!this.enabled) {
437
+ return;
438
+ }
439
+ try {
440
+ this.client = new PostHog("phc_g7pXZZdUiAQXdnwUANjloQWMvO0amEDTBaeDSWgXgrQ", {
441
+ host: PostHogClient.CONFIG.host,
442
+ flushAt: PostHogClient.CONFIG.flushAt,
443
+ flushInterval: PostHogClient.CONFIG.flushInterval,
444
+ disableGeoip: PostHogClient.CONFIG.disableGeoip
445
+ });
446
+ logger.debug("PostHog client initialized");
447
+ } catch (error) {
448
+ logger.debug(
449
+ `PostHog initialization failed: ${error instanceof Error ? error.message : "Unknown error"}`
450
+ );
452
451
  this.enabled = false;
453
- logger.debug("PostHog client disabled");
454
452
  }
455
453
  }
456
454
  /**
@@ -586,18 +584,31 @@ var TelemetryEvent = /* @__PURE__ */ ((TelemetryEvent2) => {
586
584
  })(TelemetryEvent || {});
587
585
  class Analytics {
588
586
  postHogClient;
589
- enabled = true;
587
+ enabled;
590
588
  distinctId;
591
589
  globalContext = {};
592
- constructor(enabled) {
593
- this.enabled = enabled ?? TelemetryConfig.getInstance().isEnabled();
594
- this.distinctId = generateInstallationId();
595
- this.postHogClient = new PostHogClient(this.enabled);
596
- if (this.enabled) {
590
+ /**
591
+ * Create a new Analytics instance with proper initialization
592
+ * This is the recommended way to create Analytics instances
593
+ */
594
+ static create() {
595
+ const config = TelemetryConfig.getInstance();
596
+ const shouldEnable = config.isEnabled() && true;
597
+ const analytics2 = new Analytics(shouldEnable);
598
+ if (analytics2.isEnabled()) {
597
599
  logger.debug("Analytics enabled");
598
600
  } else {
599
601
  logger.debug("Analytics disabled");
600
602
  }
603
+ return analytics2;
604
+ }
605
+ /**
606
+ * Private constructor - use Analytics.create() instead
607
+ */
608
+ constructor(enabled = true) {
609
+ this.enabled = enabled;
610
+ this.distinctId = generateInstallationId();
611
+ this.postHogClient = new PostHogClient(this.enabled);
601
612
  }
602
613
  /**
603
614
  * Set global application context that will be included in all events
@@ -642,7 +653,7 @@ class Analytics {
642
653
  * Check if analytics is enabled
643
654
  */
644
655
  isEnabled() {
645
- return this.enabled && this.postHogClient.isEnabled();
656
+ return this.enabled;
646
657
  }
647
658
  /**
648
659
  * Track tool usage with error handling and automatic timing
@@ -675,7 +686,7 @@ class Analytics {
675
686
  }
676
687
  }
677
688
  }
678
- const analytics = new Analytics();
689
+ const analytics = Analytics.create();
679
690
  function extractHostname(url) {
680
691
  try {
681
692
  const parsed = new URL(url);
@@ -696,7 +707,7 @@ function extractProtocol(urlOrPath) {
696
707
  }
697
708
  }
698
709
  const name = "@arabold/docs-mcp-server";
699
- const version = "1.22.0";
710
+ const version = "1.22.1";
700
711
  const description = "MCP server for fetching and searching documentation";
701
712
  const type = "module";
702
713
  const bin = { "docs-mcp-server": "dist/index.js" };
@@ -1907,7 +1918,7 @@ class HtmlPlaywrightMiddleware {
1907
1918
  logger.debug(`Could not access content frame for iframe ${index + 1}`);
1908
1919
  return;
1909
1920
  }
1910
- await frame.waitForSelector("body", { timeout: 1e4 }).catch(() => {
1921
+ await frame.waitForSelector("body", { timeout: DEFAULT_PAGE_TIMEOUT }).catch(() => {
1911
1922
  logger.debug(`Timeout waiting for body in iframe ${index + 1}`);
1912
1923
  });
1913
1924
  await this.waitForLoadingToComplete(frame);
@@ -1969,6 +1980,156 @@ class HtmlPlaywrightMiddleware {
1969
1980
  [index, content]
1970
1981
  );
1971
1982
  }
1983
+ /**
1984
+ * Waits for and processes framesets on the page by extracting content from each frame
1985
+ * and replacing the frameset with merged content.
1986
+ *
1987
+ * @param page The Playwright page instance to operate on.
1988
+ */
1989
+ async waitForFramesetsToLoad(page) {
1990
+ try {
1991
+ const framesets = await page.$$("frameset");
1992
+ if (framesets.length === 0) {
1993
+ return;
1994
+ }
1995
+ logger.debug(`Found ${framesets.length} frameset(s) on ${page.url()}`);
1996
+ const frameUrls = await this.extractFrameUrls(page);
1997
+ if (frameUrls.length === 0) {
1998
+ logger.debug("No frame URLs found in framesets");
1999
+ return;
2000
+ }
2001
+ logger.debug(`Found ${frameUrls.length} frame(s) to process`);
2002
+ const frameContents = [];
2003
+ for (const frameInfo of frameUrls) {
2004
+ try {
2005
+ const content = await this.fetchFrameContent(page, frameInfo.src);
2006
+ if (content && content.trim().length > 0) {
2007
+ frameContents.push({
2008
+ url: frameInfo.src,
2009
+ content,
2010
+ name: frameInfo.name
2011
+ });
2012
+ logger.debug(`Successfully fetched content from frame: ${frameInfo.src}`);
2013
+ } else {
2014
+ logger.debug(`Frame content is empty: ${frameInfo.src}`);
2015
+ }
2016
+ } catch (error) {
2017
+ logger.debug(`Error fetching frame content from ${frameInfo.src}: ${error}`);
2018
+ }
2019
+ }
2020
+ if (frameContents.length > 0) {
2021
+ await this.mergeFrameContents(page, frameContents);
2022
+ logger.debug(
2023
+ `Successfully merged ${frameContents.length} frame(s) into main page`
2024
+ );
2025
+ }
2026
+ logger.debug(`Finished processing framesets`);
2027
+ } catch (error) {
2028
+ logger.debug(`Error during frameset processing for ${page.url()}: ${error}`);
2029
+ }
2030
+ }
2031
+ /**
2032
+ * Extracts frame URLs from all framesets on the page in document order.
2033
+ *
2034
+ * @param page The Playwright page instance to operate on.
2035
+ * @returns Array of frame information objects with src and optional name.
2036
+ */
2037
+ async extractFrameUrls(page) {
2038
+ try {
2039
+ return await page.evaluate(() => {
2040
+ const frames = [];
2041
+ const frameElements = document.querySelectorAll("frame");
2042
+ for (const frame of frameElements) {
2043
+ const src = frame.getAttribute("src");
2044
+ if (src?.trim() && !src.startsWith("javascript:") && src !== "about:blank") {
2045
+ const name2 = frame.getAttribute("name") || void 0;
2046
+ frames.push({ src: src.trim(), name: name2 });
2047
+ }
2048
+ }
2049
+ return frames;
2050
+ });
2051
+ } catch (error) {
2052
+ logger.debug(`Error extracting frame URLs: ${error}`);
2053
+ return [];
2054
+ }
2055
+ }
2056
+ /**
2057
+ * Fetches content from a frame URL by navigating to it in a new page.
2058
+ *
2059
+ * @param parentPage The parent page (used to resolve relative URLs and share context)
2060
+ * @param frameUrl The URL of the frame to fetch content from
2061
+ * @returns The HTML content of the frame
2062
+ */
2063
+ async fetchFrameContent(parentPage, frameUrl) {
2064
+ let framePage = null;
2065
+ try {
2066
+ const resolvedUrl = new URL(frameUrl, parentPage.url()).href;
2067
+ framePage = await parentPage.context().newPage();
2068
+ await framePage.route("**/*", async (route) => {
2069
+ const resourceType = route.request().resourceType();
2070
+ if (["image", "font", "media"].includes(resourceType)) {
2071
+ return route.abort();
2072
+ }
2073
+ return route.continue();
2074
+ });
2075
+ logger.debug(`Fetching frame content from: ${resolvedUrl}`);
2076
+ await framePage.goto(resolvedUrl, { waitUntil: "load", timeout: 15e3 });
2077
+ await framePage.waitForSelector("body", { timeout: 1e4 });
2078
+ await this.waitForLoadingToComplete(framePage);
2079
+ const bodyContent = await framePage.$eval(
2080
+ "body",
2081
+ (el) => el.innerHTML
2082
+ );
2083
+ logger.debug(`Successfully fetched frame content from: ${resolvedUrl}`);
2084
+ return bodyContent || "";
2085
+ } catch (error) {
2086
+ logger.debug(`Error fetching frame content from ${frameUrl}: ${error}`);
2087
+ return "";
2088
+ } finally {
2089
+ if (framePage) {
2090
+ await framePage.unroute("**/*");
2091
+ await framePage.close();
2092
+ }
2093
+ }
2094
+ }
2095
+ /**
2096
+ * Merges frame contents and replaces the frameset structure with the merged content.
2097
+ *
2098
+ * @param page The main page containing the frameset
2099
+ * @param frameContents Array of frame content objects with URL, content, and optional name
2100
+ */
2101
+ async mergeFrameContents(page, frameContents) {
2102
+ try {
2103
+ const mergedContent = frameContents.map((frame, index) => {
2104
+ const frameName = frame.name ? ` (${frame.name})` : "";
2105
+ const frameHeader = `<!-- Frame ${index + 1}${frameName}: ${frame.url} -->`;
2106
+ return `${frameHeader}
2107
+ <div data-frame-url="${frame.url}" data-frame-name="${frame.name || ""}">
2108
+ ${frame.content}
2109
+ </div>`;
2110
+ }).join("\n\n");
2111
+ await page.evaluate((mergedHtml) => {
2112
+ const framesets = document.querySelectorAll("frameset");
2113
+ if (framesets.length > 0) {
2114
+ const body = document.createElement("body");
2115
+ body.innerHTML = mergedHtml;
2116
+ const firstFrameset = framesets[0];
2117
+ if (firstFrameset.parentNode) {
2118
+ firstFrameset.parentNode.replaceChild(body, firstFrameset);
2119
+ }
2120
+ for (let i = 1; i < framesets.length; i++) {
2121
+ const frameset = framesets[i];
2122
+ if (frameset.parentNode) {
2123
+ frameset.parentNode.removeChild(frameset);
2124
+ }
2125
+ }
2126
+ }
2127
+ }, mergedContent);
2128
+ logger.debug("Successfully replaced frameset with merged content");
2129
+ } catch (error) {
2130
+ logger.debug(`Error merging frame contents: ${error}`);
2131
+ }
2132
+ }
1972
2133
  /**
1973
2134
  * Processes the context using Playwright, rendering dynamic content and propagating credentials for all same-origin requests.
1974
2135
  *
@@ -2003,10 +2164,10 @@ class HtmlPlaywrightMiddleware {
2003
2164
  const browser = await this.ensureBrowser();
2004
2165
  if (credentials) {
2005
2166
  browserContext = await browser.newContext({ httpCredentials: credentials });
2006
- page = await browserContext.newPage();
2007
2167
  } else {
2008
- page = await browser.newPage();
2168
+ browserContext = await browser.newContext();
2009
2169
  }
2170
+ page = await browserContext.newPage();
2010
2171
  logger.debug(`Playwright: Processing ${context.source}`);
2011
2172
  await page.route("**/*", async (route) => {
2012
2173
  const reqUrl = route.request().url();
@@ -2039,14 +2200,15 @@ class HtmlPlaywrightMiddleware {
2039
2200
  return route.continue({ headers });
2040
2201
  });
2041
2202
  await page.goto(context.source, { waitUntil: "load" });
2042
- await page.waitForSelector("body");
2203
+ await page.waitForSelector("body, frameset");
2043
2204
  try {
2044
- await page.waitForLoadState("networkidle", { timeout: 5e3 });
2205
+ await page.waitForLoadState("networkidle", { timeout: DEFAULT_PAGE_TIMEOUT });
2045
2206
  } catch {
2046
- logger.debug("Network idle timeout (5s), proceeding anyway");
2207
+ logger.debug("Network idle timeout, proceeding anyway");
2047
2208
  }
2048
2209
  await this.waitForLoadingToComplete(page);
2049
2210
  await this.waitForIframesToLoad(page);
2211
+ await this.waitForFramesetsToLoad(page);
2050
2212
  renderedHtml = await page.content();
2051
2213
  logger.debug(`Playwright: Successfully rendered content for ${context.source}`);
2052
2214
  } catch (error) {
@@ -7252,11 +7414,6 @@ class AppServer {
7252
7414
  );
7253
7415
  }
7254
7416
  }
7255
- if (this.config.enableWorker && !this.config.enableApiServer) {
7256
- logger.warn(
7257
- "Warning: Worker is enabled but API server is disabled. Consider enabling the API for better observability."
7258
- );
7259
- }
7260
7417
  }
7261
7418
  /**
7262
7419
  * Start the application server with the configured services.
@@ -9850,8 +10007,8 @@ function createDefaultAction(program) {
9850
10007
  const appServer = await startAppServer(docService, pipeline, config);
9851
10008
  registerGlobalServices({
9852
10009
  appServer,
9853
- docService,
9854
- pipeline
10010
+ docService
10011
+ // pipeline is owned by AppServer - don't register globally to avoid double shutdown
9855
10012
  });
9856
10013
  await new Promise(() => {
9857
10014
  });
@@ -10037,8 +10194,8 @@ function createMcpCommand(program) {
10037
10194
  const appServer = await startAppServer(docService, pipeline, config);
10038
10195
  registerGlobalServices({
10039
10196
  appServer,
10040
- docService,
10041
- pipeline
10197
+ docService
10198
+ // pipeline is owned by AppServer - don't register globally to avoid double shutdown
10042
10199
  });
10043
10200
  await new Promise(() => {
10044
10201
  });
@@ -10287,8 +10444,8 @@ function createWebCommand(program) {
10287
10444
  const appServer = await startAppServer(docService, pipeline, config);
10288
10445
  registerGlobalServices({
10289
10446
  appServer,
10290
- docService,
10291
- pipeline
10447
+ docService
10448
+ // pipeline is owned by AppServer - don't register globally to avoid double shutdown
10292
10449
  });
10293
10450
  await new Promise(() => {
10294
10451
  });
@@ -10333,8 +10490,8 @@ function createWorkerCommand(program) {
10333
10490
  const appServer = await startAppServer(docService, pipeline, config);
10334
10491
  registerGlobalServices({
10335
10492
  appServer,
10336
- docService,
10337
- pipeline
10493
+ docService
10494
+ // pipeline is owned by AppServer - don't register globally to avoid double shutdown
10338
10495
  });
10339
10496
  await new Promise(() => {
10340
10497
  });
@@ -10422,7 +10579,7 @@ const sigintHandler = async () => {
10422
10579
  logger.debug("SIGINT: MCP server stopped.");
10423
10580
  }
10424
10581
  logger.debug("SIGINT: Shutting down active services...");
10425
- if (activePipelineManager) {
10582
+ if (activePipelineManager && !activeAppServer) {
10426
10583
  await activePipelineManager.stop();
10427
10584
  activePipelineManager = null;
10428
10585
  logger.debug("SIGINT: PipelineManager stopped.");
@@ -10483,7 +10640,7 @@ async function runCli() {
10483
10640
  }).catch((e) => logger.error(`❌ Error stopping MCP server: ${e}`))
10484
10641
  );
10485
10642
  }
10486
- if (activePipelineManager) {
10643
+ if (activePipelineManager && !activeAppServer) {
10487
10644
  shutdownPromises.push(
10488
10645
  activePipelineManager.stop().then(() => {
10489
10646
  activePipelineManager = null;