docusaurus-plugin-mcp-server 0.6.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/package.json +1 -1
package/dist/cli/verify.js
CHANGED
|
@@ -145,19 +145,126 @@ async function importSearchIndex(data) {
|
|
|
145
145
|
}
|
|
146
146
|
return index;
|
|
147
147
|
}
|
|
148
|
+
var FlexSearchProvider = class {
|
|
149
|
+
name = "flexsearch";
|
|
150
|
+
docs = null;
|
|
151
|
+
searchIndex = null;
|
|
152
|
+
ready = false;
|
|
153
|
+
async initialize(_context, initData) {
|
|
154
|
+
if (!initData) {
|
|
155
|
+
throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
|
|
156
|
+
}
|
|
157
|
+
if (initData.docs && initData.indexData) {
|
|
158
|
+
this.docs = initData.docs;
|
|
159
|
+
this.searchIndex = await importSearchIndex(initData.indexData);
|
|
160
|
+
this.ready = true;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (initData.docsPath && initData.indexPath) {
|
|
164
|
+
if (await fs2__default.default.pathExists(initData.docsPath)) {
|
|
165
|
+
this.docs = await fs2__default.default.readJson(initData.docsPath);
|
|
166
|
+
} else {
|
|
167
|
+
throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
|
|
168
|
+
}
|
|
169
|
+
if (await fs2__default.default.pathExists(initData.indexPath)) {
|
|
170
|
+
const indexData = await fs2__default.default.readJson(initData.indexPath);
|
|
171
|
+
this.searchIndex = await importSearchIndex(indexData);
|
|
172
|
+
} else {
|
|
173
|
+
throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
|
|
174
|
+
}
|
|
175
|
+
this.ready = true;
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
throw new Error(
|
|
179
|
+
"[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
isReady() {
|
|
183
|
+
return this.ready && this.docs !== null && this.searchIndex !== null;
|
|
184
|
+
}
|
|
185
|
+
async search(query, options) {
|
|
186
|
+
if (!this.isReady() || !this.docs || !this.searchIndex) {
|
|
187
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
188
|
+
}
|
|
189
|
+
const limit = options?.limit ?? 5;
|
|
190
|
+
return searchIndex(this.searchIndex, this.docs, query, { limit });
|
|
191
|
+
}
|
|
192
|
+
async getDocument(route) {
|
|
193
|
+
if (!this.docs) {
|
|
194
|
+
throw new Error("[FlexSearch] Provider not initialized");
|
|
195
|
+
}
|
|
196
|
+
if (this.docs[route]) {
|
|
197
|
+
return this.docs[route];
|
|
198
|
+
}
|
|
199
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
200
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
201
|
+
return this.docs[normalizedRoute] ?? this.docs[withoutSlash] ?? null;
|
|
202
|
+
}
|
|
203
|
+
async healthCheck() {
|
|
204
|
+
if (!this.isReady()) {
|
|
205
|
+
return { healthy: false, message: "FlexSearch provider not initialized" };
|
|
206
|
+
}
|
|
207
|
+
const docCount = this.docs ? Object.keys(this.docs).length : 0;
|
|
208
|
+
return {
|
|
209
|
+
healthy: true,
|
|
210
|
+
message: `FlexSearch provider ready with ${docCount} documents`
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Get all loaded documents (for compatibility with existing server code)
|
|
215
|
+
*/
|
|
216
|
+
getDocs() {
|
|
217
|
+
return this.docs;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Get the FlexSearch index (for compatibility with existing server code)
|
|
221
|
+
*/
|
|
222
|
+
getSearchIndex() {
|
|
223
|
+
return this.searchIndex;
|
|
224
|
+
}
|
|
225
|
+
};
|
|
148
226
|
|
|
149
|
-
// src/
|
|
150
|
-
function
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
227
|
+
// src/providers/loader.ts
|
|
228
|
+
async function loadSearchProvider(specifier) {
|
|
229
|
+
if (specifier === "flexsearch") {
|
|
230
|
+
return new FlexSearchProvider();
|
|
231
|
+
}
|
|
232
|
+
try {
|
|
233
|
+
const module = await import(specifier);
|
|
234
|
+
const ProviderClass = module.default;
|
|
235
|
+
if (typeof ProviderClass === "function") {
|
|
236
|
+
const instance = new ProviderClass();
|
|
237
|
+
if (!isSearchProvider(instance)) {
|
|
238
|
+
throw new Error(
|
|
239
|
+
`Invalid search provider module "${specifier}": does not implement SearchProvider interface`
|
|
240
|
+
);
|
|
241
|
+
}
|
|
242
|
+
return instance;
|
|
243
|
+
}
|
|
244
|
+
if (isSearchProvider(ProviderClass)) {
|
|
245
|
+
return ProviderClass;
|
|
246
|
+
}
|
|
247
|
+
throw new Error(
|
|
248
|
+
`Invalid search provider module "${specifier}": must export a default class or SearchProvider instance`
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
if (error instanceof Error && error.message.includes("Cannot find module")) {
|
|
252
|
+
throw new Error(
|
|
253
|
+
`Search provider module not found: "${specifier}". Check the path or package name.`
|
|
254
|
+
);
|
|
255
|
+
}
|
|
256
|
+
throw error;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
function isSearchProvider(obj) {
|
|
260
|
+
if (!obj || typeof obj !== "object") {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
const provider = obj;
|
|
264
|
+
return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
|
|
160
265
|
}
|
|
266
|
+
|
|
267
|
+
// src/mcp/tools/docs-search.ts
|
|
161
268
|
function formatSearchResults(results, baseUrl) {
|
|
162
269
|
if (results.length === 0) {
|
|
163
270
|
return "No matching documents found.";
|
|
@@ -183,28 +290,6 @@ function formatSearchResults(results, baseUrl) {
|
|
|
183
290
|
}
|
|
184
291
|
|
|
185
292
|
// src/mcp/tools/docs-get-page.ts
|
|
186
|
-
function executeDocsGetPage(params, docs) {
|
|
187
|
-
const { route } = params;
|
|
188
|
-
if (!route || typeof route !== "string") {
|
|
189
|
-
throw new Error("Route parameter is required and must be a string");
|
|
190
|
-
}
|
|
191
|
-
let normalizedRoute = route.trim();
|
|
192
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
193
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
194
|
-
}
|
|
195
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
196
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
197
|
-
}
|
|
198
|
-
const doc = docs[normalizedRoute];
|
|
199
|
-
if (!doc) {
|
|
200
|
-
const altRoute = normalizedRoute.slice(1);
|
|
201
|
-
if (docs[altRoute]) {
|
|
202
|
-
return docs[altRoute] ?? null;
|
|
203
|
-
}
|
|
204
|
-
return null;
|
|
205
|
-
}
|
|
206
|
-
return doc;
|
|
207
|
-
}
|
|
208
293
|
function formatPageContent(doc, baseUrl) {
|
|
209
294
|
if (!doc) {
|
|
210
295
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -249,52 +334,6 @@ function extractSection(markdown, headingId, headings) {
|
|
|
249
334
|
}
|
|
250
335
|
|
|
251
336
|
// src/mcp/tools/docs-get-section.ts
|
|
252
|
-
function executeDocsGetSection(params, docs) {
|
|
253
|
-
const { route, headingId } = params;
|
|
254
|
-
if (!route || typeof route !== "string") {
|
|
255
|
-
throw new Error("Route parameter is required and must be a string");
|
|
256
|
-
}
|
|
257
|
-
if (!headingId || typeof headingId !== "string") {
|
|
258
|
-
throw new Error("HeadingId parameter is required and must be a string");
|
|
259
|
-
}
|
|
260
|
-
let normalizedRoute = route.trim();
|
|
261
|
-
if (!normalizedRoute.startsWith("/")) {
|
|
262
|
-
normalizedRoute = "/" + normalizedRoute;
|
|
263
|
-
}
|
|
264
|
-
if (normalizedRoute.length > 1 && normalizedRoute.endsWith("/")) {
|
|
265
|
-
normalizedRoute = normalizedRoute.slice(0, -1);
|
|
266
|
-
}
|
|
267
|
-
const doc = docs[normalizedRoute];
|
|
268
|
-
if (!doc) {
|
|
269
|
-
return {
|
|
270
|
-
content: null,
|
|
271
|
-
doc: null,
|
|
272
|
-
headingText: null,
|
|
273
|
-
availableHeadings: []
|
|
274
|
-
};
|
|
275
|
-
}
|
|
276
|
-
const availableHeadings = doc.headings.map((h) => ({
|
|
277
|
-
id: h.id,
|
|
278
|
-
text: h.text,
|
|
279
|
-
level: h.level
|
|
280
|
-
}));
|
|
281
|
-
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
282
|
-
if (!heading) {
|
|
283
|
-
return {
|
|
284
|
-
content: null,
|
|
285
|
-
doc,
|
|
286
|
-
headingText: null,
|
|
287
|
-
availableHeadings
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
const content = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
291
|
-
return {
|
|
292
|
-
content,
|
|
293
|
-
doc,
|
|
294
|
-
headingText: heading.text,
|
|
295
|
-
availableHeadings
|
|
296
|
-
};
|
|
297
|
-
}
|
|
298
337
|
function formatSectionContent(result, headingId, baseUrl) {
|
|
299
338
|
if (!result.doc) {
|
|
300
339
|
return "Page not found. Please check the route path and try again.";
|
|
@@ -331,8 +370,7 @@ function isDataConfig(config) {
|
|
|
331
370
|
}
|
|
332
371
|
var McpDocsServer = class {
|
|
333
372
|
config;
|
|
334
|
-
|
|
335
|
-
searchIndex = null;
|
|
373
|
+
searchProvider = null;
|
|
336
374
|
mcpServer;
|
|
337
375
|
initialized = false;
|
|
338
376
|
constructor(config) {
|
|
@@ -365,18 +403,26 @@ var McpDocsServer = class {
|
|
|
365
403
|
},
|
|
366
404
|
async ({ query, limit }) => {
|
|
367
405
|
await this.initialize();
|
|
368
|
-
if (!this.
|
|
406
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
369
407
|
return {
|
|
370
408
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
371
409
|
isError: true
|
|
372
410
|
};
|
|
373
411
|
}
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
412
|
+
try {
|
|
413
|
+
const results = await this.searchProvider.search(query, { limit });
|
|
414
|
+
return {
|
|
415
|
+
content: [
|
|
416
|
+
{ type: "text", text: formatSearchResults(results, this.config.baseUrl) }
|
|
417
|
+
]
|
|
418
|
+
};
|
|
419
|
+
} catch (error) {
|
|
420
|
+
console.error("[MCP] Search error:", error);
|
|
421
|
+
return {
|
|
422
|
+
content: [{ type: "text", text: `Search error: ${String(error)}` }],
|
|
423
|
+
isError: true
|
|
424
|
+
};
|
|
425
|
+
}
|
|
380
426
|
}
|
|
381
427
|
);
|
|
382
428
|
this.mcpServer.registerTool(
|
|
@@ -389,16 +435,24 @@ var McpDocsServer = class {
|
|
|
389
435
|
},
|
|
390
436
|
async ({ route }) => {
|
|
391
437
|
await this.initialize();
|
|
392
|
-
if (!this.
|
|
438
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
393
439
|
return {
|
|
394
440
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
395
441
|
isError: true
|
|
396
442
|
};
|
|
397
443
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
444
|
+
try {
|
|
445
|
+
const doc = await this.getDocument(route);
|
|
446
|
+
return {
|
|
447
|
+
content: [{ type: "text", text: formatPageContent(doc, this.config.baseUrl) }]
|
|
448
|
+
};
|
|
449
|
+
} catch (error) {
|
|
450
|
+
console.error("[MCP] Get page error:", error);
|
|
451
|
+
return {
|
|
452
|
+
content: [{ type: "text", text: `Error getting page: ${String(error)}` }],
|
|
453
|
+
isError: true
|
|
454
|
+
};
|
|
455
|
+
}
|
|
402
456
|
}
|
|
403
457
|
);
|
|
404
458
|
this.mcpServer.registerTool(
|
|
@@ -414,26 +468,95 @@ var McpDocsServer = class {
|
|
|
414
468
|
},
|
|
415
469
|
async ({ route, headingId }) => {
|
|
416
470
|
await this.initialize();
|
|
417
|
-
if (!this.
|
|
471
|
+
if (!this.searchProvider || !this.searchProvider.isReady()) {
|
|
418
472
|
return {
|
|
419
473
|
content: [{ type: "text", text: "Server not initialized. Please try again." }],
|
|
420
474
|
isError: true
|
|
421
475
|
};
|
|
422
476
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
{
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
477
|
+
try {
|
|
478
|
+
const doc = await this.getDocument(route);
|
|
479
|
+
if (!doc) {
|
|
480
|
+
return {
|
|
481
|
+
content: [
|
|
482
|
+
{
|
|
483
|
+
type: "text",
|
|
484
|
+
text: formatSectionContent(
|
|
485
|
+
{ content: null, doc: null, headingText: null, availableHeadings: [] },
|
|
486
|
+
headingId,
|
|
487
|
+
this.config.baseUrl
|
|
488
|
+
)
|
|
489
|
+
}
|
|
490
|
+
]
|
|
491
|
+
};
|
|
492
|
+
}
|
|
493
|
+
const availableHeadings = doc.headings.map((h) => ({
|
|
494
|
+
id: h.id,
|
|
495
|
+
text: h.text,
|
|
496
|
+
level: h.level
|
|
497
|
+
}));
|
|
498
|
+
const heading = doc.headings.find((h) => h.id === headingId.trim());
|
|
499
|
+
if (!heading) {
|
|
500
|
+
return {
|
|
501
|
+
content: [
|
|
502
|
+
{
|
|
503
|
+
type: "text",
|
|
504
|
+
text: formatSectionContent(
|
|
505
|
+
{ content: null, doc, headingText: null, availableHeadings },
|
|
506
|
+
headingId,
|
|
507
|
+
this.config.baseUrl
|
|
508
|
+
)
|
|
509
|
+
}
|
|
510
|
+
]
|
|
511
|
+
};
|
|
512
|
+
}
|
|
513
|
+
const sectionContent = extractSection(doc.markdown, headingId.trim(), doc.headings);
|
|
514
|
+
return {
|
|
515
|
+
content: [
|
|
516
|
+
{
|
|
517
|
+
type: "text",
|
|
518
|
+
text: formatSectionContent(
|
|
519
|
+
{ content: sectionContent, doc, headingText: heading.text, availableHeadings },
|
|
520
|
+
headingId,
|
|
521
|
+
this.config.baseUrl
|
|
522
|
+
)
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
};
|
|
526
|
+
} catch (error) {
|
|
527
|
+
console.error("[MCP] Get section error:", error);
|
|
528
|
+
return {
|
|
529
|
+
content: [{ type: "text", text: `Error getting section: ${String(error)}` }],
|
|
530
|
+
isError: true
|
|
531
|
+
};
|
|
532
|
+
}
|
|
432
533
|
}
|
|
433
534
|
);
|
|
434
535
|
}
|
|
435
536
|
/**
|
|
436
|
-
*
|
|
537
|
+
* Get a document by route using the search provider
|
|
538
|
+
*/
|
|
539
|
+
async getDocument(route) {
|
|
540
|
+
if (!this.searchProvider) {
|
|
541
|
+
return null;
|
|
542
|
+
}
|
|
543
|
+
if (this.searchProvider.getDocument) {
|
|
544
|
+
return this.searchProvider.getDocument(route);
|
|
545
|
+
}
|
|
546
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
547
|
+
const docs = this.searchProvider.getDocs();
|
|
548
|
+
if (!docs) return null;
|
|
549
|
+
if (docs[route]) {
|
|
550
|
+
return docs[route];
|
|
551
|
+
}
|
|
552
|
+
const normalizedRoute = route.startsWith("/") ? route : `/${route}`;
|
|
553
|
+
const withoutSlash = route.startsWith("/") ? route.slice(1) : route;
|
|
554
|
+
return docs[normalizedRoute] ?? docs[withoutSlash] ?? null;
|
|
555
|
+
}
|
|
556
|
+
return null;
|
|
557
|
+
}
|
|
558
|
+
/**
|
|
559
|
+
* Load docs and search index using the configured search provider
|
|
437
560
|
*
|
|
438
561
|
* For file-based config: reads from disk
|
|
439
562
|
* For data config: uses pre-loaded data directly
|
|
@@ -443,24 +566,26 @@ var McpDocsServer = class {
|
|
|
443
566
|
return;
|
|
444
567
|
}
|
|
445
568
|
try {
|
|
569
|
+
const searchSpecifier = this.config.search ?? "flexsearch";
|
|
570
|
+
this.searchProvider = await loadSearchProvider(searchSpecifier);
|
|
571
|
+
const providerContext = {
|
|
572
|
+
baseUrl: this.config.baseUrl ?? "",
|
|
573
|
+
serverName: this.config.name,
|
|
574
|
+
serverVersion: this.config.version ?? "1.0.0",
|
|
575
|
+
outputDir: ""
|
|
576
|
+
// Not relevant for runtime
|
|
577
|
+
};
|
|
578
|
+
const initData = {};
|
|
446
579
|
if (isDataConfig(this.config)) {
|
|
447
|
-
|
|
448
|
-
|
|
580
|
+
initData.docs = this.config.docs;
|
|
581
|
+
initData.indexData = this.config.searchIndexData;
|
|
449
582
|
} else if (isFileConfig(this.config)) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
} else {
|
|
453
|
-
throw new Error(`Docs file not found: ${this.config.docsPath}`);
|
|
454
|
-
}
|
|
455
|
-
if (await fs2__default.default.pathExists(this.config.indexPath)) {
|
|
456
|
-
const indexData = await fs2__default.default.readJson(this.config.indexPath);
|
|
457
|
-
this.searchIndex = await importSearchIndex(indexData);
|
|
458
|
-
} else {
|
|
459
|
-
throw new Error(`Search index not found: ${this.config.indexPath}`);
|
|
460
|
-
}
|
|
583
|
+
initData.docsPath = this.config.docsPath;
|
|
584
|
+
initData.indexPath = this.config.indexPath;
|
|
461
585
|
} else {
|
|
462
586
|
throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
|
|
463
587
|
}
|
|
588
|
+
await this.searchProvider.initialize(providerContext, initData);
|
|
464
589
|
this.initialized = true;
|
|
465
590
|
} catch (error) {
|
|
466
591
|
console.error("[MCP] Failed to initialize:", error);
|
|
@@ -521,12 +646,18 @@ var McpDocsServer = class {
|
|
|
521
646
|
* Useful for health checks and debugging
|
|
522
647
|
*/
|
|
523
648
|
async getStatus() {
|
|
649
|
+
let docCount = 0;
|
|
650
|
+
if (this.searchProvider instanceof FlexSearchProvider) {
|
|
651
|
+
const docs = this.searchProvider.getDocs();
|
|
652
|
+
docCount = docs ? Object.keys(docs).length : 0;
|
|
653
|
+
}
|
|
524
654
|
return {
|
|
525
655
|
name: this.config.name,
|
|
526
656
|
version: this.config.version ?? "1.0.0",
|
|
527
657
|
initialized: this.initialized,
|
|
528
|
-
docCount
|
|
529
|
-
baseUrl: this.config.baseUrl
|
|
658
|
+
docCount,
|
|
659
|
+
baseUrl: this.config.baseUrl,
|
|
660
|
+
searchProvider: this.searchProvider?.name
|
|
530
661
|
};
|
|
531
662
|
}
|
|
532
663
|
/**
|