@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 +204 -47
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
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
|
|
587
|
+
enabled;
|
|
590
588
|
distinctId;
|
|
591
589
|
globalContext = {};
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
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
|
|
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 =
|
|
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.
|
|
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:
|
|
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
|
-
|
|
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:
|
|
2205
|
+
await page.waitForLoadState("networkidle", { timeout: DEFAULT_PAGE_TIMEOUT });
|
|
2045
2206
|
} catch {
|
|
2046
|
-
logger.debug("Network idle timeout
|
|
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;
|