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.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import path from 'path';
|
|
2
2
|
import fs3 from 'fs-extra';
|
|
3
3
|
import pMap from 'p-map';
|
|
4
4
|
import { unified } from 'unified';
|
|
@@ -34,7 +34,10 @@ var DEFAULT_OPTIONS = {
|
|
|
34
34
|
name: "docs-mcp-server",
|
|
35
35
|
version: "1.0.0"
|
|
36
36
|
},
|
|
37
|
-
excludeRoutes: ["/404*", "/search*"]
|
|
37
|
+
excludeRoutes: ["/404*", "/search*"],
|
|
38
|
+
indexers: void 0,
|
|
39
|
+
// Default: ['flexsearch'] applied at runtime
|
|
40
|
+
search: "flexsearch"
|
|
38
41
|
};
|
|
39
42
|
function filterRoutes(routes, excludePatterns) {
|
|
40
43
|
return routes.filter((route) => {
|
|
@@ -50,15 +53,15 @@ async function discoverHtmlFiles(outDir) {
|
|
|
50
53
|
async function scanDirectory(dir) {
|
|
51
54
|
const entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
52
55
|
for (const entry of entries) {
|
|
53
|
-
const fullPath =
|
|
56
|
+
const fullPath = path.join(dir, entry.name);
|
|
54
57
|
if (entry.isDirectory()) {
|
|
55
58
|
if (["assets", "img", "static"].includes(entry.name)) {
|
|
56
59
|
continue;
|
|
57
60
|
}
|
|
58
61
|
await scanDirectory(fullPath);
|
|
59
62
|
} else if (entry.name === "index.html") {
|
|
60
|
-
const relativePath =
|
|
61
|
-
let routePath = "/" +
|
|
63
|
+
const relativePath = path.relative(outDir, fullPath);
|
|
64
|
+
let routePath = "/" + path.dirname(relativePath).replace(/\\/g, "/");
|
|
62
65
|
if (routePath === "/.") {
|
|
63
66
|
routePath = "/";
|
|
64
67
|
}
|
|
@@ -489,6 +492,201 @@ async function importSearchIndex(data) {
|
|
|
489
492
|
return index;
|
|
490
493
|
}
|
|
491
494
|
|
|
495
|
+
// src/providers/indexers/flexsearch-indexer.ts
|
|
496
|
+
var FlexSearchIndexer = class {
|
|
497
|
+
name = "flexsearch";
|
|
498
|
+
docsIndex = {};
|
|
499
|
+
exportedIndex = null;
|
|
500
|
+
docCount = 0;
|
|
501
|
+
/**
|
|
502
|
+
* FlexSearch indexer always runs by default.
|
|
503
|
+
* It respects the indexers configuration - if not included, it won't run.
|
|
504
|
+
*/
|
|
505
|
+
shouldRun() {
|
|
506
|
+
return true;
|
|
507
|
+
}
|
|
508
|
+
async initialize(_context) {
|
|
509
|
+
this.docsIndex = {};
|
|
510
|
+
this.exportedIndex = null;
|
|
511
|
+
this.docCount = 0;
|
|
512
|
+
}
|
|
513
|
+
async indexDocuments(docs) {
|
|
514
|
+
this.docCount = docs.length;
|
|
515
|
+
for (const doc of docs) {
|
|
516
|
+
this.docsIndex[doc.route] = doc;
|
|
517
|
+
}
|
|
518
|
+
console.log("[FlexSearch] Building search index...");
|
|
519
|
+
const searchIndex2 = buildSearchIndex(docs);
|
|
520
|
+
this.exportedIndex = await exportSearchIndex(searchIndex2);
|
|
521
|
+
console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
|
|
522
|
+
}
|
|
523
|
+
async finalize() {
|
|
524
|
+
const artifacts = /* @__PURE__ */ new Map();
|
|
525
|
+
artifacts.set("docs.json", this.docsIndex);
|
|
526
|
+
artifacts.set("search-index.json", this.exportedIndex);
|
|
527
|
+
return artifacts;
|
|
528
|
+
}
|
|
529
|
+
async getManifestData() {
|
|
530
|
+
return {
|
|
531
|
+
searchEngine: "flexsearch"
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
};
|
|
535
|
+
var FlexSearchProvider = class {
|
|
536
|
+
name = "flexsearch";
|
|
537
|
+
docs = null;
|
|
538
|
+
searchIndex = null;
|
|
539
|
+
ready = false;
|
|
540
|
+
async initialize(_context, initData) {
|
|
541
|
+
if (!initData) {
|
|
542
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
543
|
+
}
|
|
544
|
+
if (initData.docs && initData.indexData) {
|
|
545
|
+
this.docs = initData.docs;
|
|
546
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
547
|
+
this.ready = true;
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
if (initData.docsPath && initData.indexPath) {
|
|
551
|
+
if (await fs3.pathExists(initData.docsPath)) {
|
|
552
|
+
this.docs = await fs3.readJson(initData.docsPath);
|
|
553
|
+
} else {
|
|
554
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
555
|
+
}
|
|
556
|
+
if (await fs3.pathExists(initData.indexPath)) {
|
|
557
|
+
const indexData = await fs3.readJson(initData.indexPath);
|
|
558
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
559
|
+
} else {
|
|
560
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
561
|
+
}
|
|
562
|
+
this.ready = true;
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
throw new Error(
|
|
566
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
isReady() {
|
|
570
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
571
|
+
}
|
|
572
|
+
async search(query, options) {
|
|
573
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
574
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
575
|
+
}
|
|
576
|
+
const limit = options?.limit ?? 5;
|
|
577
|
+
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
578
|
+
}
|
|
579
|
+
async getDocument(route) {
|
|
580
|
+
if (!this.docs) {
|
|
581
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
582
|
+
}
|
|
583
|
+
if (this.docs[route]) {
|
|
584
|
+
return this.docs[route];
|
|
585
|
+
}
|
|
586
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
587
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
588
|
+
return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
|
|
589
|
+
}
|
|
590
|
+
async healthCheck() {
|
|
591
|
+
if (!this.isReady()) {
|
|
592
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
593
|
+
}
|
|
594
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
595
|
+
return {
|
|
596
|
+
healthy: true,
|
|
597
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
598
|
+
};
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
602
|
+
*/
|
|
603
|
+
getDocs() {
|
|
604
|
+
return this.docs;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
608
|
+
*/
|
|
609
|
+
getSearchIndex() {
|
|
610
|
+
return this.searchIndex;
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// src/providers/loader.ts
|
|
615
|
+
async function loadIndexer(specifier) {
|
|
616
|
+
if (specifier === "flexsearch") {
|
|
617
|
+
return new FlexSearchIndexer();
|
|
618
|
+
}
|
|
619
|
+
try {
|
|
620
|
+
const module = await import(specifier);
|
|
621
|
+
const IndexerClass = module.default;
|
|
622
|
+
if (typeof IndexerClass === "function") {
|
|
623
|
+
const instance = new IndexerClass();
|
|
624
|
+
if (!isContentIndexer(instance)) {
|
|
625
|
+
throw new Error(
|
|
626
|
+
`Invalid indexer module "${specifier}": does not implement ContentIndexer interface`
|
|
627
|
+
);
|
|
628
|
+
}
|
|
629
|
+
return instance;
|
|
630
|
+
}
|
|
631
|
+
if (isContentIndexer(IndexerClass)) {
|
|
632
|
+
return IndexerClass;
|
|
633
|
+
}
|
|
634
|
+
throw new Error(
|
|
635
|
+
`Invalid indexer module "${specifier}": must export a default class or ContentIndexer instance`
|
|
636
|
+
);
|
|
637
|
+
} catch (error) {
|
|
638
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
639
|
+
throw new Error(`Indexer module not found: "${specifier}". Check the path or package name.`);
|
|
640
|
+
}
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
async function loadSearchProvider(specifier) {
|
|
645
|
+
if (specifier === "flexsearch") {
|
|
646
|
+
return new FlexSearchProvider();
|
|
647
|
+
}
|
|
648
|
+
try {
|
|
649
|
+
const module = await import(specifier);
|
|
650
|
+
const ProviderClass = module.default;
|
|
651
|
+
if (typeof ProviderClass === "function") {
|
|
652
|
+
const instance = new ProviderClass();
|
|
653
|
+
if (!isSearchProvider(instance)) {
|
|
654
|
+
throw new Error(
|
|
655
|
+
`Invalid search provider module "${specifier}": does not implement SearchProvider interface`
|
|
656
|
+
);
|
|
657
|
+
}
|
|
658
|
+
return instance;
|
|
659
|
+
}
|
|
660
|
+
if (isSearchProvider(ProviderClass)) {
|
|
661
|
+
return ProviderClass;
|
|
662
|
+
}
|
|
663
|
+
throw new Error(
|
|
664
|
+
`Invalid search provider module "${specifier}": must export a default class or SearchProvider instance`
|
|
665
|
+
);
|
|
666
|
+
} catch (error) {
|
|
667
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
668
|
+
throw new Error(
|
|
669
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
throw error;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
function isContentIndexer(obj) {
|
|
676
|
+
if (!obj || typeof obj !== "object") {
|
|
677
|
+
return false;
|
|
678
|
+
}
|
|
679
|
+
const indexer = obj;
|
|
680
|
+
return typeof indexer.name === "string" && typeof indexer.initialize === "function" && typeof indexer.indexDocuments === "function" && typeof indexer.finalize === "function";
|
|
681
|
+
}
|
|
682
|
+
function isSearchProvider(obj) {
|
|
683
|
+
if (!obj || typeof obj !== "object") {
|
|
684
|
+
return false;
|
|
685
|
+
}
|
|
686
|
+
const provider = obj;
|
|
687
|
+
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
688
|
+
}
|
|
689
|
+
|
|
492
690
|
// src/plugin/docusaurus-plugin.ts
|
|
493
691
|
function resolveOptions(options) {
|
|
494
692
|
return {
|
|
@@ -545,6 +743,10 @@ function mcpServerPlugin(context, options) {
|
|
|
545
743
|
async postBuild({ outDir }) {
|
|
546
744
|
console.log("[MCP] Starting MCP artifact generation...");
|
|
547
745
|
const startTime = Date.now();
|
|
746
|
+
if (resolvedOptions.indexers === false) {
|
|
747
|
+
console.log("[MCP] Indexing disabled, skipping artifact generation");
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
548
750
|
const routes = await collectRoutes(outDir, resolvedOptions.excludeRoutes);
|
|
549
751
|
console.log(`[MCP] Found ${routes.length} routes to process`);
|
|
550
752
|
if (routes.length === 0) {
|
|
@@ -569,27 +771,47 @@ function mcpServerPlugin(context, options) {
|
|
|
569
771
|
console.warn("[MCP] No valid documents to index");
|
|
570
772
|
return;
|
|
571
773
|
}
|
|
572
|
-
const
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
}
|
|
576
|
-
console.log("[MCP] Building search index...");
|
|
577
|
-
const searchIndex2 = buildSearchIndex(validDocs);
|
|
578
|
-
const exportedIndex = await exportSearchIndex(searchIndex2);
|
|
579
|
-
const manifest = {
|
|
580
|
-
version: resolvedOptions.server.version,
|
|
581
|
-
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
582
|
-
docCount: validDocs.length,
|
|
774
|
+
const mcpOutputDir = path.join(outDir, resolvedOptions.outputDir);
|
|
775
|
+
const providerContext = {
|
|
776
|
+
baseUrl: context.siteConfig.url,
|
|
583
777
|
serverName: resolvedOptions.server.name,
|
|
584
|
-
|
|
778
|
+
serverVersion: resolvedOptions.server.version,
|
|
779
|
+
outputDir: mcpOutputDir
|
|
585
780
|
};
|
|
586
|
-
const
|
|
781
|
+
const indexerSpecs = resolvedOptions.indexers ?? ["flexsearch"];
|
|
587
782
|
await fs3.ensureDir(mcpOutputDir);
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
783
|
+
const indexerNames = [];
|
|
784
|
+
for (const indexerSpec of indexerSpecs) {
|
|
785
|
+
try {
|
|
786
|
+
const indexer = await loadIndexer(indexerSpec);
|
|
787
|
+
if (indexer.shouldRun && !indexer.shouldRun()) {
|
|
788
|
+
console.log(`[MCP] Skipping indexer: ${indexer.name}`);
|
|
789
|
+
continue;
|
|
790
|
+
}
|
|
791
|
+
console.log(`[MCP] Running indexer: ${indexer.name}`);
|
|
792
|
+
await indexer.initialize(providerContext);
|
|
793
|
+
await indexer.indexDocuments(validDocs);
|
|
794
|
+
const artifacts = await indexer.finalize();
|
|
795
|
+
for (const [filename, content] of artifacts) {
|
|
796
|
+
await fs3.writeJson(path.join(mcpOutputDir, filename), content, { spaces: 0 });
|
|
797
|
+
}
|
|
798
|
+
indexerNames.push(indexer.name);
|
|
799
|
+
} catch (error) {
|
|
800
|
+
console.error(`[MCP] Error running indexer "${indexerSpec}":`, error);
|
|
801
|
+
throw error;
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
if (indexerNames.length > 0) {
|
|
805
|
+
const manifest = {
|
|
806
|
+
version: resolvedOptions.server.version,
|
|
807
|
+
buildTime: (/* @__PURE__ */ new Date()).toISOString(),
|
|
808
|
+
docCount: validDocs.length,
|
|
809
|
+
serverName: resolvedOptions.server.name,
|
|
810
|
+
baseUrl: context.siteConfig.url,
|
|
811
|
+
indexers: indexerNames
|
|
812
|
+
};
|
|
813
|
+
await fs3.writeJson(path.join(mcpOutputDir, "manifest.json"), manifest, { spaces: 2 });
|
|
814
|
+
}
|
|
593
815
|
const elapsed = Date.now() - startTime;
|
|
594
816
|
console.log(`[MCP] Artifacts written to ${mcpOutputDir}`);
|
|
595
817
|
console.log(`[MCP] Generation complete in ${elapsed}ms`);
|
|
@@ -617,17 +839,6 @@ var docsSearchTool = {
|
|
|
617
839
|
required: ["query"]
|
|
618
840
|
}
|
|
619
841
|
};
|
|
620
|
-
function executeDocsSearch(params, index, docs) {
|
|
621
|
-
const { query, limit = 5 } = params;
|
|
622
|
-
if (!query || typeof query !== "string" || query.trim().length === 0) {
|
|
623
|
-
throw new Error("Query parameter is required and must be a non-empty string");
|
|
624
|
-
}
|
|
625
|
-
const effectiveLimit = Math.min(Math.max(1, limit), 20);
|
|
626
|
-
const results = searchIndex(index, docs, query.trim(), {
|
|
627
|
-
limit: effectiveLimit
|
|
628
|
-
});
|
|
629
|
-
return results;
|
|
630
|
-
}
|
|
631
842
|
function formatSearchResults(results, baseUrl) {
|
|
632
843
|
if (results.length === 0) {
|
|
633
844
|
return "No matching documents found.";
|
|
@@ -667,28 +878,6 @@ var docsGetPageTool = {
|
|
|
667
878
|
required: ["route"]
|
|
668
879
|
}
|
|
669
880
|
};
|
|
670
|
-
function executeDocsGetPage(params, docs) {
|
|
671
|
-
const { route } = params;
|
|
672
|
-
if (!route || typeof route !== "string") {
|
|
673
|
-
throw new Error("Route parameter is required and must be a string");
|
|
674
|
-
}
|
|
675
|
-
let normalizedRoute = route.trim();
|
|
676
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
677
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
678
|
-
}
|
|
679
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
680
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
681
|
-
}
|
|
682
|
-
const doc = docs[normalizedRoute];
|
|
683
|
-
if (!doc) {
|
|
684
|
-
const altRoute = normalizedRoute.slice(1);
|
|
685
|
-
if (docs[altRoute]) {
|
|
686
|
-
return docs[altRoute] ?? null;
|
|
687
|
-
}
|
|
688
|
-
return null;
|
|
689
|
-
}
|
|
690
|
-
return doc;
|
|
691
|
-
}
|
|
692
881
|
function formatPageContent(doc, baseUrl) {
|
|
693
882
|
if (!doc) {
|
|
694
883
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -742,52 +931,6 @@ var docsGetSectionTool = {
|
|
|
742
931
|
required: ["route", "headingId"]
|
|
743
932
|
}
|
|
744
933
|
};
|
|
745
|
-
function executeDocsGetSection(params, docs) {
|
|
746
|
-
const { route, headingId } = params;
|
|
747
|
-
if (!route || typeof route !== "string") {
|
|
748
|
-
throw new Error("Route parameter is required and must be a string");
|
|
749
|
-
}
|
|
750
|
-
if (!headingId || typeof headingId !== "string") {
|
|
751
|
-
throw new Error("HeadingId parameter is required and must be a string");
|
|
752
|
-
}
|
|
753
|
-
let normalizedRoute = route.trim();
|
|
754
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
755
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
756
|
-
}
|
|
757
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
758
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
759
|
-
}
|
|
760
|
-
const doc = docs[normalizedRoute];
|
|
761
|
-
if (!doc) {
|
|
762
|
-
return {
|
|
763
|
-
content: null,
|
|
764
|
-
doc: null,
|
|
765
|
-
headingText: null,
|
|
766
|
-
availableHeadings: []
|
|
767
|
-
};
|
|
768
|
-
}
|
|
769
|
-
const availableHeadings = doc.headings.map((h) => ({
|
|
770
|
-
id: h.id,
|
|
771
|
-
text: h.text,
|
|
772
|
-
level: h.level
|
|
773
|
-
}));
|
|
774
|
-
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
775
|
-
if (!heading) {
|
|
776
|
-
return {
|
|
777
|
-
content: null,
|
|
778
|
-
doc,
|
|
779
|
-
headingText: null,
|
|
780
|
-
availableHeadings
|
|
781
|
-
};
|
|
782
|
-
}
|
|
783
|
-
const content = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
784
|
-
return {
|
|
785
|
-
content,
|
|
786
|
-
doc,
|
|
787
|
-
headingText: heading.text,
|
|
788
|
-
availableHeadings
|
|
789
|
-
};
|
|
790
|
-
}
|
|
791
934
|
function formatSectionContent(result, headingId, baseUrl) {
|
|
792
935
|
if (!result.doc) {
|
|
793
936
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -824,8 +967,7 @@ function isDataConfig(config) {
|
|
|
824
967
|
}
|
|
825
968
|
var McpDocsServer = class {
|
|
826
969
|
config;
|
|
827
|
-
|
|
828
|
-
searchIndex = null;
|
|
970
|
+
searchProvider = null;
|
|
829
971
|
mcpServer;
|
|
830
972
|
initialized = false;
|
|
831
973
|
constructor(config) {
|
|
@@ -858,18 +1000,26 @@ var McpDocsServer = class {
|
|
|
858
1000
|
},
|
|
859
1001
|
async ({ query, limit }) => {
|
|
860
1002
|
await this.initialize();
|
|
861
|
-
if (!this.
|
|
1003
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
862
1004
|
return {
|
|
863
1005
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
864
1006
|
isError: true
|
|
865
1007
|
};
|
|
866
1008
|
}
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
1009
|
+
try {
|
|
1010
|
+
const results = await this.searchProvider.search(query, { limit });
|
|
1011
|
+
return {
|
|
1012
|
+
content: [
|
|
1013
|
+
{ type: "text", text: formatSearchResults(results, this.config.baseUrl) }
|
|
1014
|
+
]
|
|
1015
|
+
};
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
console.error("[MCP] Search error:", error);
|
|
1018
|
+
return {
|
|
1019
|
+
content: [{ type: "text", text: `Search error: ${String(error)}` }],
|
|
1020
|
+
isError: true
|
|
1021
|
+
};
|
|
1022
|
+
}
|
|
873
1023
|
}
|
|
874
1024
|
);
|
|
875
1025
|
this.mcpServer.registerTool(
|
|
@@ -882,16 +1032,24 @@ var McpDocsServer = class {
|
|
|
882
1032
|
},
|
|
883
1033
|
async ({ route }) => {
|
|
884
1034
|
await this.initialize();
|
|
885
|
-
if (!this.
|
|
1035
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
886
1036
|
return {
|
|
887
1037
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
888
1038
|
isError: true
|
|
889
1039
|
};
|
|
890
1040
|
}
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
1041
|
+
try {
|
|
1042
|
+
const doc = await this.getDocument(route);
|
|
1043
|
+
return {
|
|
1044
|
+
content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
|
|
1045
|
+
};
|
|
1046
|
+
} catch (error) {
|
|
1047
|
+
console.error("[MCP] Get page error:", error);
|
|
1048
|
+
return {
|
|
1049
|
+
content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
|
|
1050
|
+
isError: true
|
|
1051
|
+
};
|
|
1052
|
+
}
|
|
895
1053
|
}
|
|
896
1054
|
);
|
|
897
1055
|
this.mcpServer.registerTool(
|
|
@@ -907,26 +1065,95 @@ var McpDocsServer = class {
|
|
|
907
1065
|
},
|
|
908
1066
|
async ({ route, headingId }) => {
|
|
909
1067
|
await this.initialize();
|
|
910
|
-
if (!this.
|
|
1068
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
911
1069
|
return {
|
|
912
1070
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
913
1071
|
isError: true
|
|
914
1072
|
};
|
|
915
1073
|
}
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
{
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
1074
|
+
try {
|
|
1075
|
+
const doc = await this.getDocument(route);
|
|
1076
|
+
if (!doc) {
|
|
1077
|
+
return {
|
|
1078
|
+
content: [
|
|
1079
|
+
{
|
|
1080
|
+
type: "text",
|
|
1081
|
+
text: formatSectionContent(
|
|
1082
|
+
{ content: null, doc: null, headingText: null, availableHeadings: [] },
|
|
1083
|
+
headingId,
|
|
1084
|
+
this.config.baseUrl
|
|
1085
|
+
)
|
|
1086
|
+
}
|
|
1087
|
+
]
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
const availableHeadings = doc.headings.map((h) => ({
|
|
1091
|
+
id: h.id,
|
|
1092
|
+
text: h.text,
|
|
1093
|
+
level: h.level
|
|
1094
|
+
}));
|
|
1095
|
+
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
1096
|
+
if (!heading) {
|
|
1097
|
+
return {
|
|
1098
|
+
content: [
|
|
1099
|
+
{
|
|
1100
|
+
type: "text",
|
|
1101
|
+
text: formatSectionContent(
|
|
1102
|
+
{ content: null, doc, headingText: null, availableHeadings },
|
|
1103
|
+
headingId,
|
|
1104
|
+
this.config.baseUrl
|
|
1105
|
+
)
|
|
1106
|
+
}
|
|
1107
|
+
]
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
const sectionContent = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
1111
|
+
return {
|
|
1112
|
+
content: [
|
|
1113
|
+
{
|
|
1114
|
+
type: "text",
|
|
1115
|
+
text: formatSectionContent(
|
|
1116
|
+
{ content: sectionContent, doc, headingText: heading.text, availableHeadings },
|
|
1117
|
+
headingId,
|
|
1118
|
+
this.config.baseUrl
|
|
1119
|
+
)
|
|
1120
|
+
}
|
|
1121
|
+
]
|
|
1122
|
+
};
|
|
1123
|
+
} catch (error) {
|
|
1124
|
+
console.error("[MCP] Get section error:", error);
|
|
1125
|
+
return {
|
|
1126
|
+
content: [{ type: "text", text: `Error getting section: ${String(error)}` }],
|
|
1127
|
+
isError: true
|
|
1128
|
+
};
|
|
1129
|
+
}
|
|
925
1130
|
}
|
|
926
1131
|
);
|
|
927
1132
|
}
|
|
928
1133
|
/**
|
|
929
|
-
*
|
|
1134
|
+
* Get a document by route using the search provider
|
|
1135
|
+
*/
|
|
1136
|
+
async getDocument(route) {
|
|
1137
|
+
if (!this.searchProvider) {
|
|
1138
|
+
return null;
|
|
1139
|
+
}
|
|
1140
|
+
if (this.searchProvider.getDocument) {
|
|
1141
|
+
return this.searchProvider.getDocument(route);
|
|
1142
|
+
}
|
|
1143
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
1144
|
+
const docs = this.searchProvider.getDocs();
|
|
1145
|
+
if (!docs) return null;
|
|
1146
|
+
if (docs[route]) {
|
|
1147
|
+
return docs[route];
|
|
1148
|
+
}
|
|
1149
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
1150
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
1151
|
+
return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
|
|
1152
|
+
}
|
|
1153
|
+
return null;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Load docs and search index using the configured search provider
|
|
930
1157
|
*
|
|
931
1158
|
* For file-based config: reads from disk
|
|
932
1159
|
* For data config: uses pre-loaded data directly
|
|
@@ -936,24 +1163,26 @@ var McpDocsServer = class {
|
|
|
936
1163
|
return;
|
|
937
1164
|
}
|
|
938
1165
|
try {
|
|
1166
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
1167
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
1168
|
+
const providerContext = {
|
|
1169
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
1170
|
+
serverName: this.config.name,
|
|
1171
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
1172
|
+
outputDir: ""
|
|
1173
|
+
// Not relevant for runtime
|
|
1174
|
+
};
|
|
1175
|
+
const initData = {};
|
|
939
1176
|
if (isDataConfig(this.config)) {
|
|
940
|
-
|
|
941
|
-
|
|
1177
|
+
initData.docs = this.config.docs;
|
|
1178
|
+
initData.indexData = this.config.searchIndexData;
|
|
942
1179
|
} else if (isFileConfig(this.config)) {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
} else {
|
|
946
|
-
throw new Error(`Docs file not found: ${this.config.docsPath}`);
|
|
947
|
-
}
|
|
948
|
-
if (await fs3.pathExists(this.config.indexPath)) {
|
|
949
|
-
const indexData = await fs3.readJson(this.config.indexPath);
|
|
950
|
-
this.searchIndex = await importSearchIndex(indexData);
|
|
951
|
-
} else {
|
|
952
|
-
throw new Error(`Search index not found: ${this.config.indexPath}`);
|
|
953
|
-
}
|
|
1180
|
+
initData.docsPath = this.config.docsPath;
|
|
1181
|
+
initData.indexPath = this.config.indexPath;
|
|
954
1182
|
} else {
|
|
955
1183
|
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
956
1184
|
}
|
|
1185
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
957
1186
|
this.initialized = true;
|
|
958
1187
|
} catch (error) {
|
|
959
1188
|
console.error("[MCP] Failed to initialize:", error);
|
|
@@ -1014,12 +1243,18 @@ var McpDocsServer = class {
|
|
|
1014
1243
|
* Useful for health checks and debugging
|
|
1015
1244
|
*/
|
|
1016
1245
|
async getStatus() {
|
|
1246
|
+
let docCount = 0;
|
|
1247
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
1248
|
+
const docs = this.searchProvider.getDocs();
|
|
1249
|
+
docCount = docs ? Object.keys(docs).length : 0;
|
|
1250
|
+
}
|
|
1017
1251
|
return {
|
|
1018
1252
|
name: this.config.name,
|
|
1019
1253
|
version: this.config.version ?? "1.0.0",
|
|
1020
1254
|
initialized: this.initialized,
|
|
1021
|
-
docCount
|
|
1022
|
-
baseUrl: this.config.baseUrl
|
|
1255
|
+
docCount,
|
|
1256
|
+
baseUrl: this.config.baseUrl,
|
|
1257
|
+
searchProvider: this.searchProvider?.name
|
|
1023
1258
|
};
|
|
1024
1259
|
}
|
|
1025
1260
|
/**
|
|
@@ -1032,6 +1267,6 @@ var McpDocsServer = class {
|
|
|
1032
1267
|
}
|
|
1033
1268
|
};
|
|
1034
1269
|
|
|
1035
|
-
export { DEFAULT_OPTIONS, McpDocsServer, buildSearchIndex, collectRoutes, mcpServerPlugin as default, discoverHtmlFiles, docsGetPageTool, docsGetSectionTool, docsSearchTool, exportSearchIndex, extractContent, extractHeadingsFromMarkdown, extractSection, htmlToMarkdown, importSearchIndex, mcpServerPlugin, parseHtml, parseHtmlFile, searchIndex };
|
|
1270
|
+
export { DEFAULT_OPTIONS, FlexSearchIndexer, FlexSearchProvider, McpDocsServer, buildSearchIndex, collectRoutes, mcpServerPlugin as default, discoverHtmlFiles, docsGetPageTool, docsGetSectionTool, docsSearchTool, exportSearchIndex, extractContent, extractHeadingsFromMarkdown, extractSection, htmlToMarkdown, importSearchIndex, loadIndexer, loadSearchProvider, mcpServerPlugin, parseHtml, parseHtmlFile, searchIndex };
|
|
1036
1271
|
//# sourceMappingURL=index.mjs.map
|
|
1037
1272
|
//# sourceMappingURL=index.mjs.map
|