docusaurus-plugin-mcp-server 0.11.0 → 0.12.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/README.md +108 -30
- package/dist/adapters-entry.d.ts +9 -3
- package/dist/adapters-entry.js +242 -181
- package/dist/adapters-entry.js.map +1 -1
- package/dist/cli/verify.js +185 -129
- package/dist/cli/verify.js.map +1 -1
- package/dist/{index-BB96x1Rw.d.ts → index-BhwLRGxJ.d.ts} +2 -31
- package/dist/index.d.ts +22 -168
- package/dist/index.js +404 -343
- package/dist/index.js.map +1 -1
- package/package.json +5 -8
package/dist/index.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import
|
|
1
|
+
import FlexSearch from 'flexsearch';
|
|
2
2
|
import fs3 from 'fs-extra';
|
|
3
|
+
import path from 'path';
|
|
3
4
|
import pMap from 'p-map';
|
|
4
5
|
import { unified } from 'unified';
|
|
5
6
|
import rehypeParse from 'rehype-parse';
|
|
@@ -9,16 +10,327 @@ import { toHtml } from 'hast-util-to-html';
|
|
|
9
10
|
import rehypeRemark from 'rehype-remark';
|
|
10
11
|
import remarkStringify from 'remark-stringify';
|
|
11
12
|
import remarkGfm from 'remark-gfm';
|
|
12
|
-
import FlexSearch from 'flexsearch';
|
|
13
13
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
14
14
|
import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
|
|
15
15
|
import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
var __defProp = Object.defineProperty;
|
|
19
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
20
|
+
var __esm = (fn, res) => function __init() {
|
|
21
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
22
|
+
};
|
|
23
|
+
var __export = (target, all) => {
|
|
24
|
+
for (var name in all)
|
|
25
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
26
|
+
};
|
|
27
|
+
function englishStemmer(word) {
|
|
28
|
+
if (word.length <= 3) return word;
|
|
29
|
+
return word.replace(/ing$/, "").replace(/tion$/, "t").replace(/sion$/, "s").replace(/([^aeiou])ed$/, "$1").replace(/([^aeiou])es$/, "$1").replace(/ly$/, "").replace(/ment$/, "").replace(/ness$/, "").replace(/ies$/, "y").replace(/([^s])s$/, "$1");
|
|
30
|
+
}
|
|
31
|
+
function createSearchIndex() {
|
|
32
|
+
return new FlexSearch.Document({
|
|
33
|
+
// Use 'forward' tokenization to avoid OOM with large doc sets
|
|
34
|
+
// See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11
|
|
35
|
+
tokenize: "forward",
|
|
36
|
+
// Enable caching for faster repeated queries
|
|
37
|
+
cache: 100,
|
|
38
|
+
// Higher resolution = more granular ranking (1-9)
|
|
39
|
+
resolution: 9,
|
|
40
|
+
// Enable context for phrase/proximity matching
|
|
41
|
+
context: {
|
|
42
|
+
resolution: 2,
|
|
43
|
+
depth: 2,
|
|
44
|
+
bidirectional: true
|
|
45
|
+
},
|
|
46
|
+
// Apply stemming to normalize word forms
|
|
47
|
+
encode: (str) => {
|
|
48
|
+
const words = str.toLowerCase().split(/[\s\-_.,;:!?'"()[\]{}]+/);
|
|
49
|
+
return words.filter(Boolean).map(englishStemmer);
|
|
50
|
+
},
|
|
51
|
+
// Document schema
|
|
52
|
+
document: {
|
|
53
|
+
id: "id",
|
|
54
|
+
// Index these fields for searching
|
|
55
|
+
index: ["title", "content", "headings", "description"],
|
|
56
|
+
// Store these fields in results (for enriched queries)
|
|
57
|
+
store: ["title", "description"]
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
function addDocumentToIndex(index, doc, baseUrl) {
|
|
62
|
+
const id = baseUrl ? `${baseUrl.replace(/\/$/, "")}${doc.route}` : doc.route;
|
|
63
|
+
const indexable = {
|
|
64
|
+
id,
|
|
65
|
+
title: doc.title,
|
|
66
|
+
content: doc.markdown,
|
|
67
|
+
headings: doc.headings.map((h) => h.text).join(" "),
|
|
68
|
+
description: doc.description
|
|
69
|
+
};
|
|
70
|
+
index.add(indexable);
|
|
71
|
+
}
|
|
72
|
+
function buildSearchIndex(docs, baseUrl) {
|
|
73
|
+
const index = createSearchIndex();
|
|
74
|
+
for (const doc of docs) {
|
|
75
|
+
addDocumentToIndex(index, doc, baseUrl);
|
|
76
|
+
}
|
|
77
|
+
return index;
|
|
78
|
+
}
|
|
79
|
+
function querySearchIndex(index, docs, query, options = {}) {
|
|
80
|
+
const { limit = 16 } = options;
|
|
81
|
+
const rawResults = index.search(query, {
|
|
82
|
+
limit: limit * 3,
|
|
83
|
+
// Get extra results for better ranking after weighting
|
|
84
|
+
enrich: true
|
|
85
|
+
});
|
|
86
|
+
const docScores = /* @__PURE__ */ new Map();
|
|
87
|
+
for (const fieldResult of rawResults) {
|
|
88
|
+
const field = fieldResult.field;
|
|
89
|
+
const fieldWeight = FIELD_WEIGHTS[field] ?? 1;
|
|
90
|
+
const results2 = fieldResult.result;
|
|
91
|
+
for (let i = 0; i < results2.length; i++) {
|
|
92
|
+
const item = results2[i];
|
|
93
|
+
if (!item) continue;
|
|
94
|
+
const docId = typeof item === "string" ? item : item.id;
|
|
95
|
+
const positionScore = (results2.length - i) / results2.length;
|
|
96
|
+
const weightedScore = positionScore * fieldWeight;
|
|
97
|
+
const existingScore = docScores.get(docId) ?? 0;
|
|
98
|
+
docScores.set(docId, existingScore + weightedScore);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const results = [];
|
|
102
|
+
for (const [docId, score] of docScores) {
|
|
103
|
+
const doc = docs[docId];
|
|
104
|
+
if (!doc) continue;
|
|
105
|
+
results.push({
|
|
106
|
+
url: docId,
|
|
107
|
+
// docId is the full URL when indexed with baseUrl
|
|
108
|
+
route: doc.route,
|
|
109
|
+
title: doc.title,
|
|
110
|
+
score,
|
|
111
|
+
snippet: generateSnippet(doc.markdown, query),
|
|
112
|
+
matchingHeadings: findMatchingHeadings(doc, query)
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
results.sort((a, b) => b.score - a.score);
|
|
116
|
+
return results.slice(0, limit);
|
|
117
|
+
}
|
|
118
|
+
function generateSnippet(markdown, query) {
|
|
119
|
+
const maxLength = 200;
|
|
120
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
121
|
+
if (queryTerms.length === 0) {
|
|
122
|
+
return markdown.slice(0, maxLength) + (markdown.length > maxLength ? "..." : "");
|
|
123
|
+
}
|
|
124
|
+
const lowerMarkdown = markdown.toLowerCase();
|
|
125
|
+
let bestIndex = -1;
|
|
126
|
+
let bestTerm = "";
|
|
127
|
+
const allTerms = [...queryTerms, ...queryTerms.map(englishStemmer)];
|
|
128
|
+
for (const term of allTerms) {
|
|
129
|
+
const index = lowerMarkdown.indexOf(term);
|
|
130
|
+
if (index !== -1 && (bestIndex === -1 || index < bestIndex)) {
|
|
131
|
+
bestIndex = index;
|
|
132
|
+
bestTerm = term;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
if (bestIndex === -1) {
|
|
136
|
+
return markdown.slice(0, maxLength) + (markdown.length > maxLength ? "..." : "");
|
|
137
|
+
}
|
|
138
|
+
const snippetStart = Math.max(0, bestIndex - 50);
|
|
139
|
+
const snippetEnd = Math.min(markdown.length, bestIndex + bestTerm.length + 150);
|
|
140
|
+
let snippet = markdown.slice(snippetStart, snippetEnd);
|
|
141
|
+
snippet = snippet.replace(/^#{1,6}\s+/gm, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "").replace(/```[a-z]*\n?/g, "").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
|
|
142
|
+
const prefix = snippetStart > 0 ? "..." : "";
|
|
143
|
+
const suffix = snippetEnd < markdown.length ? "..." : "";
|
|
144
|
+
return prefix + snippet + suffix;
|
|
145
|
+
}
|
|
146
|
+
function findMatchingHeadings(doc, query) {
|
|
147
|
+
const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
148
|
+
const allTerms = [...queryTerms, ...queryTerms.map(englishStemmer)];
|
|
149
|
+
const matching = [];
|
|
150
|
+
for (const heading of doc.headings) {
|
|
151
|
+
const headingLower = heading.text.toLowerCase();
|
|
152
|
+
const headingStemmed = headingLower.split(/\s+/).map(englishStemmer).join(" ");
|
|
153
|
+
if (allTerms.some(
|
|
154
|
+
(term) => headingLower.includes(term) || headingStemmed.includes(englishStemmer(term))
|
|
155
|
+
)) {
|
|
156
|
+
matching.push(heading.text);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return matching.slice(0, 3);
|
|
160
|
+
}
|
|
161
|
+
async function exportSearchIndex(index) {
|
|
162
|
+
const exportData = {};
|
|
163
|
+
await index.export((key, data) => {
|
|
164
|
+
exportData[key] = data;
|
|
165
|
+
});
|
|
166
|
+
return exportData;
|
|
167
|
+
}
|
|
168
|
+
async function importSearchIndex(data) {
|
|
169
|
+
const index = createSearchIndex();
|
|
170
|
+
for (const [key, value] of Object.entries(data)) {
|
|
171
|
+
await index.import(
|
|
172
|
+
key,
|
|
173
|
+
value
|
|
174
|
+
);
|
|
175
|
+
}
|
|
176
|
+
return index;
|
|
177
|
+
}
|
|
178
|
+
var FIELD_WEIGHTS;
|
|
179
|
+
var init_flexsearch_core = __esm({
|
|
180
|
+
"src/search/flexsearch-core.ts"() {
|
|
181
|
+
FIELD_WEIGHTS = {
|
|
182
|
+
title: 3,
|
|
183
|
+
headings: 2,
|
|
184
|
+
description: 1.5,
|
|
185
|
+
content: 1
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// src/providers/indexers/flexsearch-indexer.ts
|
|
191
|
+
var flexsearch_indexer_exports = {};
|
|
192
|
+
__export(flexsearch_indexer_exports, {
|
|
193
|
+
FlexSearchIndexer: () => FlexSearchIndexer
|
|
194
|
+
});
|
|
195
|
+
var FlexSearchIndexer;
|
|
196
|
+
var init_flexsearch_indexer = __esm({
|
|
197
|
+
"src/providers/indexers/flexsearch-indexer.ts"() {
|
|
198
|
+
init_flexsearch_core();
|
|
199
|
+
FlexSearchIndexer = class {
|
|
200
|
+
name = "flexsearch";
|
|
201
|
+
baseUrl = "";
|
|
202
|
+
docsIndex = {};
|
|
203
|
+
exportedIndex = null;
|
|
204
|
+
docCount = 0;
|
|
205
|
+
/**
|
|
206
|
+
* FlexSearch indexer always runs by default.
|
|
207
|
+
* It respects the indexers configuration - if not included, it won't run.
|
|
208
|
+
*/
|
|
209
|
+
shouldRun() {
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
async initialize(context) {
|
|
213
|
+
this.baseUrl = context.baseUrl.replace(/\/$/, "");
|
|
214
|
+
this.docsIndex = {};
|
|
215
|
+
this.exportedIndex = null;
|
|
216
|
+
this.docCount = 0;
|
|
217
|
+
}
|
|
218
|
+
async indexDocuments(docs) {
|
|
219
|
+
this.docCount = docs.length;
|
|
220
|
+
for (const doc of docs) {
|
|
221
|
+
const fullUrl = `${this.baseUrl}${doc.route}`;
|
|
222
|
+
this.docsIndex[fullUrl] = doc;
|
|
223
|
+
}
|
|
224
|
+
console.log("[FlexSearch] Building search index...");
|
|
225
|
+
const searchIndex = buildSearchIndex(docs, this.baseUrl);
|
|
226
|
+
this.exportedIndex = await exportSearchIndex(searchIndex);
|
|
227
|
+
console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
|
|
228
|
+
}
|
|
229
|
+
async finalize() {
|
|
230
|
+
const artifacts = /* @__PURE__ */ new Map();
|
|
231
|
+
artifacts.set("docs.json", this.docsIndex);
|
|
232
|
+
artifacts.set("search-index.json", this.exportedIndex);
|
|
233
|
+
return artifacts;
|
|
234
|
+
}
|
|
235
|
+
async getManifestData() {
|
|
236
|
+
return {
|
|
237
|
+
searchEngine: "flexsearch"
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// src/providers/search/flexsearch-provider.ts
|
|
245
|
+
var flexsearch_provider_exports = {};
|
|
246
|
+
__export(flexsearch_provider_exports, {
|
|
247
|
+
FlexSearchProvider: () => FlexSearchProvider
|
|
248
|
+
});
|
|
249
|
+
var FlexSearchProvider;
|
|
250
|
+
var init_flexsearch_provider = __esm({
|
|
251
|
+
"src/providers/search/flexsearch-provider.ts"() {
|
|
252
|
+
init_flexsearch_core();
|
|
253
|
+
FlexSearchProvider = class {
|
|
254
|
+
name = "flexsearch";
|
|
255
|
+
docs = null;
|
|
256
|
+
searchIndex = null;
|
|
257
|
+
ready = false;
|
|
258
|
+
async initialize(_context, initData) {
|
|
259
|
+
if (!initData) {
|
|
260
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
261
|
+
}
|
|
262
|
+
if (initData.docs && initData.indexData) {
|
|
263
|
+
this.docs = initData.docs;
|
|
264
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
265
|
+
this.ready = true;
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
if (initData.docsPath && initData.indexPath) {
|
|
269
|
+
if (await fs3.pathExists(initData.docsPath)) {
|
|
270
|
+
this.docs = await fs3.readJson(initData.docsPath);
|
|
271
|
+
} else {
|
|
272
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
273
|
+
}
|
|
274
|
+
if (await fs3.pathExists(initData.indexPath)) {
|
|
275
|
+
const indexData = await fs3.readJson(initData.indexPath);
|
|
276
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
277
|
+
} else {
|
|
278
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
279
|
+
}
|
|
280
|
+
this.ready = true;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
throw new Error(
|
|
284
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
285
|
+
);
|
|
286
|
+
}
|
|
287
|
+
isReady() {
|
|
288
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
289
|
+
}
|
|
290
|
+
async search(query, options) {
|
|
291
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
292
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
293
|
+
}
|
|
294
|
+
const limit = options?.limit ?? 16;
|
|
295
|
+
return querySearchIndex(this.searchIndex, this.docs, query, { limit });
|
|
296
|
+
}
|
|
297
|
+
async getDocument(url) {
|
|
298
|
+
if (!this.docs) {
|
|
299
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
300
|
+
}
|
|
301
|
+
return this.docs[url] ?? null;
|
|
302
|
+
}
|
|
303
|
+
async healthCheck() {
|
|
304
|
+
if (!this.isReady()) {
|
|
305
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
306
|
+
}
|
|
307
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
308
|
+
return {
|
|
309
|
+
healthy: true,
|
|
310
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
getDocCount() {
|
|
314
|
+
return this.docs ? Object.keys(this.docs).length : 0;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
318
|
+
*/
|
|
319
|
+
getDocs() {
|
|
320
|
+
return this.docs;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
324
|
+
*/
|
|
325
|
+
getSearchIndex() {
|
|
326
|
+
return this.searchIndex;
|
|
327
|
+
}
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
});
|
|
19
331
|
|
|
20
332
|
// src/types/index.ts
|
|
21
|
-
var
|
|
333
|
+
var DEFAULT_PLUGIN_OPTIONS = {
|
|
22
334
|
outputDir: "mcp",
|
|
23
335
|
contentSelectors: ["article", "main", ".main-wrapper", '[role="main"]'],
|
|
24
336
|
excludeSelectors: [
|
|
@@ -280,292 +592,12 @@ function extractHeadingsFromMarkdown(markdown) {
|
|
|
280
592
|
function generateHeadingId(text) {
|
|
281
593
|
return text.toLowerCase().replace(/[^\w\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
|
|
282
594
|
}
|
|
283
|
-
function extractSection(markdown, headingId, headings) {
|
|
284
|
-
const heading = headings.find((h) => h.id === headingId);
|
|
285
|
-
if (!heading) {
|
|
286
|
-
return null;
|
|
287
|
-
}
|
|
288
|
-
return markdown.slice(heading.startOffset, heading.endOffset).trim();
|
|
289
|
-
}
|
|
290
|
-
var FIELD_WEIGHTS = {
|
|
291
|
-
title: 3,
|
|
292
|
-
headings: 2,
|
|
293
|
-
description: 1.5,
|
|
294
|
-
content: 1
|
|
295
|
-
};
|
|
296
|
-
function englishStemmer(word) {
|
|
297
|
-
if (word.length <= 3) return word;
|
|
298
|
-
return word.replace(/ing$/, "").replace(/tion$/, "t").replace(/sion$/, "s").replace(/([^aeiou])ed$/, "$1").replace(/([^aeiou])es$/, "$1").replace(/ly$/, "").replace(/ment$/, "").replace(/ness$/, "").replace(/ies$/, "y").replace(/([^s])s$/, "$1");
|
|
299
|
-
}
|
|
300
|
-
function createSearchIndex() {
|
|
301
|
-
return new FlexSearch.Document({
|
|
302
|
-
// Use 'forward' tokenization to avoid OOM with large doc sets
|
|
303
|
-
// See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11
|
|
304
|
-
tokenize: "forward",
|
|
305
|
-
// Enable caching for faster repeated queries
|
|
306
|
-
cache: 100,
|
|
307
|
-
// Higher resolution = more granular ranking (1-9)
|
|
308
|
-
resolution: 9,
|
|
309
|
-
// Enable context for phrase/proximity matching
|
|
310
|
-
context: {
|
|
311
|
-
resolution: 2,
|
|
312
|
-
depth: 2,
|
|
313
|
-
bidirectional: true
|
|
314
|
-
},
|
|
315
|
-
// Apply stemming to normalize word forms
|
|
316
|
-
encode: (str) => {
|
|
317
|
-
const words = str.toLowerCase().split(/[\s\-_.,;:!?'"()[\]{}]+/);
|
|
318
|
-
return words.filter(Boolean).map(englishStemmer);
|
|
319
|
-
},
|
|
320
|
-
// Document schema
|
|
321
|
-
document: {
|
|
322
|
-
id: "id",
|
|
323
|
-
// Index these fields for searching
|
|
324
|
-
index: ["title", "content", "headings", "description"],
|
|
325
|
-
// Store these fields in results (for enriched queries)
|
|
326
|
-
store: ["title", "description"]
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
}
|
|
330
|
-
function addDocumentToIndex(index, doc, baseUrl) {
|
|
331
|
-
const id = baseUrl ? `${baseUrl.replace(/\/$/, "")}${doc.route}` : doc.route;
|
|
332
|
-
const indexable = {
|
|
333
|
-
id,
|
|
334
|
-
title: doc.title,
|
|
335
|
-
content: doc.markdown,
|
|
336
|
-
headings: doc.headings.map((h) => h.text).join(" "),
|
|
337
|
-
description: doc.description
|
|
338
|
-
};
|
|
339
|
-
index.add(indexable);
|
|
340
|
-
}
|
|
341
|
-
function buildSearchIndex(docs, baseUrl) {
|
|
342
|
-
const index = createSearchIndex();
|
|
343
|
-
for (const doc of docs) {
|
|
344
|
-
addDocumentToIndex(index, doc, baseUrl);
|
|
345
|
-
}
|
|
346
|
-
return index;
|
|
347
|
-
}
|
|
348
|
-
function searchIndex(index, docs, query, options = {}) {
|
|
349
|
-
const { limit = 16 } = options;
|
|
350
|
-
const rawResults = index.search(query, {
|
|
351
|
-
limit: limit * 3,
|
|
352
|
-
// Get extra results for better ranking after weighting
|
|
353
|
-
enrich: true
|
|
354
|
-
});
|
|
355
|
-
const docScores = /* @__PURE__ */ new Map();
|
|
356
|
-
for (const fieldResult of rawResults) {
|
|
357
|
-
const field = fieldResult.field;
|
|
358
|
-
const fieldWeight = FIELD_WEIGHTS[field] ?? 1;
|
|
359
|
-
const results2 = fieldResult.result;
|
|
360
|
-
for (let i = 0; i < results2.length; i++) {
|
|
361
|
-
const item = results2[i];
|
|
362
|
-
if (!item) continue;
|
|
363
|
-
const docId = typeof item === "string" ? item : item.id;
|
|
364
|
-
const positionScore = (results2.length - i) / results2.length;
|
|
365
|
-
const weightedScore = positionScore * fieldWeight;
|
|
366
|
-
const existingScore = docScores.get(docId) ?? 0;
|
|
367
|
-
docScores.set(docId, existingScore + weightedScore);
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
const results = [];
|
|
371
|
-
for (const [docId, score] of docScores) {
|
|
372
|
-
const doc = docs[docId];
|
|
373
|
-
if (!doc) continue;
|
|
374
|
-
results.push({
|
|
375
|
-
url: docId,
|
|
376
|
-
// docId is the full URL when indexed with baseUrl
|
|
377
|
-
route: doc.route,
|
|
378
|
-
title: doc.title,
|
|
379
|
-
score,
|
|
380
|
-
snippet: generateSnippet(doc.markdown, query),
|
|
381
|
-
matchingHeadings: findMatchingHeadings(doc, query)
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
results.sort((a, b) => b.score - a.score);
|
|
385
|
-
return results.slice(0, limit);
|
|
386
|
-
}
|
|
387
|
-
function generateSnippet(markdown, query) {
|
|
388
|
-
const maxLength = 200;
|
|
389
|
-
const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
390
|
-
if (queryTerms.length === 0) {
|
|
391
|
-
return markdown.slice(0, maxLength) + (markdown.length > maxLength ? "..." : "");
|
|
392
|
-
}
|
|
393
|
-
const lowerMarkdown = markdown.toLowerCase();
|
|
394
|
-
let bestIndex = -1;
|
|
395
|
-
let bestTerm = "";
|
|
396
|
-
const allTerms = [...queryTerms, ...queryTerms.map(englishStemmer)];
|
|
397
|
-
for (const term of allTerms) {
|
|
398
|
-
const index = lowerMarkdown.indexOf(term);
|
|
399
|
-
if (index !== -1 && (bestIndex === -1 || index < bestIndex)) {
|
|
400
|
-
bestIndex = index;
|
|
401
|
-
bestTerm = term;
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
if (bestIndex === -1) {
|
|
405
|
-
return markdown.slice(0, maxLength) + (markdown.length > maxLength ? "..." : "");
|
|
406
|
-
}
|
|
407
|
-
const snippetStart = Math.max(0, bestIndex - 50);
|
|
408
|
-
const snippetEnd = Math.min(markdown.length, bestIndex + bestTerm.length + 150);
|
|
409
|
-
let snippet = markdown.slice(snippetStart, snippetEnd);
|
|
410
|
-
snippet = snippet.replace(/^#{1,6}\s+/gm, "").replace(/\[([^\]]+)\]\([^)]+\)/g, "$1").replace(/!\[([^\]]*)\]\([^)]+\)/g, "").replace(/```[a-z]*\n?/g, "").replace(/`([^`]+)`/g, "$1").replace(/\s+/g, " ").trim();
|
|
411
|
-
const prefix = snippetStart > 0 ? "..." : "";
|
|
412
|
-
const suffix = snippetEnd < markdown.length ? "..." : "";
|
|
413
|
-
return prefix + snippet + suffix;
|
|
414
|
-
}
|
|
415
|
-
function findMatchingHeadings(doc, query) {
|
|
416
|
-
const queryTerms = query.toLowerCase().split(/\s+/).filter(Boolean);
|
|
417
|
-
const allTerms = [...queryTerms, ...queryTerms.map(englishStemmer)];
|
|
418
|
-
const matching = [];
|
|
419
|
-
for (const heading of doc.headings) {
|
|
420
|
-
const headingLower = heading.text.toLowerCase();
|
|
421
|
-
const headingStemmed = headingLower.split(/\s+/).map(englishStemmer).join(" ");
|
|
422
|
-
if (allTerms.some(
|
|
423
|
-
(term) => headingLower.includes(term) || headingStemmed.includes(englishStemmer(term))
|
|
424
|
-
)) {
|
|
425
|
-
matching.push(heading.text);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
return matching.slice(0, 3);
|
|
429
|
-
}
|
|
430
|
-
async function exportSearchIndex(index) {
|
|
431
|
-
const exportData = {};
|
|
432
|
-
await index.export((key, data) => {
|
|
433
|
-
exportData[key] = data;
|
|
434
|
-
});
|
|
435
|
-
return exportData;
|
|
436
|
-
}
|
|
437
|
-
async function importSearchIndex(data) {
|
|
438
|
-
const index = createSearchIndex();
|
|
439
|
-
for (const [key, value] of Object.entries(data)) {
|
|
440
|
-
await index.import(
|
|
441
|
-
key,
|
|
442
|
-
value
|
|
443
|
-
);
|
|
444
|
-
}
|
|
445
|
-
return index;
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
// src/providers/indexers/flexsearch-indexer.ts
|
|
449
|
-
var FlexSearchIndexer = class {
|
|
450
|
-
name = "flexsearch";
|
|
451
|
-
baseUrl = "";
|
|
452
|
-
docsIndex = {};
|
|
453
|
-
exportedIndex = null;
|
|
454
|
-
docCount = 0;
|
|
455
|
-
/**
|
|
456
|
-
* FlexSearch indexer always runs by default.
|
|
457
|
-
* It respects the indexers configuration - if not included, it won't run.
|
|
458
|
-
*/
|
|
459
|
-
shouldRun() {
|
|
460
|
-
return true;
|
|
461
|
-
}
|
|
462
|
-
async initialize(context) {
|
|
463
|
-
this.baseUrl = context.baseUrl.replace(/\/$/, "");
|
|
464
|
-
this.docsIndex = {};
|
|
465
|
-
this.exportedIndex = null;
|
|
466
|
-
this.docCount = 0;
|
|
467
|
-
}
|
|
468
|
-
async indexDocuments(docs) {
|
|
469
|
-
this.docCount = docs.length;
|
|
470
|
-
for (const doc of docs) {
|
|
471
|
-
const fullUrl = `${this.baseUrl}${doc.route}`;
|
|
472
|
-
this.docsIndex[fullUrl] = doc;
|
|
473
|
-
}
|
|
474
|
-
console.log("[FlexSearch] Building search index...");
|
|
475
|
-
const searchIndex2 = buildSearchIndex(docs, this.baseUrl);
|
|
476
|
-
this.exportedIndex = await exportSearchIndex(searchIndex2);
|
|
477
|
-
console.log(`[FlexSearch] Indexed ${this.docCount} documents`);
|
|
478
|
-
}
|
|
479
|
-
async finalize() {
|
|
480
|
-
const artifacts = /* @__PURE__ */ new Map();
|
|
481
|
-
artifacts.set("docs.json", this.docsIndex);
|
|
482
|
-
artifacts.set("search-index.json", this.exportedIndex);
|
|
483
|
-
return artifacts;
|
|
484
|
-
}
|
|
485
|
-
async getManifestData() {
|
|
486
|
-
return {
|
|
487
|
-
searchEngine: "flexsearch"
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
var FlexSearchProvider = class {
|
|
492
|
-
name = "flexsearch";
|
|
493
|
-
docs = null;
|
|
494
|
-
searchIndex = null;
|
|
495
|
-
ready = false;
|
|
496
|
-
async initialize(_context, initData) {
|
|
497
|
-
if (!initData) {
|
|
498
|
-
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
499
|
-
}
|
|
500
|
-
if (initData.docs && initData.indexData) {
|
|
501
|
-
this.docs = initData.docs;
|
|
502
|
-
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
503
|
-
this.ready = true;
|
|
504
|
-
return;
|
|
505
|
-
}
|
|
506
|
-
if (initData.docsPath && initData.indexPath) {
|
|
507
|
-
if (await fs3.pathExists(initData.docsPath)) {
|
|
508
|
-
this.docs = await fs3.readJson(initData.docsPath);
|
|
509
|
-
} else {
|
|
510
|
-
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
511
|
-
}
|
|
512
|
-
if (await fs3.pathExists(initData.indexPath)) {
|
|
513
|
-
const indexData = await fs3.readJson(initData.indexPath);
|
|
514
|
-
this.searchIndex = await importSearchIndex(indexData);
|
|
515
|
-
} else {
|
|
516
|
-
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
517
|
-
}
|
|
518
|
-
this.ready = true;
|
|
519
|
-
return;
|
|
520
|
-
}
|
|
521
|
-
throw new Error(
|
|
522
|
-
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
523
|
-
);
|
|
524
|
-
}
|
|
525
|
-
isReady() {
|
|
526
|
-
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
527
|
-
}
|
|
528
|
-
async search(query, options) {
|
|
529
|
-
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
530
|
-
throw new Error("[FlexSearch] Provider not initialized");
|
|
531
|
-
}
|
|
532
|
-
const limit = options?.limit ?? 16;
|
|
533
|
-
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
534
|
-
}
|
|
535
|
-
async getDocument(url) {
|
|
536
|
-
if (!this.docs) {
|
|
537
|
-
throw new Error("[FlexSearch] Provider not initialized");
|
|
538
|
-
}
|
|
539
|
-
return this.docs[url] ?? null;
|
|
540
|
-
}
|
|
541
|
-
async healthCheck() {
|
|
542
|
-
if (!this.isReady()) {
|
|
543
|
-
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
544
|
-
}
|
|
545
|
-
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
546
|
-
return {
|
|
547
|
-
healthy: true,
|
|
548
|
-
message: `FlexSearch provider ready with ${docCount} documents`
|
|
549
|
-
};
|
|
550
|
-
}
|
|
551
|
-
/**
|
|
552
|
-
* Get all loaded documents (for compatibility with existing server code)
|
|
553
|
-
*/
|
|
554
|
-
getDocs() {
|
|
555
|
-
return this.docs;
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Get the FlexSearch index (for compatibility with existing server code)
|
|
559
|
-
*/
|
|
560
|
-
getSearchIndex() {
|
|
561
|
-
return this.searchIndex;
|
|
562
|
-
}
|
|
563
|
-
};
|
|
564
595
|
|
|
565
596
|
// src/providers/loader.ts
|
|
566
597
|
async function loadIndexer(specifier) {
|
|
567
598
|
if (specifier === "flexsearch") {
|
|
568
|
-
|
|
599
|
+
const { FlexSearchIndexer: FlexSearchIndexer2 } = await Promise.resolve().then(() => (init_flexsearch_indexer(), flexsearch_indexer_exports));
|
|
600
|
+
return new FlexSearchIndexer2();
|
|
569
601
|
}
|
|
570
602
|
try {
|
|
571
603
|
const module = await import(specifier);
|
|
@@ -587,14 +619,17 @@ async function loadIndexer(specifier) {
|
|
|
587
619
|
);
|
|
588
620
|
} catch (error) {
|
|
589
621
|
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
590
|
-
throw new Error(`Indexer module not found: "${specifier}". Check the path or package name
|
|
622
|
+
throw new Error(`Indexer module not found: "${specifier}". Check the path or package name.`, {
|
|
623
|
+
cause: error
|
|
624
|
+
});
|
|
591
625
|
}
|
|
592
626
|
throw error;
|
|
593
627
|
}
|
|
594
628
|
}
|
|
595
629
|
async function loadSearchProvider(specifier) {
|
|
596
630
|
if (specifier === "flexsearch") {
|
|
597
|
-
|
|
631
|
+
const { FlexSearchProvider: FlexSearchProvider2 } = await Promise.resolve().then(() => (init_flexsearch_provider(), flexsearch_provider_exports));
|
|
632
|
+
return new FlexSearchProvider2();
|
|
598
633
|
}
|
|
599
634
|
try {
|
|
600
635
|
const module = await import(specifier);
|
|
@@ -617,7 +652,8 @@ async function loadSearchProvider(specifier) {
|
|
|
617
652
|
} catch (error) {
|
|
618
653
|
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
619
654
|
throw new Error(
|
|
620
|
-
`Search provider module not found: "${specifier}". Check the path or package name
|
|
655
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`,
|
|
656
|
+
{ cause: error }
|
|
621
657
|
);
|
|
622
658
|
}
|
|
623
659
|
throw error;
|
|
@@ -641,10 +677,10 @@ function isSearchProvider(obj) {
|
|
|
641
677
|
// src/plugin/docusaurus-plugin.ts
|
|
642
678
|
function resolveOptions(options) {
|
|
643
679
|
return {
|
|
644
|
-
...
|
|
680
|
+
...DEFAULT_PLUGIN_OPTIONS,
|
|
645
681
|
...options,
|
|
646
682
|
server: {
|
|
647
|
-
...
|
|
683
|
+
...DEFAULT_PLUGIN_OPTIONS.server,
|
|
648
684
|
...options.server
|
|
649
685
|
}
|
|
650
686
|
};
|
|
@@ -769,6 +805,9 @@ function mcpServerPlugin(context, options) {
|
|
|
769
805
|
}
|
|
770
806
|
};
|
|
771
807
|
}
|
|
808
|
+
|
|
809
|
+
// src/mcp/tools/docs-search.ts
|
|
810
|
+
init_flexsearch_core();
|
|
772
811
|
var docsSearchInputSchema = {
|
|
773
812
|
query: z.string().min(1).describe("The search query string"),
|
|
774
813
|
limit: z.number().int().min(1).max(20).optional().default(16).describe("Maximum number of results to return (1-20, default: 16)")
|
|
@@ -846,14 +885,22 @@ function isDataConfig(config) {
|
|
|
846
885
|
var McpDocsServer = class {
|
|
847
886
|
config;
|
|
848
887
|
searchProvider = null;
|
|
849
|
-
mcpServer;
|
|
850
888
|
initialized = false;
|
|
889
|
+
initPromise = null;
|
|
890
|
+
initError = null;
|
|
851
891
|
constructor(config) {
|
|
852
892
|
this.config = config;
|
|
853
|
-
|
|
893
|
+
}
|
|
894
|
+
/**
|
|
895
|
+
* Create a fresh McpServer instance with tools registered.
|
|
896
|
+
* Each request gets its own server to avoid concurrency issues
|
|
897
|
+
* with the SDK's transport reassignment.
|
|
898
|
+
*/
|
|
899
|
+
createMcpServer() {
|
|
900
|
+
const server = new McpServer(
|
|
854
901
|
{
|
|
855
|
-
name: config.name,
|
|
856
|
-
version: config.version ?? "1.0.0"
|
|
902
|
+
name: this.config.name,
|
|
903
|
+
version: this.config.version ?? "1.0.0"
|
|
857
904
|
},
|
|
858
905
|
{
|
|
859
906
|
capabilities: {
|
|
@@ -861,20 +908,20 @@ var McpDocsServer = class {
|
|
|
861
908
|
}
|
|
862
909
|
}
|
|
863
910
|
);
|
|
864
|
-
this.registerTools();
|
|
911
|
+
this.registerTools(server);
|
|
912
|
+
return server;
|
|
865
913
|
}
|
|
866
914
|
/**
|
|
867
915
|
* Register all MCP tools using definitions from tool files
|
|
868
916
|
*/
|
|
869
|
-
registerTools() {
|
|
870
|
-
|
|
917
|
+
registerTools(server) {
|
|
918
|
+
server.registerTool(
|
|
871
919
|
docsSearchTool.name,
|
|
872
920
|
{
|
|
873
921
|
description: docsSearchTool.description,
|
|
874
922
|
inputSchema: docsSearchTool.inputSchema
|
|
875
923
|
},
|
|
876
924
|
async ({ query, limit }) => {
|
|
877
|
-
await this.initialize();
|
|
878
925
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
879
926
|
return {
|
|
880
927
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -889,20 +936,24 @@ var McpDocsServer = class {
|
|
|
889
936
|
} catch (error) {
|
|
890
937
|
console.error("[MCP] Search error:", error);
|
|
891
938
|
return {
|
|
892
|
-
content: [
|
|
939
|
+
content: [
|
|
940
|
+
{
|
|
941
|
+
type: "text",
|
|
942
|
+
text: "An error occurred while searching. Please try again."
|
|
943
|
+
}
|
|
944
|
+
],
|
|
893
945
|
isError: true
|
|
894
946
|
};
|
|
895
947
|
}
|
|
896
948
|
}
|
|
897
949
|
);
|
|
898
|
-
|
|
950
|
+
server.registerTool(
|
|
899
951
|
docsFetchTool.name,
|
|
900
952
|
{
|
|
901
953
|
description: docsFetchTool.description,
|
|
902
954
|
inputSchema: docsFetchTool.inputSchema
|
|
903
955
|
},
|
|
904
956
|
async ({ url }) => {
|
|
905
|
-
await this.initialize();
|
|
906
957
|
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
907
958
|
return {
|
|
908
959
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
@@ -917,7 +968,12 @@ var McpDocsServer = class {
|
|
|
917
968
|
} catch (error) {
|
|
918
969
|
console.error("[MCP] Fetch error:", error);
|
|
919
970
|
return {
|
|
920
|
-
content: [
|
|
971
|
+
content: [
|
|
972
|
+
{
|
|
973
|
+
type: "text",
|
|
974
|
+
text: "An error occurred while fetching the page. Please try again."
|
|
975
|
+
}
|
|
976
|
+
],
|
|
921
977
|
isError: true
|
|
922
978
|
};
|
|
923
979
|
}
|
|
@@ -941,43 +997,54 @@ var McpDocsServer = class {
|
|
|
941
997
|
*
|
|
942
998
|
* For file-based config: reads from disk
|
|
943
999
|
* For data config: uses pre-loaded data directly
|
|
1000
|
+
*
|
|
1001
|
+
* Uses promise-based locking to prevent concurrent initialization.
|
|
1002
|
+
* Caches initialization errors so repeated calls fail fast.
|
|
944
1003
|
*/
|
|
945
1004
|
async initialize() {
|
|
1005
|
+
if (this.initError) {
|
|
1006
|
+
throw this.initError;
|
|
1007
|
+
}
|
|
946
1008
|
if (this.initialized) {
|
|
947
1009
|
return;
|
|
948
1010
|
}
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
1011
|
+
if (!this.initPromise) {
|
|
1012
|
+
this.initPromise = this._doInitialize().catch((error) => {
|
|
1013
|
+
this.initError = error instanceof Error ? error : new Error(String(error));
|
|
1014
|
+
this.initPromise = null;
|
|
1015
|
+
throw this.initError;
|
|
1016
|
+
});
|
|
1017
|
+
}
|
|
1018
|
+
return this.initPromise;
|
|
1019
|
+
}
|
|
1020
|
+
async _doInitialize() {
|
|
1021
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
1022
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
1023
|
+
const providerContext = {
|
|
1024
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
1025
|
+
serverName: this.config.name,
|
|
1026
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
1027
|
+
outputDir: ""
|
|
1028
|
+
// Not relevant for runtime
|
|
1029
|
+
};
|
|
1030
|
+
const initData = {};
|
|
1031
|
+
if (isDataConfig(this.config)) {
|
|
1032
|
+
initData.docs = this.config.docs;
|
|
1033
|
+
initData.indexData = this.config.searchIndexData;
|
|
1034
|
+
} else if (isFileConfig(this.config)) {
|
|
1035
|
+
initData.docsPath = this.config.docsPath;
|
|
1036
|
+
initData.indexPath = this.config.indexPath;
|
|
1037
|
+
} else {
|
|
1038
|
+
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
974
1039
|
}
|
|
1040
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
1041
|
+
this.initialized = true;
|
|
975
1042
|
}
|
|
976
1043
|
/**
|
|
977
1044
|
* Handle an HTTP request using the MCP SDK's transport
|
|
978
1045
|
*
|
|
979
1046
|
* This method is designed for serverless environments (Vercel, Netlify).
|
|
980
|
-
*
|
|
1047
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
981
1048
|
*
|
|
982
1049
|
* @param req - Node.js IncomingMessage or compatible request object
|
|
983
1050
|
* @param res - Node.js ServerResponse or compatible response object
|
|
@@ -985,13 +1052,14 @@ var McpDocsServer = class {
|
|
|
985
1052
|
*/
|
|
986
1053
|
async handleHttpRequest(req, res, parsedBody) {
|
|
987
1054
|
await this.initialize();
|
|
1055
|
+
const server = this.createMcpServer();
|
|
988
1056
|
const transport = new StreamableHTTPServerTransport({
|
|
989
1057
|
sessionIdGenerator: void 0,
|
|
990
1058
|
// Stateless mode - no session tracking
|
|
991
1059
|
enableJsonResponse: true
|
|
992
1060
|
// Return JSON instead of SSE streams
|
|
993
1061
|
});
|
|
994
|
-
await
|
|
1062
|
+
await server.connect(transport);
|
|
995
1063
|
try {
|
|
996
1064
|
await transport.handleRequest(req, res, parsedBody);
|
|
997
1065
|
} finally {
|
|
@@ -1003,18 +1071,20 @@ var McpDocsServer = class {
|
|
|
1003
1071
|
*
|
|
1004
1072
|
* This method is designed for Web Standard environments that use
|
|
1005
1073
|
* the Fetch API Request/Response pattern.
|
|
1074
|
+
* Creates a fresh McpServer per request to avoid concurrency issues.
|
|
1006
1075
|
*
|
|
1007
1076
|
* @param request - Web Standard Request object
|
|
1008
1077
|
* @returns Web Standard Response object
|
|
1009
1078
|
*/
|
|
1010
1079
|
async handleWebRequest(request) {
|
|
1011
1080
|
await this.initialize();
|
|
1081
|
+
const server = this.createMcpServer();
|
|
1012
1082
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
1013
1083
|
sessionIdGenerator: void 0,
|
|
1014
1084
|
// Stateless mode
|
|
1015
1085
|
enableJsonResponse: true
|
|
1016
1086
|
});
|
|
1017
|
-
await
|
|
1087
|
+
await server.connect(transport);
|
|
1018
1088
|
try {
|
|
1019
1089
|
return await transport.handleRequest(request);
|
|
1020
1090
|
} finally {
|
|
@@ -1028,9 +1098,8 @@ var McpDocsServer = class {
|
|
|
1028
1098
|
*/
|
|
1029
1099
|
async getStatus() {
|
|
1030
1100
|
let docCount = 0;
|
|
1031
|
-
if (this.searchProvider
|
|
1032
|
-
|
|
1033
|
-
docCount = docs ? Object.keys(docs).length : 0;
|
|
1101
|
+
if (this.searchProvider?.getDocCount) {
|
|
1102
|
+
docCount = this.searchProvider.getDocCount();
|
|
1034
1103
|
}
|
|
1035
1104
|
return {
|
|
1036
1105
|
name: this.config.name,
|
|
@@ -1041,16 +1110,8 @@ var McpDocsServer = class {
|
|
|
1041
1110
|
searchProvider: this.searchProvider?.name
|
|
1042
1111
|
};
|
|
1043
1112
|
}
|
|
1044
|
-
/**
|
|
1045
|
-
* Get the underlying McpServer instance
|
|
1046
|
-
*
|
|
1047
|
-
* Useful for advanced use cases like custom transports
|
|
1048
|
-
*/
|
|
1049
|
-
getMcpServer() {
|
|
1050
|
-
return this.mcpServer;
|
|
1051
|
-
}
|
|
1052
1113
|
};
|
|
1053
1114
|
|
|
1054
|
-
export {
|
|
1115
|
+
export { DEFAULT_PLUGIN_OPTIONS, McpDocsServer, mcpServerPlugin as default, docsFetchTool, docsSearchTool, loadIndexer, loadSearchProvider, mcpServerPlugin };
|
|
1055
1116
|
//# sourceMappingURL=index.js.map
|
|
1056
1117
|
//# sourceMappingURL=index.js.map
|