@arabold/docs-mcp-server 1.18.0 → 1.20.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 CHANGED
@@ -96,7 +96,7 @@ Add this to your MCP settings (VS Code, Claude Desktop, etc.):
96
96
 
97
97
  **Alternative connection types:**
98
98
 
99
- ```json
99
+ ```jsonc
100
100
  // SSE (Server-Sent Events)
101
101
  "type": "sse", "url": "http://localhost:6280/sse"
102
102
 
@@ -122,7 +122,7 @@ OPENAI_API_KEY="your-key" npx @arabold/docs-mcp-server@latest list
122
122
  OPENAI_API_KEY="your-key" npx @arabold/docs-mcp-server@latest search react "useState hook"
123
123
 
124
124
  # Scrape new documentation (connects to running server's worker)
125
- OPENAI_API_KEY="your-key" npx @arabold/docs-mcp-server@latest scrape react https://react.dev/reference/react --server-url http://localhost:6280/api
125
+ npx @arabold/docs-mcp-server@latest scrape react https://react.dev/reference/react --server-url http://localhost:6280/api
126
126
  ```
127
127
 
128
128
  ### Adding Library Documentation
@@ -248,9 +248,7 @@ See the tooltips in the Web UI and CLI help for more details.
248
248
 
249
249
  ## Advanced: Docker Compose (Scaling)
250
250
 
251
- For production deployments or when you need to scale processing, use Docker Compose to run separate services.
252
-
253
- > **Note:** This feature is work in progress and will still be improved in future releases. Currently, it still requires all services to share the same database volume, defeating its original purpose.
251
+ For production deployments or when you need to scale processing, use Docker Compose to run separate services. The system selects a local in-process worker or a remote worker client based on configuration, keeping behavior consistent across modes.
254
252
 
255
253
  **Start the services:**
256
254
 
@@ -334,7 +332,7 @@ See [examples above](#alternative-using-docker) for usage.
334
332
  Set `DOCS_MCP_EMBEDDING_MODEL` to one of:
335
333
 
336
334
  - `text-embedding-3-small` (default, OpenAI)
337
- - `openai:llama2` (OpenAI-compatible, Ollama)
335
+ - `openai:snowflake-arctic-embed2` (OpenAI-compatible, Ollama)
338
336
  - `vertex:text-embedding-004` (Google Vertex AI)
339
337
  - `gemini:embedding-001` (Google Gemini)
340
338
  - `aws:amazon.titan-embed-text-v1` (AWS Bedrock)
@@ -0,0 +1,62 @@
1
+ -- Migration 007: Deduplicate unversioned versions
2
+ -- Goal: collapse multiple NULL-name version rows per library to a single canonical row
3
+ -- Steps:
4
+ -- 1. For each library, choose canonical NULL-name row:
5
+ -- a) Prefer a row referenced by any documents (highest document count)
6
+ -- b) Fallback to lowest id
7
+ -- 2. Repoint any documents referencing non-canonical NULL rows to canonical
8
+ -- 3. Delete surplus NULL-name rows with zero documents
9
+ -- 4. Convert remaining NULL names to empty string '' for future uniqueness enforcement
10
+ -- Safe to run multiple times (idempotent)
11
+
12
+ -- 1 & 2: Repoint documents
13
+ -- Use TEMP tables instead of CTEs because we need the canonical mapping
14
+ -- across multiple subsequent statements. All TEMP objects are connection-scoped
15
+ -- and vanish automatically; safe for repeated runs (we DROP IF EXISTS first).
16
+
17
+ DROP TABLE IF EXISTS temp_null_versions;
18
+ CREATE TEMP TABLE temp_null_versions AS
19
+ SELECT v.id, v.library_id,
20
+ (SELECT COUNT(*) FROM documents d WHERE d.version_id = v.id) AS doc_count
21
+ FROM versions v
22
+ WHERE v.name IS NULL;
23
+
24
+ -- Build canonical mapping per library (one row per library_id)
25
+ DROP TABLE IF EXISTS temp_canonical_versions;
26
+ CREATE TEMP TABLE temp_canonical_versions AS
27
+ SELECT nv.library_id,
28
+ COALESCE(
29
+ (
30
+ SELECT id FROM temp_null_versions nv2
31
+ WHERE nv2.library_id = nv.library_id AND nv2.doc_count > 0
32
+ ORDER BY nv2.doc_count DESC, nv2.id ASC LIMIT 1
33
+ ),
34
+ (
35
+ SELECT id FROM temp_null_versions nv3
36
+ WHERE nv3.library_id = nv.library_id
37
+ ORDER BY nv3.id ASC LIMIT 1
38
+ )
39
+ ) AS keep_id
40
+ FROM temp_null_versions nv
41
+ GROUP BY nv.library_id;
42
+
43
+ -- Repoint documents from non-canonical NULL-name versions
44
+ UPDATE documents
45
+ SET version_id = (
46
+ SELECT keep_id FROM temp_canonical_versions c
47
+ WHERE c.library_id = documents.library_id
48
+ )
49
+ WHERE version_id IN (SELECT id FROM versions WHERE name IS NULL)
50
+ AND version_id NOT IN (SELECT keep_id FROM temp_canonical_versions);
51
+
52
+ -- 3: Delete surplus NULL-name rows now unreferenced
53
+ DELETE FROM versions
54
+ WHERE name IS NULL
55
+ AND id NOT IN (SELECT keep_id FROM temp_canonical_versions)
56
+ AND (SELECT COUNT(*) FROM documents d WHERE d.version_id = versions.id) = 0;
57
+
58
+ -- 4: Normalize remaining NULL names to ''
59
+ UPDATE versions SET name = '' WHERE name IS NULL;
60
+
61
+ -- (Optional) Unique index already exists if schema defined; if not, we could add:
62
+ -- CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_library_name ON versions(library_id, name);
@@ -0,0 +1,10 @@
1
+ -- We only need to normalize existing strings to lower-case and add expression unique indexes
2
+ -- for defense-in-depth. Idempotent: LOWER(name) is stable on re-run.
3
+
4
+ UPDATE libraries SET name = LOWER(name);
5
+ UPDATE versions SET name = LOWER(name) WHERE name IS NOT NULL AND name <> '';
6
+
7
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_libraries_lower_name ON libraries(LOWER(name));
8
+ CREATE UNIQUE INDEX IF NOT EXISTS idx_versions_library_lower_name ON versions(library_id, LOWER(name));
9
+
10
+ -- Existing UNIQUE(library_id, name) plus these expression indexes enforce case-insensitive uniqueness.
@@ -0,0 +1,57 @@
1
+ import { createTRPCProxyClient, httpBatchLink } from "@trpc/client";
2
+ import { l as logger } from "./index.js";
3
+ class DocumentManagementClient {
4
+ baseUrl;
5
+ client;
6
+ constructor(serverUrl) {
7
+ this.baseUrl = serverUrl.replace(/\/$/, "");
8
+ this.client = createTRPCProxyClient({
9
+ links: [httpBatchLink({ url: this.baseUrl })]
10
+ });
11
+ logger.debug(`DocumentManagementClient (tRPC) created for: ${this.baseUrl}`);
12
+ }
13
+ async initialize() {
14
+ await this.client.ping.query();
15
+ }
16
+ async shutdown() {
17
+ }
18
+ async listLibraries() {
19
+ return this.client.listLibraries.query();
20
+ }
21
+ async validateLibraryExists(library) {
22
+ await this.client.validateLibraryExists.mutate({ library });
23
+ }
24
+ async findBestVersion(library, targetVersion) {
25
+ return this.client.findBestVersion.query({ library, targetVersion });
26
+ }
27
+ async searchStore(library, version, query, limit) {
28
+ return this.client.search.query({ library, version: version ?? null, query, limit });
29
+ }
30
+ async removeAllDocuments(library, version) {
31
+ await this.client.removeAllDocuments.mutate({ library, version: version ?? null });
32
+ }
33
+ async getVersionsByStatus(statuses) {
34
+ return this.client.getVersionsByStatus.query({
35
+ statuses
36
+ });
37
+ }
38
+ async findVersionsBySourceUrl(url) {
39
+ return this.client.findVersionsBySourceUrl.query({ url });
40
+ }
41
+ async getScraperOptions(versionId) {
42
+ return this.client.getScraperOptions.query({ versionId });
43
+ }
44
+ async updateVersionStatus(versionId, status, errorMessage) {
45
+ await this.client.updateVersionStatus.mutate({ versionId, status, errorMessage });
46
+ }
47
+ async updateVersionProgress(versionId, pages, maxPages) {
48
+ await this.client.updateVersionProgress.mutate({ versionId, pages, maxPages });
49
+ }
50
+ async storeScraperOptions(versionId, options) {
51
+ await this.client.storeScraperOptions.mutate({ versionId, options });
52
+ }
53
+ }
54
+ export {
55
+ DocumentManagementClient
56
+ };
57
+ //# sourceMappingURL=DocumentManagementClient-CAFdDwTu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DocumentManagementClient-CAFdDwTu.js","sources":["../src/store/DocumentManagementClient.ts"],"sourcesContent":["/**\n * tRPC client for the document management API.\n * Implements IDocumentManagement and delegates to /api data router.\n */\nimport { createTRPCProxyClient, httpBatchLink } from \"@trpc/client\";\nimport type { ScraperOptions } from \"../scraper/types\";\nimport { logger } from \"../utils/logger\";\nimport type { IDocumentManagement } from \"./trpc/interfaces\";\nimport type { DataRouter } from \"./trpc/router\";\nimport type {\n DbVersionWithLibrary,\n FindVersionResult,\n LibrarySummary,\n StoredScraperOptions,\n StoreSearchResult,\n VersionStatus,\n} from \"./types\";\n\nexport class DocumentManagementClient implements IDocumentManagement {\n private readonly baseUrl: string;\n private readonly client: ReturnType<typeof createTRPCProxyClient<DataRouter>>;\n\n constructor(serverUrl: string) {\n this.baseUrl = serverUrl.replace(/\\/$/, \"\");\n this.client = createTRPCProxyClient<DataRouter>({\n links: [httpBatchLink({ url: this.baseUrl })],\n });\n logger.debug(`DocumentManagementClient (tRPC) created for: ${this.baseUrl}`);\n }\n\n async initialize(): Promise<void> {\n // Connectivity check\n await (\n this.client as unknown as { ping: { query: () => Promise<unknown> } }\n ).ping.query();\n }\n\n async shutdown(): Promise<void> {\n // no-op for HTTP client\n }\n\n async listLibraries(): Promise<LibrarySummary[]> {\n return this.client.listLibraries.query();\n }\n\n async validateLibraryExists(library: string): Promise<void> {\n await this.client.validateLibraryExists.mutate({ library });\n }\n\n async findBestVersion(\n library: string,\n targetVersion?: string,\n ): Promise<FindVersionResult> {\n return this.client.findBestVersion.query({ library, targetVersion });\n }\n\n async searchStore(\n library: string,\n version: string | null | undefined,\n query: string,\n limit?: number,\n ): Promise<StoreSearchResult[]> {\n return this.client.search.query({ library, version: version ?? null, query, limit });\n }\n\n async removeAllDocuments(library: string, version?: string | null): Promise<void> {\n await this.client.removeAllDocuments.mutate({ library, version: version ?? null });\n }\n\n async getVersionsByStatus(statuses: VersionStatus[]): Promise<DbVersionWithLibrary[]> {\n return this.client.getVersionsByStatus.query({\n statuses: statuses as unknown as string[],\n });\n }\n\n async findVersionsBySourceUrl(url: string): Promise<DbVersionWithLibrary[]> {\n return this.client.findVersionsBySourceUrl.query({ url });\n }\n\n async getScraperOptions(versionId: number): Promise<StoredScraperOptions | null> {\n return this.client.getScraperOptions.query({ versionId });\n }\n\n async updateVersionStatus(\n versionId: number,\n status: VersionStatus,\n errorMessage?: string,\n ): Promise<void> {\n await this.client.updateVersionStatus.mutate({ versionId, status, errorMessage });\n }\n\n async updateVersionProgress(\n versionId: number,\n pages: number,\n maxPages: number,\n ): Promise<void> {\n await this.client.updateVersionProgress.mutate({ versionId, pages, maxPages });\n }\n\n async storeScraperOptions(versionId: number, options: ScraperOptions): Promise<void> {\n await this.client.storeScraperOptions.mutate({ versionId, options });\n }\n}\n"],"names":[],"mappings":";;AAkBO,MAAM,yBAAwD;AAAA,EAClD;AAAA,EACA;AAAA,EAEjB,YAAY,WAAmB;AAC7B,SAAK,UAAU,UAAU,QAAQ,OAAO,EAAE;AAC1C,SAAK,SAAS,sBAAkC;AAAA,MAC9C,OAAO,CAAC,cAAc,EAAE,KAAK,KAAK,QAAA,CAAS,CAAC;AAAA,IAAA,CAC7C;AACD,WAAO,MAAM,gDAAgD,KAAK,OAAO,EAAE;AAAA,EAC7E;AAAA,EAEA,MAAM,aAA4B;AAEhC,UACE,KAAK,OACL,KAAK,MAAA;AAAA,EACT;AAAA,EAEA,MAAM,WAA0B;AAAA,EAEhC;AAAA,EAEA,MAAM,gBAA2C;AAC/C,WAAO,KAAK,OAAO,cAAc,MAAA;AAAA,EACnC;AAAA,EAEA,MAAM,sBAAsB,SAAgC;AAC1D,UAAM,KAAK,OAAO,sBAAsB,OAAO,EAAE,SAAS;AAAA,EAC5D;AAAA,EAEA,MAAM,gBACJ,SACA,eAC4B;AAC5B,WAAO,KAAK,OAAO,gBAAgB,MAAM,EAAE,SAAS,eAAe;AAAA,EACrE;AAAA,EAEA,MAAM,YACJ,SACA,SACA,OACA,OAC8B;AAC9B,WAAO,KAAK,OAAO,OAAO,MAAM,EAAE,SAAS,SAAS,WAAW,MAAM,OAAO,MAAA,CAAO;AAAA,EACrF;AAAA,EAEA,MAAM,mBAAmB,SAAiB,SAAwC;AAChF,UAAM,KAAK,OAAO,mBAAmB,OAAO,EAAE,SAAS,SAAS,WAAW,MAAM;AAAA,EACnF;AAAA,EAEA,MAAM,oBAAoB,UAA4D;AACpF,WAAO,KAAK,OAAO,oBAAoB,MAAM;AAAA,MAC3C;AAAA,IAAA,CACD;AAAA,EACH;AAAA,EAEA,MAAM,wBAAwB,KAA8C;AAC1E,WAAO,KAAK,OAAO,wBAAwB,MAAM,EAAE,KAAK;AAAA,EAC1D;AAAA,EAEA,MAAM,kBAAkB,WAAyD;AAC/E,WAAO,KAAK,OAAO,kBAAkB,MAAM,EAAE,WAAW;AAAA,EAC1D;AAAA,EAEA,MAAM,oBACJ,WACA,QACA,cACe;AACf,UAAM,KAAK,OAAO,oBAAoB,OAAO,EAAE,WAAW,QAAQ,cAAc;AAAA,EAClF;AAAA,EAEA,MAAM,sBACJ,WACA,OACA,UACe;AACf,UAAM,KAAK,OAAO,sBAAsB,OAAO,EAAE,WAAW,OAAO,UAAU;AAAA,EAC/E;AAAA,EAEA,MAAM,oBAAoB,WAAmB,SAAwC;AACnF,UAAM,KAAK,OAAO,oBAAoB,OAAO,EAAE,WAAW,SAAS;AAAA,EACrE;AACF;"}