@arabold/docs-mcp-server 1.18.0 → 1.19.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 +4 -6
- package/db/migrations/007-dedupe-unversioned-versions.sql +62 -0
- package/db/migrations/008-case-insensitive-names.sql +10 -0
- package/dist/DocumentManagementClient-CAFdDwTu.js +57 -0
- package/dist/DocumentManagementClient-CAFdDwTu.js.map +1 -0
- package/dist/DocumentManagementService-BH02TJEe.js +1917 -0
- package/dist/DocumentManagementService-BH02TJEe.js.map +1 -0
- package/dist/index.js +736 -2429
- package/dist/index.js.map +1 -1
- package/package.json +3 -1
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
|
-
```
|
|
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
|
-
|
|
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:
|
|
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;"}
|