docusaurus-plugin-mcp-server 0.5.0 → 0.7.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/adapters-entry.d.mts +1 -1
- package/dist/adapters-entry.d.ts +1 -1
- package/dist/adapters-entry.js +253 -121
- package/dist/adapters-entry.js.map +1 -1
- package/dist/adapters-entry.mjs +252 -120
- package/dist/adapters-entry.mjs.map +1 -1
- package/dist/cli/verify.js +250 -119
- package/dist/cli/verify.js.map +1 -1
- package/dist/cli/verify.mjs +250 -119
- package/dist/cli/verify.mjs.map +1 -1
- package/dist/{index-CzA4FjeE.d.mts → index-4g0ZZK3z.d.mts} +33 -0
- package/dist/{index-CzA4FjeE.d.ts → index-4g0ZZK3z.d.ts} +33 -0
- package/dist/index.d.mts +287 -7
- package/dist/index.d.ts +287 -7
- package/dist/index.js +382 -143
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +378 -143
- package/dist/index.mjs.map +1 -1
- package/dist/theme/index.d.mts +2 -1
- package/dist/theme/index.d.ts +2 -1
- package/dist/theme/index.js +313 -158
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/index.mjs +308 -158
- package/dist/theme/index.mjs.map +1 -1
- package/package.json +4 -1
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
-
var
|
|
5
|
+
var path = require('path');
|
|
6
6
|
var fs3 = require('fs-extra');
|
|
7
7
|
var pMap = require('p-map');
|
|
8
8
|
var unified = require('unified');
|
|
@@ -20,7 +20,7 @@ var zod = require('zod');
|
|
|
20
20
|
|
|
21
21
|
function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
|
|
22
22
|
|
|
23
|
-
var
|
|
23
|
+
var path__default = /*#__PURE__*/_interopDefault(path);
|
|
24
24
|
var fs3__default = /*#__PURE__*/_interopDefault(fs3);
|
|
25
25
|
var pMap__default = /*#__PURE__*/_interopDefault(pMap);
|
|
26
26
|
var rehypeParse__default = /*#__PURE__*/_interopDefault(rehypeParse);
|
|
@@ -49,7 +49,10 @@ var DEFAULT_OPTIONS = {
|
|
|
49
49
|
name: "docs-mcp-server",
|
|
50
50
|
version: "1.0.0"
|
|
51
51
|
},
|
|
52
|
-
excludeRoutes: ["/404*", "/search*"]
|
|
52
|
+
excludeRoutes: ["/404*", "/search*"],
|
|
53
|
+
indexers: void 0,
|
|
54
|
+
// Default: ['flexsearch'] applied at runtime
|
|
55
|
+
search: "flexsearch"
|
|
53
56
|
};
|
|
54
57
|
function filterRoutes(routes, excludePatterns) {
|
|
55
58
|
return routes.filter((route) => {
|
|
@@ -65,15 +68,15 @@ async function discoverHtmlFiles(outDir) {
|
|
|
65
68
|
async function scanDirectory(dir) {
|
|
66
69
|
const entries = await fs3__default.default.readdir(dir, { withFileTypes: true });
|
|
67
70
|
for (const entry of entries) {
|
|
68
|
-
const fullPath =
|
|
71
|
+
const fullPath = path__default.default.join(dir, entry.name);
|
|
69
72
|
if (entry.isDirectory()) {
|
|
70
73
|
if (["assets", "img", "static"].includes(entry.name)) {
|
|
71
74
|
continue;
|
|
72
75
|
}
|
|
73
76
|
await scanDirectory(fullPath);
|
|
74
77
|
} else if (entry.name === "index.html") {
|
|
75
|
-
const relativePath =
|
|
76
|
-
let routePath = "/" +
|
|
78
|
+
const relativePath = path__default.default.relative(outDir, fullPath);
|
|
79
|
+
let routePath = "/" + path__default.default.dirname(relativePath).replace(/\\/g, "/");
|
|
77
80
|
if (routePath === "/.") {
|
|
78
81
|
routePath = "/";
|
|
79
82
|
}
|
|
@@ -504,6 +507,201 @@ async function importSearchIndex(data) {
|
|
|
504
507
|
return index;
|
|
505
508
|
}
|
|
506
509
|
|
|
510
|
+
// src/providers/indexers/flexsearch-indexer.ts
|
|
511
|
+
var FlexSearchIndexer = class {
|
|
512
|
+
name = "flexsearch";
|
|
513
|
+
docsIndex = {};
|
|
514
|
+
exportedIndex = null;
|
|
515
|
+
docCount = 0;
|
|
516
|
+
/**
|
|
517
|
+
* FlexSearch indexer always runs by default.
|
|
518
|
+
* It respects the indexers configuration - if not included, it won't run.
|
|
519
|
+
*/
|
|
520
|
+
shouldRun() {
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
async initialize(_context) {
|
|
524
|
+
this.docsIndex = {};
|
|
525
|
+
this.exportedIndex = null;
|
|
526
|
+
this.docCount = 0;
|
|
527
|
+
}
|
|
528
|
+
async indexDocuments(docs) {
|
|
529
|
+
this.docCount = docs.length;
|
|
530
|
+
for (const doc of docs) {
|
|
531
|
+
this.docsIndex[doc.route] = doc;
|
|
532
|
+
}
|
|
533
|
+
console.log("[FlexSearch] Building search index...");
|
|
534
|
+
const searchIndex2 = buildSearchIndex(docs);
|
|
535
|
+
this.exportedIndex = await exportSearchIndex(searchIndex2);
|
|
536
|
+
console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
|
|
537
|
+
}
|
|
538
|
+
async finalize() {
|
|
539
|
+
const artifacts = /* @__PURE__ */ new Map();
|
|
540
|
+
artifacts.set("docs.json", this.docsIndex);
|
|
541
|
+
artifacts.set("search-index.json", this.exportedIndex);
|
|
542
|
+
return artifacts;
|
|
543
|
+
}
|
|
544
|
+
async getManifestData() {
|
|
545
|
+
return {
|
|
546
|
+
searchEngine: "flexsearch"
|
|
547
|
+
};
|
|
548
|
+
}
|
|
549
|
+
};
|
|
550
|
+
var FlexSearchProvider = class {
|
|
551
|
+
name = "flexsearch";
|
|
552
|
+
docs = null;
|
|
553
|
+
searchIndex = null;
|
|
554
|
+
ready = false;
|
|
555
|
+
async initialize(_context, initData) {
|
|
556
|
+
if (!initData) {
|
|
557
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
558
|
+
}
|
|
559
|
+
if (initData.docs && initData.indexData) {
|
|
560
|
+
this.docs = initData.docs;
|
|
561
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
562
|
+
this.ready = true;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (initData.docsPath && initData.indexPath) {
|
|
566
|
+
if (await fs3__default.default.pathExists(initData.docsPath)) {
|
|
567
|
+
this.docs = await fs3__default.default.readJson(initData.docsPath);
|
|
568
|
+
} else {
|
|
569
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
570
|
+
}
|
|
571
|
+
if (await fs3__default.default.pathExists(initData.indexPath)) {
|
|
572
|
+
const indexData = await fs3__default.default.readJson(initData.indexPath);
|
|
573
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
574
|
+
} else {
|
|
575
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
576
|
+
}
|
|
577
|
+
this.ready = true;
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
throw new Error(
|
|
581
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
isReady() {
|
|
585
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
586
|
+
}
|
|
587
|
+
async search(query, options) {
|
|
588
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
589
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
590
|
+
}
|
|
591
|
+
const limit = options?.limit ?? 5;
|
|
592
|
+
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
593
|
+
}
|
|
594
|
+
async getDocument(route) {
|
|
595
|
+
if (!this.docs) {
|
|
596
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
597
|
+
}
|
|
598
|
+
if (this.docs[route]) {
|
|
599
|
+
return this.docs[route];
|
|
600
|
+
}
|
|
601
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
602
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
603
|
+
return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
|
|
604
|
+
}
|
|
605
|
+
async healthCheck() {
|
|
606
|
+
if (!this.isReady()) {
|
|
607
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
608
|
+
}
|
|
609
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
610
|
+
return {
|
|
611
|
+
healthy: true,
|
|
612
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
613
|
+
};
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
617
|
+
*/
|
|
618
|
+
getDocs() {
|
|
619
|
+
return this.docs;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
623
|
+
*/
|
|
624
|
+
getSearchIndex() {
|
|
625
|
+
return this.searchIndex;
|
|
626
|
+
}
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
// src/providers/loader.ts
|
|
630
|
+
async function loadIndexer(specifier) {
|
|
631
|
+
if (specifier === "flexsearch") {
|
|
632
|
+
return new FlexSearchIndexer();
|
|
633
|
+
}
|
|
634
|
+
try {
|
|
635
|
+
const module = await import(specifier);
|
|
636
|
+
const IndexerClass = module.default;
|
|
637
|
+
if (typeof IndexerClass === "function") {
|
|
638
|
+
const instance = new IndexerClass();
|
|
639
|
+
if (!isContentIndexer(instance)) {
|
|
640
|
+
throw new Error(
|
|
641
|
+
`Invalid indexer module "${specifier}": does not implement ContentIndexer interface`
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
return instance;
|
|
645
|
+
}
|
|
646
|
+
if (isContentIndexer(IndexerClass)) {
|
|
647
|
+
return IndexerClass;
|
|
648
|
+
}
|
|
649
|
+
throw new Error(
|
|
650
|
+
`Invalid indexer module "${specifier}": must export a default class or ContentIndexer instance`
|
|
651
|
+
);
|
|
652
|
+
} catch (error) {
|
|
653
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
654
|
+
throw new Error(`Indexer module not found: "${specifier}". Check the path or package name.`);
|
|
655
|
+
}
|
|
656
|
+
throw error;
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
async function loadSearchProvider(specifier) {
|
|
660
|
+
if (specifier === "flexsearch") {
|
|
661
|
+
return new FlexSearchProvider();
|
|
662
|
+
}
|
|
663
|
+
try {
|
|
664
|
+
const module = await import(specifier);
|
|
665
|
+
const ProviderClass = module.default;
|
|
666
|
+
if (typeof ProviderClass === "function") {
|
|
667
|
+
const instance = new ProviderClass();
|
|
668
|
+
if (!isSearchProvider(instance)) {
|
|
669
|
+
throw new Error(
|
|
670
|
+
`Invalid search provider module "${specifier}": does not implement SearchProvider interface`
|
|
671
|
+
);
|
|
672
|
+
}
|
|
673
|
+
return instance;
|
|
674
|
+
}
|
|
675
|
+
if (isSearchProvider(ProviderClass)) {
|
|
676
|
+
return ProviderClass;
|
|
677
|
+
}
|
|
678
|
+
throw new Error(
|
|
679
|
+
`Invalid search provider module "${specifier}": must export a default class or SearchProvider instance`
|
|
680
|
+
);
|
|
681
|
+
} catch (error) {
|
|
682
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
683
|
+
throw new Error(
|
|
684
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
throw error;
|
|
688
|
+
}
|
|
689
|
+
}
|
|
690
|
+
function isContentIndexer(obj) {
|
|
691
|
+
if (!obj || typeof obj !== "object") {
|
|
692
|
+
return false;
|
|
693
|
+
}
|
|
694
|
+
const indexer = obj;
|
|
695
|
+
return typeof indexer.name === "string" && typeof indexer.initialize === "function" && typeof indexer.indexDocuments === "function" && typeof indexer.finalize === "function";
|
|
696
|
+
}
|
|
697
|
+
function isSearchProvider(obj) {
|
|
698
|
+
if (!obj || typeof obj !== "object") {
|
|
699
|
+
return false;
|
|
700
|
+
}
|
|
701
|
+
const provider = obj;
|
|
702
|
+
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
703
|
+
}
|
|
704
|
+
|
|
507
705
|
// src/plugin/docusaurus-plugin.ts
|
|
508
706
|
function resolveOptions(options) {
|
|
509
707
|
return {
|
|
@@ -560,6 +758,10 @@ function mcpServerPlugin(context, options) {
|
|
|
560
758
|
async postBuild({ outDir }) {
|
|
561
759
|
console.log("[MCP] Starting MCP artifact generation...");
|
|
562
760
|
const startTime = Date.now();
|
|
761
|
+
if (resolvedOptions.indexers === false) {
|
|
762
|
+
console.log("[MCP] Indexing disabled, skipping artifact generation");
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
563
765
|
const routes = await collectRoutes(outDir, resolvedOptions.excludeRoutes);
|
|
564
766
|
console.log(`[MCP] Found ${routes.length} routes to process`);
|
|
565
767
|
if (routes.length === 0) {
|
|
@@ -584,27 +786,47 @@ function mcpServerPlugin(context, options) {
|
|
|
584
786
|
console.warn("[MCP] No valid documents to index");
|
|
585
787
|
return;
|
|
586
788
|
}
|
|
587
|
-
const
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
}
|
|
591
|
-
console.log("[MCP] Building search index...");
|
|
592
|
-
const searchIndex2 = buildSearchIndex(validDocs);
|
|
593
|
-
const exportedIndex = await exportSearchIndex(searchIndex2);
|
|
594
|
-
const manifest = {
|
|
595
|
-
version: resolvedOptions.server.version,
|
|
596
|
-
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
597
|
-
docCount: validDocs.length,
|
|
789
|
+
const mcpOutputDir = path__default.default.join(outDir, resolvedOptions.outputDir);
|
|
790
|
+
const providerContext = {
|
|
791
|
+
baseUrl: context.siteConfig.url,
|
|
598
792
|
serverName: resolvedOptions.server.name,
|
|
599
|
-
|
|
793
|
+
serverVersion: resolvedOptions.server.version,
|
|
794
|
+
outputDir: mcpOutputDir
|
|
600
795
|
};
|
|
601
|
-
const
|
|
796
|
+
const indexerSpecs = resolvedOptions.indexers ?? ["flexsearch"];
|
|
602
797
|
await fs3__default.default.ensureDir(mcpOutputDir);
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
798
|
+
const indexerNames = [];
|
|
799
|
+
for (const indexerSpec of indexerSpecs) {
|
|
800
|
+
try {
|
|
801
|
+
const indexer = await loadIndexer(indexerSpec);
|
|
802
|
+
if (indexer.shouldRun && !indexer.shouldRun()) {
|
|
803
|
+
console.log(`[MCP] Skipping indexer: ${indexer.name}`);
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
console.log(`[MCP] Running indexer: ${indexer.name}`);
|
|
807
|
+
await indexer.initialize(providerContext);
|
|
808
|
+
await indexer.indexDocuments(validDocs);
|
|
809
|
+
const artifacts = await indexer.finalize();
|
|
810
|
+
for (const [filename, content] of artifacts) {
|
|
811
|
+
await fs3__default.default.writeJson(path__default.default.join(mcpOutputDir, filename), content, { spaces: 0 });
|
|
812
|
+
}
|
|
813
|
+
indexerNames.push(indexer.name);
|
|
814
|
+
} catch (error) {
|
|
815
|
+
console.error(`[MCP] Error running indexer "${indexerSpec}":`, error);
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
if (indexerNames.length > 0) {
|
|
820
|
+
const manifest = {
|
|
821
|
+
version: resolvedOptions.server.version,
|
|
822
|
+
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
823
|
+
docCount: validDocs.length,
|
|
824
|
+
serverName: resolvedOptions.server.name,
|
|
825
|
+
baseUrl: context.siteConfig.url,
|
|
826
|
+
indexers: indexerNames
|
|
827
|
+
};
|
|
828
|
+
await fs3__default.default.writeJson(path__default.default.join(mcpOutputDir, "manifest.json"), manifest, { spaces: 2 });
|
|
829
|
+
}
|
|
608
830
|
const elapsed = Date.now() - startTime;
|
|
609
831
|
console.log(`[MCP] Artifacts written to ${mcpOutputDir}`);
|
|
610
832
|
console.log(`[MCP] Generation complete in ${elapsed}ms`);
|
|
@@ -632,17 +854,6 @@ var docsSearchTool = {
|
|
|
632
854
|
required: ["query"]
|
|
633
855
|
}
|
|
634
856
|
};
|
|
635
|
-
function executeDocsSearch(params, index, docs) {
|
|
636
|
-
const { query, limit = 5 } = params;
|
|
637
|
-
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
|
638
|
-
throw new Error("Query parameter is required and must be a non-empty string");
|
|
639
|
-
}
|
|
640
|
-
const effectiveLimit = Math.min(Math.max(1, limit), 20);
|
|
641
|
-
const results = searchIndex(index, docs, query.trim(), {
|
|
642
|
-
limit: effectiveLimit
|
|
643
|
-
});
|
|
644
|
-
return results;
|
|
645
|
-
}
|
|
646
857
|
function formatSearchResults(results, baseUrl) {
|
|
647
858
|
if (results.length === 0) {
|
|
648
859
|
return "No matching documents found.";
|
|
@@ -682,28 +893,6 @@ var docsGetPageTool = {
|
|
|
682
893
|
required: ["route"]
|
|
683
894
|
}
|
|
684
895
|
};
|
|
685
|
-
function executeDocsGetPage(params, docs) {
|
|
686
|
-
const { route } = params;
|
|
687
|
-
if (!route || typeof route !== "string") {
|
|
688
|
-
throw new Error("Route parameter is required and must be a string");
|
|
689
|
-
}
|
|
690
|
-
let normalizedRoute = route.trim();
|
|
691
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
692
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
693
|
-
}
|
|
694
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
695
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
696
|
-
}
|
|
697
|
-
const doc = docs[normalizedRoute];
|
|
698
|
-
if (!doc) {
|
|
699
|
-
const altRoute = normalizedRoute.slice(1);
|
|
700
|
-
if (docs[altRoute]) {
|
|
701
|
-
return docs[altRoute] ?? null;
|
|
702
|
-
}
|
|
703
|
-
return null;
|
|
704
|
-
}
|
|
705
|
-
return doc;
|
|
706
|
-
}
|
|
707
896
|
function formatPageContent(doc, baseUrl) {
|
|
708
897
|
if (!doc) {
|
|
709
898
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -757,52 +946,6 @@ var docsGetSectionTool = {
|
|
|
757
946
|
required: ["route", "headingId"]
|
|
758
947
|
}
|
|
759
948
|
};
|
|
760
|
-
function executeDocsGetSection(params, docs) {
|
|
761
|
-
const { route, headingId } = params;
|
|
762
|
-
if (!route || typeof route !== "string") {
|
|
763
|
-
throw new Error("Route parameter is required and must be a string");
|
|
764
|
-
}
|
|
765
|
-
if (!headingId || typeof headingId !== "string") {
|
|
766
|
-
throw new Error("HeadingId parameter is required and must be a string");
|
|
767
|
-
}
|
|
768
|
-
let normalizedRoute = route.trim();
|
|
769
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
770
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
771
|
-
}
|
|
772
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
773
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
774
|
-
}
|
|
775
|
-
const doc = docs[normalizedRoute];
|
|
776
|
-
if (!doc) {
|
|
777
|
-
return {
|
|
778
|
-
content: null,
|
|
779
|
-
doc: null,
|
|
780
|
-
headingText: null,
|
|
781
|
-
availableHeadings: []
|
|
782
|
-
};
|
|
783
|
-
}
|
|
784
|
-
const availableHeadings = doc.headings.map((h) => ({
|
|
785
|
-
id: h.id,
|
|
786
|
-
text: h.text,
|
|
787
|
-
level: h.level
|
|
788
|
-
}));
|
|
789
|
-
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
790
|
-
if (!heading) {
|
|
791
|
-
return {
|
|
792
|
-
content: null,
|
|
793
|
-
doc,
|
|
794
|
-
headingText: null,
|
|
795
|
-
availableHeadings
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
const content = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
799
|
-
return {
|
|
800
|
-
content,
|
|
801
|
-
doc,
|
|
802
|
-
headingText: heading.text,
|
|
803
|
-
availableHeadings
|
|
804
|
-
};
|
|
805
|
-
}
|
|
806
949
|
function formatSectionContent(result, headingId, baseUrl) {
|
|
807
950
|
if (!result.doc) {
|
|
808
951
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -839,8 +982,7 @@ function isDataConfig(config) {
|
|
|
839
982
|
}
|
|
840
983
|
var McpDocsServer = class {
|
|
841
984
|
config;
|
|
842
|
-
|
|
843
|
-
searchIndex = null;
|
|
985
|
+
searchProvider = null;
|
|
844
986
|
mcpServer;
|
|
845
987
|
initialized = false;
|
|
846
988
|
constructor(config) {
|
|
@@ -873,18 +1015,26 @@ var McpDocsServer = class {
|
|
|
873
1015
|
},
|
|
874
1016
|
async ({ query, limit }) => {
|
|
875
1017
|
await this.initialize();
|
|
876
|
-
if (!this.
|
|
1018
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
877
1019
|
return {
|
|
878
1020
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
879
1021
|
isError: true
|
|
880
1022
|
};
|
|
881
1023
|
}
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1024
|
+
try {
|
|
1025
|
+
const results = await this.searchProvider.search(query, { limit });
|
|
1026
|
+
return {
|
|
1027
|
+
content: [
|
|
1028
|
+
{ type: "text", text: formatSearchResults(results, this.config.baseUrl) }
|
|
1029
|
+
]
|
|
1030
|
+
};
|
|
1031
|
+
} catch (error) {
|
|
1032
|
+
console.error("[MCP] Search error:", error);
|
|
1033
|
+
return {
|
|
1034
|
+
content: [{ type: "text", text: `Search error: ${String(error)}` }],
|
|
1035
|
+
isError: true
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
888
1038
|
}
|
|
889
1039
|
);
|
|
890
1040
|
this.mcpServer.registerTool(
|
|
@@ -897,16 +1047,24 @@ var McpDocsServer = class {
|
|
|
897
1047
|
},
|
|
898
1048
|
async ({ route }) => {
|
|
899
1049
|
await this.initialize();
|
|
900
|
-
if (!this.
|
|
1050
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
901
1051
|
return {
|
|
902
1052
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
903
1053
|
isError: true
|
|
904
1054
|
};
|
|
905
1055
|
}
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
1056
|
+
try {
|
|
1057
|
+
const doc = await this.getDocument(route);
|
|
1058
|
+
return {
|
|
1059
|
+
content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
|
|
1060
|
+
};
|
|
1061
|
+
} catch (error) {
|
|
1062
|
+
console.error("[MCP] Get page error:", error);
|
|
1063
|
+
return {
|
|
1064
|
+
content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
|
|
1065
|
+
isError: true
|
|
1066
|
+
};
|
|
1067
|
+
}
|
|
910
1068
|
}
|
|
911
1069
|
);
|
|
912
1070
|
this.mcpServer.registerTool(
|
|
@@ -922,26 +1080,95 @@ var McpDocsServer = class {
|
|
|
922
1080
|
},
|
|
923
1081
|
async ({ route, headingId }) => {
|
|
924
1082
|
await this.initialize();
|
|
925
|
-
if (!this.
|
|
1083
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
926
1084
|
return {
|
|
927
1085
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
928
1086
|
isError: true
|
|
929
1087
|
};
|
|
930
1088
|
}
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
{
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
1089
|
+
try {
|
|
1090
|
+
const doc = await this.getDocument(route);
|
|
1091
|
+
if (!doc) {
|
|
1092
|
+
return {
|
|
1093
|
+
content: [
|
|
1094
|
+
{
|
|
1095
|
+
type: "text",
|
|
1096
|
+
text: formatSectionContent(
|
|
1097
|
+
{ content: null, doc: null, headingText: null, availableHeadings: [] },
|
|
1098
|
+
headingId,
|
|
1099
|
+
this.config.baseUrl
|
|
1100
|
+
)
|
|
1101
|
+
}
|
|
1102
|
+
]
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
const availableHeadings = doc.headings.map((h) => ({
|
|
1106
|
+
id: h.id,
|
|
1107
|
+
text: h.text,
|
|
1108
|
+
level: h.level
|
|
1109
|
+
}));
|
|
1110
|
+
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
1111
|
+
if (!heading) {
|
|
1112
|
+
return {
|
|
1113
|
+
content: [
|
|
1114
|
+
{
|
|
1115
|
+
type: "text",
|
|
1116
|
+
text: formatSectionContent(
|
|
1117
|
+
{ content: null, doc, headingText: null, availableHeadings },
|
|
1118
|
+
headingId,
|
|
1119
|
+
this.config.baseUrl
|
|
1120
|
+
)
|
|
1121
|
+
}
|
|
1122
|
+
]
|
|
1123
|
+
};
|
|
1124
|
+
}
|
|
1125
|
+
const sectionContent = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
1126
|
+
return {
|
|
1127
|
+
content: [
|
|
1128
|
+
{
|
|
1129
|
+
type: "text",
|
|
1130
|
+
text: formatSectionContent(
|
|
1131
|
+
{ content: sectionContent, doc, headingText: heading.text, availableHeadings },
|
|
1132
|
+
headingId,
|
|
1133
|
+
this.config.baseUrl
|
|
1134
|
+
)
|
|
1135
|
+
}
|
|
1136
|
+
]
|
|
1137
|
+
};
|
|
1138
|
+
} catch (error) {
|
|
1139
|
+
console.error("[MCP] Get section error:", error);
|
|
1140
|
+
return {
|
|
1141
|
+
content: [{ type: "text", text: `Error getting section: ${String(error)}` }],
|
|
1142
|
+
isError: true
|
|
1143
|
+
};
|
|
1144
|
+
}
|
|
940
1145
|
}
|
|
941
1146
|
);
|
|
942
1147
|
}
|
|
943
1148
|
/**
|
|
944
|
-
*
|
|
1149
|
+
* Get a document by route using the search provider
|
|
1150
|
+
*/
|
|
1151
|
+
async getDocument(route) {
|
|
1152
|
+
if (!this.searchProvider) {
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
if (this.searchProvider.getDocument) {
|
|
1156
|
+
return this.searchProvider.getDocument(route);
|
|
1157
|
+
}
|
|
1158
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
1159
|
+
const docs = this.searchProvider.getDocs();
|
|
1160
|
+
if (!docs) return null;
|
|
1161
|
+
if (docs[route]) {
|
|
1162
|
+
return docs[route];
|
|
1163
|
+
}
|
|
1164
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
1165
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
1166
|
+
return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
|
|
1167
|
+
}
|
|
1168
|
+
return null;
|
|
1169
|
+
}
|
|
1170
|
+
/**
|
|
1171
|
+
* Load docs and search index using the configured search provider
|
|
945
1172
|
*
|
|
946
1173
|
* For file-based config: reads from disk
|
|
947
1174
|
* For data config: uses pre-loaded data directly
|
|
@@ -951,24 +1178,26 @@ var McpDocsServer = class {
|
|
|
951
1178
|
return;
|
|
952
1179
|
}
|
|
953
1180
|
try {
|
|
1181
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
1182
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
1183
|
+
const providerContext = {
|
|
1184
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
1185
|
+
serverName: this.config.name,
|
|
1186
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
1187
|
+
outputDir: ""
|
|
1188
|
+
// Not relevant for runtime
|
|
1189
|
+
};
|
|
1190
|
+
const initData = {};
|
|
954
1191
|
if (isDataConfig(this.config)) {
|
|
955
|
-
|
|
956
|
-
|
|
1192
|
+
initData.docs = this.config.docs;
|
|
1193
|
+
initData.indexData = this.config.searchIndexData;
|
|
957
1194
|
} else if (isFileConfig(this.config)) {
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
} else {
|
|
961
|
-
throw new Error(`Docs file not found: ${this.config.docsPath}`);
|
|
962
|
-
}
|
|
963
|
-
if (await fs3__default.default.pathExists(this.config.indexPath)) {
|
|
964
|
-
const indexData = await fs3__default.default.readJson(this.config.indexPath);
|
|
965
|
-
this.searchIndex = await importSearchIndex(indexData);
|
|
966
|
-
} else {
|
|
967
|
-
throw new Error(`Search index not found: ${this.config.indexPath}`);
|
|
968
|
-
}
|
|
1195
|
+
initData.docsPath = this.config.docsPath;
|
|
1196
|
+
initData.indexPath = this.config.indexPath;
|
|
969
1197
|
} else {
|
|
970
1198
|
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
971
1199
|
}
|
|
1200
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
972
1201
|
this.initialized = true;
|
|
973
1202
|
} catch (error) {
|
|
974
1203
|
console.error("[MCP] Failed to initialize:", error);
|
|
@@ -1029,12 +1258,18 @@ var McpDocsServer = class {
|
|
|
1029
1258
|
* Useful for health checks and debugging
|
|
1030
1259
|
*/
|
|
1031
1260
|
async getStatus() {
|
|
1261
|
+
let docCount = 0;
|
|
1262
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
1263
|
+
const docs = this.searchProvider.getDocs();
|
|
1264
|
+
docCount = docs ? Object.keys(docs).length : 0;
|
|
1265
|
+
}
|
|
1032
1266
|
return {
|
|
1033
1267
|
name: this.config.name,
|
|
1034
1268
|
version: this.config.version ?? "1.0.0",
|
|
1035
1269
|
initialized: this.initialized,
|
|
1036
|
-
docCount
|
|
1037
|
-
baseUrl: this.config.baseUrl
|
|
1270
|
+
docCount,
|
|
1271
|
+
baseUrl: this.config.baseUrl,
|
|
1272
|
+
searchProvider: this.searchProvider?.name
|
|
1038
1273
|
};
|
|
1039
1274
|
}
|
|
1040
1275
|
/**
|
|
@@ -1048,6 +1283,8 @@ var McpDocsServer = class {
|
|
|
1048
1283
|
};
|
|
1049
1284
|
|
|
1050
1285
|
exports.DEFAULT_OPTIONS = DEFAULT_OPTIONS;
|
|
1286
|
+
exports.FlexSearchIndexer = FlexSearchIndexer;
|
|
1287
|
+
exports.FlexSearchProvider = FlexSearchProvider;
|
|
1051
1288
|
exports.McpDocsServer = McpDocsServer;
|
|
1052
1289
|
exports.buildSearchIndex = buildSearchIndex;
|
|
1053
1290
|
exports.collectRoutes = collectRoutes;
|
|
@@ -1062,6 +1299,8 @@ exports.extractHeadingsFromMarkdown = extractHeadingsFromMarkdown;
|
|
|
1062
1299
|
exports.extractSection = extractSection;
|
|
1063
1300
|
exports.htmlToMarkdown = htmlToMarkdown;
|
|
1064
1301
|
exports.importSearchIndex = importSearchIndex;
|
|
1302
|
+
exports.loadIndexer = loadIndexer;
|
|
1303
|
+
exports.loadSearchProvider = loadSearchProvider;
|
|
1065
1304
|
exports.mcpServerPlugin = mcpServerPlugin;
|
|
1066
1305
|
exports.parseHtml = parseHtml;
|
|
1067
1306
|
exports.parseHtmlFile = parseHtmlFile;
|