@arabold/docs-mcp-server 1.25.2 → 1.25.3

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 CHANGED
@@ -254,6 +254,7 @@ You can index documentation from your local filesystem by using a `file://` URL
254
254
  -e OPENAI_API_KEY="your-key" \
255
255
  -v /absolute/path/to/docs:/docs:ro \
256
256
  -v docs-mcp-data:/data \
257
+ -p 6280:6280 \
257
258
  ghcr.io/arabold/docs-mcp-server:latest \
258
259
  scrape mylib file:///docs/my-library
259
260
  ```
@@ -498,7 +499,11 @@ DOCS_MCP_TELEMETRY=false npx @arabold/docs-mcp-server@latest
498
499
  **Option 3: Docker**
499
500
 
500
501
  ```bash
501
- docker run -e DOCS_MCP_TELEMETRY=false ghcr.io/arabold/docs-mcp-server:latest
502
+ docker run \
503
+ -e DOCS_MCP_TELEMETRY=false \
504
+ -v docs-mcp-data:/data \
505
+ -p 6280:6280 \
506
+ ghcr.io/arabold/docs-mcp-server:latest
502
507
  ```
503
508
 
504
509
  For more details about our telemetry practices, see the [Telemetry Guide](docs/telemetry.md).
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { PostHog } from "posthog-node";
9
9
  import { randomUUID } from "node:crypto";
10
10
  import fs, { existsSync, readFileSync } from "node:fs";
11
11
  import path from "node:path";
12
+ import { fileURLToPath, URL as URL$1 } from "node:url";
12
13
  import envPaths from "env-paths";
13
14
  import { Option, Command } from "commander";
14
15
  import formBody from "@fastify/formbody";
@@ -22,7 +23,6 @@ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
22
23
  import { v4 } from "uuid";
23
24
  import { VirtualConsole, JSDOM } from "jsdom";
24
25
  import mime from "mime";
25
- import { fileURLToPath, URL as URL$1 } from "node:url";
26
26
  import psl from "psl";
27
27
  import fs$1 from "node:fs/promises";
28
28
  import axios from "axios";
@@ -539,6 +539,49 @@ class PostHogClient {
539
539
  return this.enabled && !!this.client;
540
540
  }
541
541
  }
542
+ let projectRoot = null;
543
+ function getProjectRoot() {
544
+ if (projectRoot) {
545
+ return projectRoot;
546
+ }
547
+ const currentFilePath = fileURLToPath(import.meta.url);
548
+ let currentDir = path.dirname(currentFilePath);
549
+ while (true) {
550
+ const packageJsonPath = path.join(currentDir, "package.json");
551
+ if (fs.existsSync(packageJsonPath)) {
552
+ projectRoot = currentDir;
553
+ return currentDir;
554
+ }
555
+ const parentDir = path.dirname(currentDir);
556
+ if (parentDir === currentDir) {
557
+ throw new Error("Could not find project root containing package.json.");
558
+ }
559
+ currentDir = parentDir;
560
+ }
561
+ }
562
+ function resolveStorePath(storePath) {
563
+ let dbDir;
564
+ if (storePath) {
565
+ dbDir = storePath;
566
+ } else {
567
+ const projectRoot2 = getProjectRoot();
568
+ const oldDbDir = path.join(projectRoot2, ".store");
569
+ const oldDbPath = path.join(oldDbDir, "documents.db");
570
+ const oldDbExists = fs.existsSync(oldDbPath);
571
+ if (oldDbExists) {
572
+ dbDir = oldDbDir;
573
+ } else {
574
+ const standardPaths = envPaths("docs-mcp-server", { suffix: "" });
575
+ dbDir = standardPaths.data;
576
+ }
577
+ }
578
+ try {
579
+ fs.mkdirSync(dbDir, { recursive: true });
580
+ } catch (error) {
581
+ console.warn(`⚠️ Failed to create database directory ${dbDir}: ${error}`);
582
+ }
583
+ return dbDir;
584
+ }
542
585
  class TelemetryConfig {
543
586
  static instance;
544
587
  enabled = true;
@@ -560,7 +603,7 @@ class TelemetryConfig {
560
603
  }
561
604
  function generateInstallationId(storePath) {
562
605
  try {
563
- const dataDir = storePath || envPaths("docs-mcp-server", { suffix: "" }).data;
606
+ const dataDir = resolveStorePath(storePath);
564
607
  const installationIdPath = path.join(dataDir, "installation.id");
565
608
  if (fs.existsSync(installationIdPath)) {
566
609
  const existingId = fs.readFileSync(installationIdPath, "utf8").trim();
@@ -709,7 +752,7 @@ function extractProtocol(urlOrPath) {
709
752
  }
710
753
  }
711
754
  const name = "@arabold/docs-mcp-server";
712
- const version = "1.25.1";
755
+ const version = "1.25.2";
713
756
  const description = "MCP server for fetching and searching documentation";
714
757
  const type = "module";
715
758
  const bin = { "docs-mcp-server": "dist/index.js" };
@@ -1582,26 +1625,6 @@ class MimeTypeUtils {
1582
1625
  return mimeToLanguage[mimeType] || "";
1583
1626
  }
1584
1627
  }
1585
- let projectRoot = null;
1586
- function getProjectRoot() {
1587
- if (projectRoot) {
1588
- return projectRoot;
1589
- }
1590
- const currentFilePath = fileURLToPath(import.meta.url);
1591
- let currentDir = path.dirname(currentFilePath);
1592
- while (true) {
1593
- const packageJsonPath = path.join(currentDir, "package.json");
1594
- if (fs.existsSync(packageJsonPath)) {
1595
- projectRoot = currentDir;
1596
- return projectRoot;
1597
- }
1598
- const parentDir = path.dirname(currentDir);
1599
- if (parentDir === currentDir) {
1600
- throw new Error("Could not find project root containing package.json.");
1601
- }
1602
- currentDir = parentDir;
1603
- }
1604
- }
1605
1628
  const fullTrim = (str) => {
1606
1629
  return str.replace(/^[\s\r\n\t]+|[\s\r\n\t]+$/g, "");
1607
1630
  };
@@ -7390,14 +7413,6 @@ function parseHeaders(headerOptions) {
7390
7413
  }
7391
7414
  return headers;
7392
7415
  }
7393
- const CLI_DEFAULTS = {
7394
- PROTOCOL: DEFAULT_PROTOCOL,
7395
- HTTP_PORT: DEFAULT_HTTP_PORT,
7396
- WEB_PORT: DEFAULT_WEB_PORT,
7397
- HOST: DEFAULT_HOST,
7398
- MAX_CONCURRENCY: DEFAULT_MAX_CONCURRENCY,
7399
- TELEMETRY: true
7400
- };
7401
7416
  function parseAuthConfig(options) {
7402
7417
  if (!options.authEnabled) {
7403
7418
  return void 0;
@@ -12540,34 +12555,10 @@ class DocumentManagementService {
12540
12555
  normalizeVersion(version2) {
12541
12556
  return (version2 ?? "").toLowerCase();
12542
12557
  }
12543
- constructor(embeddingConfig, pipelineConfig, storePath) {
12544
- let dbPath;
12545
- let dbDir;
12546
- if (storePath) {
12547
- dbDir = storePath;
12548
- dbPath = path.join(dbDir, "documents.db");
12549
- logger.debug(`Using database directory from storePath parameter: ${dbDir}`);
12550
- } else {
12551
- const projectRoot2 = getProjectRoot();
12552
- const oldDbDir = path.join(projectRoot2, ".store");
12553
- const oldDbPath = path.join(oldDbDir, "documents.db");
12554
- const oldDbExists = fs.existsSync(oldDbPath);
12555
- if (oldDbExists) {
12556
- dbPath = oldDbPath;
12557
- dbDir = oldDbDir;
12558
- logger.debug(`Using legacy database path: ${dbPath}`);
12559
- } else {
12560
- const standardPaths = envPaths("docs-mcp-server", { suffix: "" });
12561
- dbDir = standardPaths.data;
12562
- dbPath = path.join(dbDir, "documents.db");
12563
- logger.debug(`Using standard database directory: ${dbDir}`);
12564
- }
12565
- }
12566
- try {
12567
- fs.mkdirSync(dbDir, { recursive: true });
12568
- } catch (error) {
12569
- logger.error(`⚠️ Failed to create database directory ${dbDir}: ${error}`);
12570
- }
12558
+ constructor(storePath, embeddingConfig, pipelineConfig) {
12559
+ const dbDir = storePath;
12560
+ const dbPath = path.join(dbDir, "documents.db");
12561
+ logger.debug(`Using database directory: ${dbDir}`);
12571
12562
  this.store = new DocumentStore(dbPath, embeddingConfig);
12572
12563
  this.documentRetriever = new DocumentRetrieverService(this.store);
12573
12564
  this.pipelines = PipelineFactory$1.createStandardPipelines(pipelineConfig);
@@ -12922,16 +12913,19 @@ async function createDocumentManagement(options = {}) {
12922
12913
  await client.initialize();
12923
12914
  return client;
12924
12915
  }
12916
+ if (!options.storePath) {
12917
+ throw new Error("storePath is required when not using a remote server");
12918
+ }
12925
12919
  const service = new DocumentManagementService(
12920
+ options.storePath,
12926
12921
  options.embeddingConfig,
12927
- void 0,
12928
- options.storePath
12922
+ void 0
12929
12923
  );
12930
12924
  await service.initialize();
12931
12925
  return service;
12932
12926
  }
12933
- async function createLocalDocumentManagement(embeddingConfig, storePath) {
12934
- const service = new DocumentManagementService(embeddingConfig, void 0, storePath);
12927
+ async function createLocalDocumentManagement(storePath, embeddingConfig) {
12928
+ const service = new DocumentManagementService(storePath, embeddingConfig, void 0);
12935
12929
  await service.initialize();
12936
12930
  return service;
12937
12931
  }
@@ -12939,7 +12933,7 @@ function createDefaultAction(program) {
12939
12933
  return program.addOption(
12940
12934
  new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default("auto").choices(["auto", "stdio", "http"])
12941
12935
  ).addOption(
12942
- new Option("--port <number>", "Port for the server").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.HTTP_PORT.toString()).argParser((v) => {
12936
+ new Option("--port <number>", "Port for the server").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_HTTP_PORT.toString()).argParser((v) => {
12943
12937
  const n = Number(v);
12944
12938
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
12945
12939
  throw new Error("Port must be an integer between 1 and 65535");
@@ -12947,7 +12941,7 @@ function createDefaultAction(program) {
12947
12941
  return String(n);
12948
12942
  })
12949
12943
  ).addOption(
12950
- new Option("--host <host>", "Host to bind the server to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
12944
+ new Option("--host <host>", "Host to bind the server to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
12951
12945
  ).addOption(
12952
12946
  new Option(
12953
12947
  "--embedding-model <model>",
@@ -13004,12 +12998,12 @@ function createDefaultAction(program) {
13004
12998
  validateAuthConfig(authConfig);
13005
12999
  warnHttpUsage(authConfig, port);
13006
13000
  }
13007
- const globalOptions = program.parent?.opts() || {};
13001
+ const globalOptions = program.opts();
13008
13002
  ensurePlaywrightBrowsersInstalled();
13009
13003
  const embeddingConfig = resolveEmbeddingContext(options.embeddingModel);
13010
13004
  const docService = await createLocalDocumentManagement(
13011
- embeddingConfig,
13012
- globalOptions.storePath
13005
+ globalOptions.storePath,
13006
+ embeddingConfig
13013
13007
  );
13014
13008
  const pipelineOptions = {
13015
13009
  recoverJobs: options.resume || false,
@@ -13160,9 +13154,9 @@ function createListCommand(program) {
13160
13154
  }
13161
13155
  function createMcpCommand(program) {
13162
13156
  return program.command("mcp").description("Start MCP server only").addOption(
13163
- new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default(CLI_DEFAULTS.PROTOCOL).choices(["auto", "stdio", "http"])
13157
+ new Option("--protocol <protocol>", "Protocol for MCP server").env("DOCS_MCP_PROTOCOL").default(DEFAULT_PROTOCOL).choices(["auto", "stdio", "http"])
13164
13158
  ).addOption(
13165
- new Option("--port <number>", "Port for the MCP server").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.HTTP_PORT.toString()).argParser((v) => {
13159
+ new Option("--port <number>", "Port for the MCP server").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_HTTP_PORT.toString()).argParser((v) => {
13166
13160
  const n = Number(v);
13167
13161
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
13168
13162
  throw new Error("Port must be an integer between 1 and 65535");
@@ -13170,7 +13164,7 @@ function createMcpCommand(program) {
13170
13164
  return String(n);
13171
13165
  })
13172
13166
  ).addOption(
13173
- new Option("--host <host>", "Host to bind the MCP server to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
13167
+ new Option("--host <host>", "Host to bind the MCP server to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13174
13168
  ).addOption(
13175
13169
  new Option(
13176
13170
  "--embedding-model <model>",
@@ -13530,7 +13524,7 @@ function createSearchCommand(program) {
13530
13524
  }
13531
13525
  function createWebCommand(program) {
13532
13526
  return program.command("web").description("Start web interface only").addOption(
13533
- new Option("--port <number>", "Port for the web interface").env("DOCS_MCP_WEB_PORT").env("DOCS_MCP_PORT").env("PORT").default(CLI_DEFAULTS.WEB_PORT.toString()).argParser((v) => {
13527
+ new Option("--port <number>", "Port for the web interface").env("DOCS_MCP_WEB_PORT").env("DOCS_MCP_PORT").env("PORT").default(DEFAULT_WEB_PORT.toString()).argParser((v) => {
13534
13528
  const n = Number(v);
13535
13529
  if (!Number.isInteger(n) || n < 1 || n > 65535) {
13536
13530
  throw new Error("Port must be an integer between 1 and 65535");
@@ -13538,7 +13532,7 @@ function createWebCommand(program) {
13538
13532
  return String(n);
13539
13533
  })
13540
13534
  ).addOption(
13541
- new Option("--host <host>", "Host to bind the web interface to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
13535
+ new Option("--host <host>", "Host to bind the web interface to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13542
13536
  ).addOption(
13543
13537
  new Option(
13544
13538
  "--embedding-model <model>",
@@ -13620,7 +13614,7 @@ function createWorkerCommand(program) {
13620
13614
  return String(n);
13621
13615
  })
13622
13616
  ).addOption(
13623
- new Option("--host <host>", "Host to bind the worker API to").env("DOCS_MCP_HOST").env("HOST").default(CLI_DEFAULTS.HOST).argParser(validateHost)
13617
+ new Option("--host <host>", "Host to bind the worker API to").env("DOCS_MCP_HOST").env("HOST").default(DEFAULT_HOST).argParser(validateHost)
13624
13618
  ).addOption(
13625
13619
  new Option(
13626
13620
  "--embedding-model <model>",
@@ -13640,11 +13634,15 @@ function createWorkerCommand(program) {
13640
13634
  logger.info(`🚀 Starting external pipeline worker on port ${port}`);
13641
13635
  ensurePlaywrightBrowsersInstalled();
13642
13636
  const embeddingConfig = resolveEmbeddingContext(cmdOptions.embeddingModel);
13643
- const docService = await createLocalDocumentManagement(embeddingConfig);
13637
+ const globalOptions = program.parent?.opts() || {};
13638
+ const docService = await createLocalDocumentManagement(
13639
+ globalOptions.storePath,
13640
+ embeddingConfig
13641
+ );
13644
13642
  const pipelineOptions = {
13645
13643
  recoverJobs: cmdOptions.resume,
13646
13644
  // Use the resume option
13647
- concurrency: CLI_DEFAULTS.MAX_CONCURRENCY
13645
+ concurrency: DEFAULT_MAX_CONCURRENCY
13648
13646
  };
13649
13647
  const pipeline = await createPipelineWithCallbacks(docService, pipelineOptions);
13650
13648
  const config = createAppServerConfig({
@@ -13692,10 +13690,12 @@ function createCliProgram() {
13692
13690
  ).enablePositionalOptions().allowExcessArguments(false).showHelpAfterError(true);
13693
13691
  program.hook("preAction", async (thisCommand, actionCommand) => {
13694
13692
  const globalOptions = thisCommand.opts();
13693
+ const resolvedStorePath = resolveStorePath(globalOptions.storePath);
13694
+ globalOptions.storePath = resolvedStorePath;
13695
13695
  setupLogging(globalOptions);
13696
13696
  initTelemetry({
13697
13697
  enabled: globalOptions.telemetry ?? true,
13698
- storePath: globalOptions.storePath
13698
+ storePath: resolvedStorePath
13699
13699
  });
13700
13700
  if (shouldEnableTelemetry()) {
13701
13701
  if (analytics.isEnabled()) {