docusaurus-plugin-mcp-server 0.10.2 → 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.
@@ -1,17 +1,19 @@
1
+ import FlexSearch from 'flexsearch';
2
+ import fs from 'fs-extra';
1
3
  import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
4
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
3
5
  import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';
4
- import FlexSearch from 'flexsearch';
5
- import fs from 'fs-extra';
6
6
  import { z } from 'zod';
7
7
  import { createServer } from 'http';
8
8
 
9
- // src/mcp/server.ts
10
- var FIELD_WEIGHTS = {
11
- title: 3,
12
- headings: 2,
13
- description: 1.5,
14
- content: 1
9
+ var __defProp = Object.defineProperty;
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+ var __export = (target, all) => {
15
+ for (var name in all)
16
+ __defProp(target, name, { get: all[name], enumerable: true });
15
17
  };
16
18
  function englishStemmer(word) {
17
19
  if (word.length <= 3) return word;
@@ -19,9 +21,9 @@ function englishStemmer(word) {
19
21
  }
20
22
  function createSearchIndex() {
21
23
  return new FlexSearch.Document({
22
- // Use 'full' tokenization for substring matching
23
- // This allows "auth" to match "authentication"
24
- tokenize: "full",
24
+ // Use 'forward' tokenization to avoid OOM with large doc sets
25
+ // See: https://github.com/scalvert/docusaurus-plugin-mcp-server/issues/11
26
+ tokenize: "forward",
25
27
  // Enable caching for faster repeated queries
26
28
  cache: 100,
27
29
  // Higher resolution = more granular ranking (1-9)
@@ -47,8 +49,8 @@ function createSearchIndex() {
47
49
  }
48
50
  });
49
51
  }
50
- function searchIndex(index, docs, query, options = {}) {
51
- const { limit = 5 } = options;
52
+ function querySearchIndex(index, docs, query, options = {}) {
53
+ const { limit = 16 } = options;
52
54
  const rawResults = index.search(query, {
53
55
  limit: limit * 3,
54
56
  // Get extra results for better ranking after weighting
@@ -139,84 +141,111 @@ async function importSearchIndex(data) {
139
141
  }
140
142
  return index;
141
143
  }
142
- var FlexSearchProvider = class {
143
- name = "flexsearch";
144
- docs = null;
145
- searchIndex = null;
146
- ready = false;
147
- async initialize(_context, initData) {
148
- if (!initData) {
149
- throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
150
- }
151
- if (initData.docs && initData.indexData) {
152
- this.docs = initData.docs;
153
- this.searchIndex = await importSearchIndex(initData.indexData);
154
- this.ready = true;
155
- return;
156
- }
157
- if (initData.docsPath && initData.indexPath) {
158
- if (await fs.pathExists(initData.docsPath)) {
159
- this.docs = await fs.readJson(initData.docsPath);
160
- } else {
161
- throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
144
+ var FIELD_WEIGHTS;
145
+ var init_flexsearch_core = __esm({
146
+ "src/search/flexsearch-core.ts"() {
147
+ FIELD_WEIGHTS = {
148
+ title: 3,
149
+ headings: 2,
150
+ description: 1.5,
151
+ content: 1
152
+ };
153
+ }
154
+ });
155
+
156
+ // src/providers/search/flexsearch-provider.ts
157
+ var flexsearch_provider_exports = {};
158
+ __export(flexsearch_provider_exports, {
159
+ FlexSearchProvider: () => FlexSearchProvider
160
+ });
161
+ var FlexSearchProvider;
162
+ var init_flexsearch_provider = __esm({
163
+ "src/providers/search/flexsearch-provider.ts"() {
164
+ init_flexsearch_core();
165
+ FlexSearchProvider = class {
166
+ name = "flexsearch";
167
+ docs = null;
168
+ searchIndex = null;
169
+ ready = false;
170
+ async initialize(_context, initData) {
171
+ if (!initData) {
172
+ throw new Error("[FlexSearch] SearchProviderInitData required for FlexSearch provider");
173
+ }
174
+ if (initData.docs && initData.indexData) {
175
+ this.docs = initData.docs;
176
+ this.searchIndex = await importSearchIndex(initData.indexData);
177
+ this.ready = true;
178
+ return;
179
+ }
180
+ if (initData.docsPath && initData.indexPath) {
181
+ if (await fs.pathExists(initData.docsPath)) {
182
+ this.docs = await fs.readJson(initData.docsPath);
183
+ } else {
184
+ throw new Error(`[FlexSearch] Docs file not found: ${initData.docsPath}`);
185
+ }
186
+ if (await fs.pathExists(initData.indexPath)) {
187
+ const indexData = await fs.readJson(initData.indexPath);
188
+ this.searchIndex = await importSearchIndex(indexData);
189
+ } else {
190
+ throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
191
+ }
192
+ this.ready = true;
193
+ return;
194
+ }
195
+ throw new Error(
196
+ "[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
197
+ );
162
198
  }
163
- if (await fs.pathExists(initData.indexPath)) {
164
- const indexData = await fs.readJson(initData.indexPath);
165
- this.searchIndex = await importSearchIndex(indexData);
166
- } else {
167
- throw new Error(`[FlexSearch] Search index not found: ${initData.indexPath}`);
199
+ isReady() {
200
+ return this.ready && this.docs !== null && this.searchIndex !== null;
201
+ }
202
+ async search(query, options) {
203
+ if (!this.isReady() || !this.docs || !this.searchIndex) {
204
+ throw new Error("[FlexSearch] Provider not initialized");
205
+ }
206
+ const limit = options?.limit ?? 16;
207
+ return querySearchIndex(this.searchIndex, this.docs, query, { limit });
208
+ }
209
+ async getDocument(url) {
210
+ if (!this.docs) {
211
+ throw new Error("[FlexSearch] Provider not initialized");
212
+ }
213
+ return this.docs[url] ?? null;
214
+ }
215
+ async healthCheck() {
216
+ if (!this.isReady()) {
217
+ return { healthy: false, message: "FlexSearch provider not initialized" };
218
+ }
219
+ const docCount = this.docs ? Object.keys(this.docs).length : 0;
220
+ return {
221
+ healthy: true,
222
+ message: `FlexSearch provider ready with ${docCount} documents`
223
+ };
224
+ }
225
+ getDocCount() {
226
+ return this.docs ? Object.keys(this.docs).length : 0;
227
+ }
228
+ /**
229
+ * Get all loaded documents (for compatibility with existing server code)
230
+ */
231
+ getDocs() {
232
+ return this.docs;
233
+ }
234
+ /**
235
+ * Get the FlexSearch index (for compatibility with existing server code)
236
+ */
237
+ getSearchIndex() {
238
+ return this.searchIndex;
168
239
  }
169
- this.ready = true;
170
- return;
171
- }
172
- throw new Error(
173
- "[FlexSearch] Invalid init data: must provide either file paths (docsPath, indexPath) or pre-loaded data (docs, indexData)"
174
- );
175
- }
176
- isReady() {
177
- return this.ready && this.docs !== null && this.searchIndex !== null;
178
- }
179
- async search(query, options) {
180
- if (!this.isReady() || !this.docs || !this.searchIndex) {
181
- throw new Error("[FlexSearch] Provider not initialized");
182
- }
183
- const limit = options?.limit ?? 5;
184
- return searchIndex(this.searchIndex, this.docs, query, { limit });
185
- }
186
- async getDocument(url) {
187
- if (!this.docs) {
188
- throw new Error("[FlexSearch] Provider not initialized");
189
- }
190
- return this.docs[url] ?? null;
191
- }
192
- async healthCheck() {
193
- if (!this.isReady()) {
194
- return { healthy: false, message: "FlexSearch provider not initialized" };
195
- }
196
- const docCount = this.docs ? Object.keys(this.docs).length : 0;
197
- return {
198
- healthy: true,
199
- message: `FlexSearch provider ready with ${docCount} documents`
200
240
  };
201
241
  }
202
- /**
203
- * Get all loaded documents (for compatibility with existing server code)
204
- */
205
- getDocs() {
206
- return this.docs;
207
- }
208
- /**
209
- * Get the FlexSearch index (for compatibility with existing server code)
210
- */
211
- getSearchIndex() {
212
- return this.searchIndex;
213
- }
214
- };
242
+ });
215
243
 
216
244
  // src/providers/loader.ts
217
245
  async function loadSearchProvider(specifier) {
218
246
  if (specifier === "flexsearch") {
219
- return new FlexSearchProvider();
247
+ const { FlexSearchProvider: FlexSearchProvider2 } = await Promise.resolve().then(() => (init_flexsearch_provider(), flexsearch_provider_exports));
248
+ return new FlexSearchProvider2();
220
249
  }
221
250
  try {
222
251
  const module = await import(specifier);
@@ -239,7 +268,8 @@ async function loadSearchProvider(specifier) {
239
268
  } catch (error) {
240
269
  if (error instanceof Error && error.message.includes("Cannot find module")) {
241
270
  throw new Error(
242
- `Search provider module not found: "${specifier}". Check the path or package name.`
271
+ `Search provider module not found: "${specifier}". Check the path or package name.`,
272
+ { cause: error }
243
273
  );
244
274
  }
245
275
  throw error;
@@ -252,9 +282,12 @@ function isSearchProvider(obj) {
252
282
  const provider = obj;
253
283
  return typeof provider.name === "string" && typeof provider.initialize === "function" && typeof provider.isReady === "function" && typeof provider.search === "function";
254
284
  }
285
+
286
+ // src/mcp/tools/docs-search.ts
287
+ init_flexsearch_core();
255
288
  var docsSearchInputSchema = {
256
289
  query: z.string().min(1).describe("The search query string"),
257
- limit: z.number().int().min(1).max(20).optional().default(5).describe("Maximum number of results to return (1-20, default: 5)")
290
+ limit: z.number().int().min(1).max(20).optional().default(16).describe("Maximum number of results to return (1-20, default: 16)")
258
291
  };
259
292
  var docsSearchTool = {
260
293
  name: "docs_search",
@@ -329,14 +362,22 @@ function isDataConfig(config) {
329
362
  var McpDocsServer = class {
330
363
  config;
331
364
  searchProvider = null;
332
- mcpServer;
333
365
  initialized = false;
366
+ initPromise = null;
367
+ initError = null;
334
368
  constructor(config) {
335
369
  this.config = config;
336
- this.mcpServer = new McpServer(
370
+ }
371
+ /**
372
+ * Create a fresh McpServer instance with tools registered.
373
+ * Each request gets its own server to avoid concurrency issues
374
+ * with the SDK's transport reassignment.
375
+ */
376
+ createMcpServer() {
377
+ const server = new McpServer(
337
378
  {
338
- name: config.name,
339
- version: config.version ?? "1.0.0"
379
+ name: this.config.name,
380
+ version: this.config.version ?? "1.0.0"
340
381
  },
341
382
  {
342
383
  capabilities: {
@@ -344,20 +385,20 @@ var McpDocsServer = class {
344
385
  }
345
386
  }
346
387
  );
347
- this.registerTools();
388
+ this.registerTools(server);
389
+ return server;
348
390
  }
349
391
  /**
350
392
  * Register all MCP tools using definitions from tool files
351
393
  */
352
- registerTools() {
353
- this.mcpServer.registerTool(
394
+ registerTools(server) {
395
+ server.registerTool(
354
396
  docsSearchTool.name,
355
397
  {
356
398
  description: docsSearchTool.description,
357
399
  inputSchema: docsSearchTool.inputSchema
358
400
  },
359
401
  async ({ query, limit }) => {
360
- await this.initialize();
361
402
  if (!this.searchProvider || !this.searchProvider.isReady()) {
362
403
  return {
363
404
  content: [{ type: "text", text: "Server not initialized. Please try again." }],
@@ -372,20 +413,24 @@ var McpDocsServer = class {
372
413
  } catch (error) {
373
414
  console.error("[MCP] Search error:", error);
374
415
  return {
375
- content: [{ type: "text", text: `Search error: ${String(error)}` }],
416
+ content: [
417
+ {
418
+ type: "text",
419
+ text: "An error occurred while searching. Please try again."
420
+ }
421
+ ],
376
422
  isError: true
377
423
  };
378
424
  }
379
425
  }
380
426
  );
381
- this.mcpServer.registerTool(
427
+ server.registerTool(
382
428
  docsFetchTool.name,
383
429
  {
384
430
  description: docsFetchTool.description,
385
431
  inputSchema: docsFetchTool.inputSchema
386
432
  },
387
433
  async ({ url }) => {
388
- await this.initialize();
389
434
  if (!this.searchProvider || !this.searchProvider.isReady()) {
390
435
  return {
391
436
  content: [{ type: "text", text: "Server not initialized. Please try again." }],
@@ -400,7 +445,12 @@ var McpDocsServer = class {
400
445
  } catch (error) {
401
446
  console.error("[MCP] Fetch error:", error);
402
447
  return {
403
- content: [{ type: "text", text: `Error fetching page: ${String(error)}` }],
448
+ content: [
449
+ {
450
+ type: "text",
451
+ text: "An error occurred while fetching the page. Please try again."
452
+ }
453
+ ],
404
454
  isError: true
405
455
  };
406
456
  }
@@ -424,43 +474,54 @@ var McpDocsServer = class {
424
474
  *
425
475
  * For file-based config: reads from disk
426
476
  * For data config: uses pre-loaded data directly
477
+ *
478
+ * Uses promise-based locking to prevent concurrent initialization.
479
+ * Caches initialization errors so repeated calls fail fast.
427
480
  */
428
481
  async initialize() {
482
+ if (this.initError) {
483
+ throw this.initError;
484
+ }
429
485
  if (this.initialized) {
430
486
  return;
431
487
  }
432
- try {
433
- const searchSpecifier = this.config.search ?? "flexsearch";
434
- this.searchProvider = await loadSearchProvider(searchSpecifier);
435
- const providerContext = {
436
- baseUrl: this.config.baseUrl ?? "",
437
- serverName: this.config.name,
438
- serverVersion: this.config.version ?? "1.0.0",
439
- outputDir: ""
440
- // Not relevant for runtime
441
- };
442
- const initData = {};
443
- if (isDataConfig(this.config)) {
444
- initData.docs = this.config.docs;
445
- initData.indexData = this.config.searchIndexData;
446
- } else if (isFileConfig(this.config)) {
447
- initData.docsPath = this.config.docsPath;
448
- initData.indexPath = this.config.indexPath;
449
- } else {
450
- throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
451
- }
452
- await this.searchProvider.initialize(providerContext, initData);
453
- this.initialized = true;
454
- } catch (error) {
455
- console.error("[MCP] Failed to initialize:", error);
456
- throw error;
488
+ if (!this.initPromise) {
489
+ this.initPromise = this._doInitialize().catch((error) => {
490
+ this.initError = error instanceof Error ? error : new Error(String(error));
491
+ this.initPromise = null;
492
+ throw this.initError;
493
+ });
457
494
  }
495
+ return this.initPromise;
496
+ }
497
+ async _doInitialize() {
498
+ const searchSpecifier = this.config.search ?? "flexsearch";
499
+ this.searchProvider = await loadSearchProvider(searchSpecifier);
500
+ const providerContext = {
501
+ baseUrl: this.config.baseUrl ?? "",
502
+ serverName: this.config.name,
503
+ serverVersion: this.config.version ?? "1.0.0",
504
+ outputDir: ""
505
+ // Not relevant for runtime
506
+ };
507
+ const initData = {};
508
+ if (isDataConfig(this.config)) {
509
+ initData.docs = this.config.docs;
510
+ initData.indexData = this.config.searchIndexData;
511
+ } else if (isFileConfig(this.config)) {
512
+ initData.docsPath = this.config.docsPath;
513
+ initData.indexPath = this.config.indexPath;
514
+ } else {
515
+ throw new Error("Invalid server config: must provide either file paths or pre-loaded data");
516
+ }
517
+ await this.searchProvider.initialize(providerContext, initData);
518
+ this.initialized = true;
458
519
  }
459
520
  /**
460
521
  * Handle an HTTP request using the MCP SDK's transport
461
522
  *
462
523
  * This method is designed for serverless environments (Vercel, Netlify).
463
- * It creates a stateless transport instance and processes the request.
524
+ * Creates a fresh McpServer per request to avoid concurrency issues.
464
525
  *
465
526
  * @param req - Node.js IncomingMessage or compatible request object
466
527
  * @param res - Node.js ServerResponse or compatible response object
@@ -468,13 +529,14 @@ var McpDocsServer = class {
468
529
  */
469
530
  async handleHttpRequest(req, res, parsedBody) {
470
531
  await this.initialize();
532
+ const server = this.createMcpServer();
471
533
  const transport = new StreamableHTTPServerTransport({
472
534
  sessionIdGenerator: void 0,
473
535
  // Stateless mode - no session tracking
474
536
  enableJsonResponse: true
475
537
  // Return JSON instead of SSE streams
476
538
  });
477
- await this.mcpServer.connect(transport);
539
+ await server.connect(transport);
478
540
  try {
479
541
  await transport.handleRequest(req, res, parsedBody);
480
542
  } finally {
@@ -486,18 +548,20 @@ var McpDocsServer = class {
486
548
  *
487
549
  * This method is designed for Web Standard environments that use
488
550
  * the Fetch API Request/Response pattern.
551
+ * Creates a fresh McpServer per request to avoid concurrency issues.
489
552
  *
490
553
  * @param request - Web Standard Request object
491
554
  * @returns Web Standard Response object
492
555
  */
493
556
  async handleWebRequest(request) {
494
557
  await this.initialize();
558
+ const server = this.createMcpServer();
495
559
  const transport = new WebStandardStreamableHTTPServerTransport({
496
560
  sessionIdGenerator: void 0,
497
561
  // Stateless mode
498
562
  enableJsonResponse: true
499
563
  });
500
- await this.mcpServer.connect(transport);
564
+ await server.connect(transport);
501
565
  try {
502
566
  return await transport.handleRequest(request);
503
567
  } finally {
@@ -511,9 +575,8 @@ var McpDocsServer = class {
511
575
  */
512
576
  async getStatus() {
513
577
  let docCount = 0;
514
- if (this.searchProvider instanceof FlexSearchProvider) {
515
- const docs = this.searchProvider.getDocs();
516
- docCount = docs ? Object.keys(docs).length : 0;
578
+ if (this.searchProvider?.getDocCount) {
579
+ docCount = this.searchProvider.getDocCount();
517
580
  }
518
581
  return {
519
582
  name: this.config.name,
@@ -524,37 +587,29 @@ var McpDocsServer = class {
524
587
  searchProvider: this.searchProvider?.name
525
588
  };
526
589
  }
527
- /**
528
- * Get the underlying McpServer instance
529
- *
530
- * Useful for advanced use cases like custom transports
531
- */
532
- getMcpServer() {
533
- return this.mcpServer;
534
- }
535
590
  };
536
591
 
537
592
  // src/adapters/cors.ts
538
- var CORS_HEADERS = {
539
- "Access-Control-Allow-Origin": "*",
540
- "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
541
- "Access-Control-Allow-Headers": "Content-Type"
542
- };
543
- function getCorsHeaders() {
544
- return { ...CORS_HEADERS };
593
+ function getCorsHeaders(origin = "*") {
594
+ return {
595
+ "Access-Control-Allow-Origin": origin,
596
+ "Access-Control-Allow-Methods": "GET, POST, OPTIONS",
597
+ "Access-Control-Allow-Headers": "Content-Type"
598
+ };
545
599
  }
546
600
 
547
601
  // src/adapters/vercel.ts
548
602
  function createVercelHandler(config) {
549
603
  let server = null;
604
+ const { corsOrigin, ...serverConfig } = config;
550
605
  function getServer() {
551
606
  if (!server) {
552
- server = new McpDocsServer(config);
607
+ server = new McpDocsServer(serverConfig);
553
608
  }
554
609
  return server;
555
610
  }
556
611
  return async function handler(req, res) {
557
- const corsHeaders = getCorsHeaders();
612
+ const corsHeaders = getCorsHeaders(corsOrigin);
558
613
  if (req.method === "OPTIONS") {
559
614
  res.writeHead(204, corsHeaders);
560
615
  res.end();
@@ -588,14 +643,13 @@ function createVercelHandler(config) {
588
643
  const mcpServer = getServer();
589
644
  await mcpServer.handleHttpRequest(req, res, req.body);
590
645
  } catch (error) {
591
- const errorMessage = error instanceof Error ? error.message : String(error);
592
646
  console.error("MCP Server Error:", error);
593
647
  return res.status(500).json({
594
648
  jsonrpc: "2.0",
595
649
  id: null,
596
650
  error: {
597
651
  code: -32603,
598
- message: `Internal server error: ${errorMessage}`
652
+ message: "Internal server error"
599
653
  }
600
654
  });
601
655
  }
@@ -635,14 +689,15 @@ async function responseToNetlify(response, additionalHeaders) {
635
689
  }
636
690
  function createNetlifyHandler(config) {
637
691
  let server = null;
692
+ const { corsOrigin, ...serverConfig } = config;
638
693
  function getServer() {
639
694
  if (!server) {
640
- server = new McpDocsServer(config);
695
+ server = new McpDocsServer(serverConfig);
641
696
  }
642
697
  return server;
643
698
  }
644
699
  return async function handler(event, _context) {
645
- const corsHeaders = getCorsHeaders();
700
+ const corsHeaders = getCorsHeaders(corsOrigin);
646
701
  const headers = {
647
702
  "Content-Type": "application/json",
648
703
  ...corsHeaders
@@ -682,7 +737,6 @@ function createNetlifyHandler(config) {
682
737
  const response = await mcpServer.handleWebRequest(request);
683
738
  return await responseToNetlify(response, corsHeaders);
684
739
  } catch (error) {
685
- const errorMessage = error instanceof Error ? error.message : String(error);
686
740
  console.error("MCP Server Error:", error);
687
741
  return {
688
742
  statusCode: 500,
@@ -692,7 +746,7 @@ function createNetlifyHandler(config) {
692
746
  id: null,
693
747
  error: {
694
748
  code: -32603,
695
- message: `Internal server error: ${errorMessage}`
749
+ message: "Internal server error"
696
750
  }
697
751
  })
698
752
  };
@@ -703,12 +757,13 @@ function createNetlifyHandler(config) {
703
757
  // src/adapters/cloudflare.ts
704
758
  function createCloudflareHandler(config) {
705
759
  let server = null;
760
+ const { corsOrigin, ...rest } = config;
706
761
  const serverConfig = {
707
- docs: config.docs,
708
- searchIndexData: config.searchIndexData,
709
- name: config.name,
710
- version: config.version,
711
- baseUrl: config.baseUrl
762
+ docs: rest.docs,
763
+ searchIndexData: rest.searchIndexData,
764
+ name: rest.name,
765
+ version: rest.version,
766
+ baseUrl: rest.baseUrl
712
767
  };
713
768
  function getServer() {
714
769
  if (!server) {
@@ -717,7 +772,7 @@ function createCloudflareHandler(config) {
717
772
  return server;
718
773
  }
719
774
  return async function fetch(request) {
720
- const corsHeaders = getCorsHeaders();
775
+ const corsHeaders = getCorsHeaders(corsOrigin);
721
776
  if (request.method === "OPTIONS") {
722
777
  return new Response(null, { status: 204, headers: corsHeaders });
723
778
  }
@@ -758,7 +813,6 @@ function createCloudflareHandler(config) {
758
813
  headers: newHeaders
759
814
  });
760
815
  } catch (error) {
761
- const errorMessage = error instanceof Error ? error.message : String(error);
762
816
  console.error("MCP Server Error:", error);
763
817
  return new Response(
764
818
  JSON.stringify({
@@ -766,7 +820,7 @@ function createCloudflareHandler(config) {
766
820
  id: null,
767
821
  error: {
768
822
  code: -32603,
769
- message: `Internal server error: ${errorMessage}`
823
+ message: "Internal server error"
770
824
  }
771
825
  }),
772
826
  {
@@ -891,31 +945,21 @@ function generateCloudflareFiles(name, baseUrl) {
891
945
  content: `/**
892
946
  * Cloudflare Worker for MCP server
893
947
  *
894
- * Note: This requires bundling docs.json and search-index.json with the worker,
895
- * or using Cloudflare KV/R2 for storage.
896
- *
897
- * For bundling, use wrangler with custom build configuration.
948
+ * Workers cannot access the filesystem, so docs and search index
949
+ * are imported as JSON modules and passed as pre-loaded data.
898
950
  */
899
951
 
900
952
  import { createCloudflareHandler } from 'docusaurus-plugin-mcp-server/adapters';
901
-
902
- // Option 1: Import bundled data (requires bundler configuration)
903
- // import docs from '../build/mcp/docs.json';
904
- // import searchIndex from '../build/mcp/search-index.json';
905
-
906
- // Option 2: Use KV bindings (requires KV namespace configuration)
907
- // const docs = await env.MCP_KV.get('docs', { type: 'json' });
908
- // const searchIndex = await env.MCP_KV.get('search-index', { type: 'json' });
953
+ import docs from '../build/mcp/docs.json';
954
+ import searchIndex from '../build/mcp/search-index.json';
909
955
 
910
956
  export default {
911
957
  fetch: createCloudflareHandler({
958
+ docs,
959
+ searchIndexData: searchIndex,
912
960
  name: '${name}',
913
961
  version: '1.0.0',
914
962
  baseUrl: '${baseUrl}',
915
- // docsPath and indexPath are used for file-based loading
916
- // For Workers, you'll need to configure data loading differently
917
- docsPath: './mcp/docs.json',
918
- indexPath: './mcp/search-index.json',
919
963
  }),
920
964
  };
921
965
  `
@@ -927,14 +971,10 @@ export default {
927
971
  main = "workers/mcp.js"
928
972
  compatibility_date = "2024-01-01"
929
973
 
930
- # Uncomment to use KV for storing docs
931
- # [[kv_namespaces]]
932
- # binding = "MCP_KV"
933
- # id = "your-kv-namespace-id"
934
-
935
- # Static assets (the Docusaurus build)
936
- # [site]
937
- # bucket = "./build"
974
+ # Allow importing JSON files as modules
975
+ [[rules]]
976
+ type = "Data"
977
+ globs = ["**/*.json"]
938
978
  `
939
979
  }
940
980
  ];
@@ -994,8 +1034,21 @@ function createNodeHandler(options) {
994
1034
  const mcpServer = getServer();
995
1035
  await mcpServer.handleHttpRequest(req, res, body);
996
1036
  } catch (error) {
997
- const message = error instanceof Error ? error.message : String(error);
998
1037
  console.error("[MCP] Request error:", error);
1038
+ if (error instanceof Error && error.message === "Request body too large") {
1039
+ res.writeHead(413, { "Content-Type": "application/json" });
1040
+ res.end(
1041
+ JSON.stringify({
1042
+ jsonrpc: "2.0",
1043
+ id: null,
1044
+ error: {
1045
+ code: -32600,
1046
+ message: "Request body too large"
1047
+ }
1048
+ })
1049
+ );
1050
+ return;
1051
+ }
999
1052
  res.writeHead(500, { "Content-Type": "application/json" });
1000
1053
  res.end(
1001
1054
  JSON.stringify({
@@ -1003,7 +1056,7 @@ function createNodeHandler(options) {
1003
1056
  id: null,
1004
1057
  error: {
1005
1058
  code: -32603,
1006
- message: `Internal server error: ${message}`
1059
+ message: "Internal server error"
1007
1060
  }
1008
1061
  })
1009
1062
  );
@@ -1014,11 +1067,19 @@ function createNodeServer(options) {
1014
1067
  const handler = createNodeHandler(options);
1015
1068
  return createServer(handler);
1016
1069
  }
1070
+ var MAX_BODY_SIZE = 1024 * 1024;
1017
1071
  async function parseRequestBody(req) {
1018
1072
  return new Promise((resolve, reject) => {
1019
1073
  let body = "";
1074
+ let size = 0;
1020
1075
  req.on("data", (chunk) => {
1021
- body += chunk;
1076
+ size += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
1077
+ if (size > MAX_BODY_SIZE) {
1078
+ req.destroy();
1079
+ reject(new Error("Request body too large"));
1080
+ return;
1081
+ }
1082
+ body += chunk.toString();
1022
1083
  });
1023
1084
  req.on("end", () => {
1024
1085
  try {