docusaurus-plugin-mcp-server 0.8.0 → 0.10.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.mjs CHANGED
@@ -5,6 +5,7 @@ import { unified } from 'unified';
5
5
  import rehypeParse from 'rehype-parse';
6
6
  import { select } from 'hast-util-select';
7
7
  import { toString } from 'hast-util-to-string';
8
+ import { toHtml } from 'hast-util-to-html';
8
9
  import rehypeRemark from 'rehype-remark';
9
10
  import remarkStringify from 'remark-stringify';
10
11
  import remarkGfm from 'remark-gfm';
@@ -182,7 +183,7 @@ async function extractContent(filePath, options) {
182
183
  let contentHtml = "";
183
184
  if (contentElement) {
184
185
  const cleanedElement = cleanContentElement(contentElement, options.excludeSelectors);
185
- contentHtml = serializeElement(cleanedElement);
186
+ contentHtml = toHtml(cleanedElement);
186
187
  }
187
188
  return {
188
189
  title,
@@ -190,57 +191,6 @@ async function extractContent(filePath, options) {
190
191
  contentHtml
191
192
  };
192
193
  }
193
- function serializeElement(element) {
194
- const voidElements = /* @__PURE__ */ new Set([
195
- "area",
196
- "base",
197
- "br",
198
- "col",
199
- "embed",
200
- "hr",
201
- "img",
202
- "input",
203
- "link",
204
- "meta",
205
- "param",
206
- "source",
207
- "track",
208
- "wbr"
209
- ]);
210
- function serialize(node) {
211
- if (!node || typeof node !== "object") return "";
212
- const n = node;
213
- if (n.type === "text") {
214
- return n.value ?? "";
215
- }
216
- if (n.type === "element" && n.tagName) {
217
- const tagName = n.tagName;
218
- const props = n.properties ?? {};
219
- const attrs = [];
220
- for (const [key, value] of Object.entries(props)) {
221
- if (key === "className" && Array.isArray(value)) {
222
- attrs.push(`class="${value.join(" ")}"`);
223
- } else if (typeof value === "boolean") {
224
- if (value) attrs.push(key);
225
- } else if (value !== void 0 && value !== null) {
226
- attrs.push(`${key}="${String(value)}"`);
227
- }
228
- }
229
- const attrStr = attrs.length > 0 ? " " + attrs.join(" ") : "";
230
- if (voidElements.has(tagName)) {
231
- return `<${tagName}${attrStr} />`;
232
- }
233
- const children = n.children ?? [];
234
- const childrenHtml = children.map(serialize).join("");
235
- return `<${tagName}${attrStr}>${childrenHtml}</${tagName}>`;
236
- }
237
- if (n.type === "root" && n.children) {
238
- return n.children.map(serialize).join("");
239
- }
240
- return "";
241
- }
242
- return serialize(element);
243
- }
244
194
  async function htmlToMarkdown(html) {
245
195
  if (!html || html.trim().length === 0) {
246
196
  return "";
@@ -377,9 +327,10 @@ function createSearchIndex() {
377
327
  }
378
328
  });
379
329
  }
380
- function addDocumentToIndex(index, doc) {
330
+ function addDocumentToIndex(index, doc, baseUrl) {
331
+ const id = baseUrl ? `${baseUrl.replace(/\/$/, "")}${doc.route}` : doc.route;
381
332
  const indexable = {
382
- id: doc.route,
333
+ id,
383
334
  title: doc.title,
384
335
  content: doc.markdown,
385
336
  headings: doc.headings.map((h) => h.text).join(" "),
@@ -387,10 +338,10 @@ function addDocumentToIndex(index, doc) {
387
338
  };
388
339
  index.add(indexable);
389
340
  }
390
- function buildSearchIndex(docs) {
341
+ function buildSearchIndex(docs, baseUrl) {
391
342
  const index = createSearchIndex();
392
343
  for (const doc of docs) {
393
- addDocumentToIndex(index, doc);
344
+ addDocumentToIndex(index, doc, baseUrl);
394
345
  }
395
346
  return index;
396
347
  }
@@ -421,6 +372,8 @@ function searchIndex(index, docs, query, options = {}) {
421
372
  const doc = docs[docId];
422
373
  if (!doc) continue;
423
374
  results.push({
375
+ url: docId,
376
+ // docId is the full URL when indexed with baseUrl
424
377
  route: doc.route,
425
378
  title: doc.title,
426
379
  score,
@@ -495,6 +448,7 @@ async function importSearchIndex(data) {
495
448
  // src/providers/indexers/flexsearch-indexer.ts
496
449
  var FlexSearchIndexer = class {
497
450
  name = "flexsearch";
451
+ baseUrl = "";
498
452
  docsIndex = {};
499
453
  exportedIndex = null;
500
454
  docCount = 0;
@@ -505,7 +459,8 @@ var FlexSearchIndexer = class {
505
459
  shouldRun() {
506
460
  return true;
507
461
  }
508
- async initialize(_context) {
462
+ async initialize(context) {
463
+ this.baseUrl = context.baseUrl.replace(/\/$/, "");
509
464
  this.docsIndex = {};
510
465
  this.exportedIndex = null;
511
466
  this.docCount = 0;
@@ -513,10 +468,11 @@ var FlexSearchIndexer = class {
513
468
  async indexDocuments(docs) {
514
469
  this.docCount = docs.length;
515
470
  for (const doc of docs) {
516
- this.docsIndex[doc.route] = doc;
471
+ const fullUrl = `${this.baseUrl}${doc.route}`;
472
+ this.docsIndex[fullUrl] = doc;
517
473
  }
518
474
  console.log("[FlexSearch] Building search index...");
519
- const searchIndex2 = buildSearchIndex(docs);
475
+ const searchIndex2 = buildSearchIndex(docs, this.baseUrl);
520
476
  this.exportedIndex = await exportSearchIndex(searchIndex2);
521
477
  console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
522
478
  }
@@ -576,16 +532,11 @@ var FlexSearchProvider = class {
576
532
  const limit = options?.limit ?? 5;
577
533
  return searchIndex(this.searchIndex, this.docs, query, { limit });
578
534
  }
579
- async getDocument(route) {
535
+ async getDocument(url) {
580
536
  if (!this.docs) {
581
537
  throw new Error("[FlexSearch] Provider not initialized");
582
538
  }
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;
539
+ return this.docs[url] ?? null;
589
540
  }
590
541
  async healthCheck() {
591
542
  if (!this.isReady()) {
@@ -818,28 +769,16 @@ function mcpServerPlugin(context, options) {
818
769
  }
819
770
  };
820
771
  }
821
-
822
- // src/mcp/tools/docs-search.ts
772
+ var docsSearchInputSchema = {
773
+ query: z.string().min(1).describe("The search query string"),
774
+ limit: z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
775
+ };
823
776
  var docsSearchTool = {
824
777
  name: "docs_search",
825
- description: "Search across developer documentation. Returns ranked results with snippets and matching headings.",
826
- inputSchema: {
827
- type: "object",
828
- properties: {
829
- query: {
830
- type: "string",
831
- description: "Search query string"
832
- },
833
- limit: {
834
- type: "number",
835
- description: "Maximum number of results to return (default: 5, max: 20)",
836
- default: 5
837
- }
838
- },
839
- required: ["query"]
840
- }
778
+ description: "Search the documentation for relevant pages. Returns matching documents with URLs, snippets, and relevance scores. Use this to find information across all documentation.",
779
+ inputSchema: docsSearchInputSchema
841
780
  };
842
- function formatSearchResults(results, baseUrl) {
781
+ function formatSearchResults(results) {
843
782
  if (results.length === 0) {
844
783
  return "No matching documents found.";
845
784
  }
@@ -849,38 +788,29 @@ function formatSearchResults(results, baseUrl) {
849
788
  const result = results[i];
850
789
  if (!result) continue;
851
790
  lines.push(`${i + 1}. **${result.title}**`);
852
- if (baseUrl) {
853
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${result.route}`;
854
- lines.push(` URL: ${fullUrl}`);
855
- }
856
- lines.push(` Route: ${result.route}`);
791
+ lines.push(` URL: ${result.url}`);
857
792
  if (result.matchingHeadings && result.matchingHeadings.length > 0) {
858
793
  lines.push(` Matching sections: ${result.matchingHeadings.join(", ")}`);
859
794
  }
860
795
  lines.push(` ${result.snippet}`);
861
796
  lines.push("");
862
797
  }
798
+ lines.push("Use docs_fetch with the URL to retrieve the full page content.");
863
799
  return lines.join("\n");
864
800
  }
865
-
866
- // src/mcp/tools/docs-fetch.ts
801
+ var docsFetchInputSchema = {
802
+ url: z.string().url().describe(
803
+ 'The full URL of the page to fetch (e.g., "https://docs.example.com/docs/getting-started")'
804
+ )
805
+ };
867
806
  var docsFetchTool = {
868
807
  name: "docs_fetch",
869
- description: "Fetch the complete content of a documentation page. Use this after searching to get full page content.",
870
- inputSchema: {
871
- type: "object",
872
- properties: {
873
- route: {
874
- type: "string",
875
- description: "The route path of the page (e.g., /docs/getting-started)"
876
- }
877
- },
878
- required: ["route"]
879
- }
808
+ description: "Fetch the complete content of a documentation page. Use this after searching to get the full markdown content of a specific page.",
809
+ inputSchema: docsFetchInputSchema
880
810
  };
881
- function formatPageContent(doc, baseUrl) {
811
+ function formatPageContent(doc) {
882
812
  if (!doc) {
883
- return "Page not found. Please check the route path and try again.";
813
+ return "Page not found. Please check the URL and try again.";
884
814
  }
885
815
  const lines = [];
886
816
  lines.push(`# ${doc.title}`);
@@ -889,12 +819,6 @@ function formatPageContent(doc, baseUrl) {
889
819
  lines.push(`> ${doc.description}`);
890
820
  lines.push("");
891
821
  }
892
- if (baseUrl) {
893
- const fullUrl = `${baseUrl.replace(/\/$/, "")}${doc.route}`;
894
- lines.push(`**URL:** ${fullUrl}`);
895
- }
896
- lines.push(`**Route:** ${doc.route}`);
897
- lines.push("");
898
822
  if (doc.headings.length > 0) {
899
823
  lines.push("## Contents");
900
824
  lines.push("");
@@ -940,17 +864,14 @@ var McpDocsServer = class {
940
864
  this.registerTools();
941
865
  }
942
866
  /**
943
- * Register all MCP tools using the SDK's registerTool API
867
+ * Register all MCP tools using definitions from tool files
944
868
  */
945
869
  registerTools() {
946
870
  this.mcpServer.registerTool(
947
- "docs_search",
871
+ docsSearchTool.name,
948
872
  {
949
- description: "Search the documentation for relevant pages. Returns matching documents with snippets and relevance scores. Use this to find information across all documentation.",
950
- inputSchema: {
951
- query: z.string().min(1).describe("The search query string"),
952
- limit: z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
953
- }
873
+ description: docsSearchTool.description,
874
+ inputSchema: docsSearchTool.inputSchema
954
875
  },
955
876
  async ({ query, limit }) => {
956
877
  await this.initialize();
@@ -963,9 +884,7 @@ var McpDocsServer = class {
963
884
  try {
964
885
  const results = await this.searchProvider.search(query, { limit });
965
886
  return {
966
- content: [
967
- { type: "text", text: formatSearchResults(results, this.config.baseUrl) }
968
- ]
887
+ content: [{ type: "text", text: formatSearchResults(results) }]
969
888
  };
970
889
  } catch (error) {
971
890
  console.error("[MCP] Search error:", error);
@@ -977,14 +896,12 @@ var McpDocsServer = class {
977
896
  }
978
897
  );
979
898
  this.mcpServer.registerTool(
980
- "docs_fetch",
899
+ docsFetchTool.name,
981
900
  {
982
- description: "Fetch the complete content of a documentation page. Use this when you need the full content of a specific page.",
983
- inputSchema: {
984
- route: z.string().min(1).describe('The page route path (e.g., "/docs/getting-started" or "/api/reference")')
985
- }
901
+ description: docsFetchTool.description,
902
+ inputSchema: docsFetchTool.inputSchema
986
903
  },
987
- async ({ route }) => {
904
+ async ({ url }) => {
988
905
  await this.initialize();
989
906
  if (!this.searchProvider || !this.searchProvider.isReady()) {
990
907
  return {
@@ -993,14 +910,14 @@ var McpDocsServer = class {
993
910
  };
994
911
  }
995
912
  try {
996
- const doc = await this.getDocument(route);
913
+ const doc = await this.getDocument(url);
997
914
  return {
998
- content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
915
+ content: [{ type: "text", text: formatPageContent(doc) }]
999
916
  };
1000
917
  } catch (error) {
1001
- console.error("[MCP] Get page error:", error);
918
+ console.error("[MCP] Fetch error:", error);
1002
919
  return {
1003
- content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
920
+ content: [{ type: "text", text: `Error fetching page: ${String(error)}` }],
1004
921
  isError: true
1005
922
  };
1006
923
  }
@@ -1008,24 +925,14 @@ var McpDocsServer = class {
1008
925
  );
1009
926
  }
1010
927
  /**
1011
- * Get a document by route using the search provider
928
+ * Get a document by URL using the search provider
1012
929
  */
1013
- async getDocument(route) {
930
+ async getDocument(url) {
1014
931
  if (!this.searchProvider) {
1015
932
  return null;
1016
933
  }
1017
934
  if (this.searchProvider.getDocument) {
1018
- return this.searchProvider.getDocument(route);
1019
- }
1020
- if (this.searchProvider instanceof FlexSearchProvider) {
1021
- const docs = this.searchProvider.getDocs();
1022
- if (!docs) return null;
1023
- if (docs[route]) {
1024
- return docs[route];
1025
- }
1026
- const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
1027
- const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
1028
- return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
935
+ return this.searchProvider.getDocument(url);
1029
936
  }
1030
937
  return null;
1031
938
  }