simple-dynamsoft-mcp 6.3.0 → 7.0.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/.env.example +35 -9
- package/README.md +156 -497
- package/package.json +13 -7
- package/scripts/prebuild-rag-index.mjs +1 -1
- package/scripts/run-gemini-tests.mjs +1 -1
- package/scripts/sync-submodules.mjs +1 -1
- package/scripts/verify-doc-resources.mjs +79 -0
- package/src/data/bootstrap.js +475 -0
- package/src/data/download-utils.js +99 -0
- package/src/data/hydration-mode.js +15 -0
- package/src/data/hydration-policy.js +39 -0
- package/src/data/repo-map.js +149 -0
- package/src/{data-root.js → data/root.js} +1 -1
- package/src/{submodule-sync.js → data/submodule-sync.js} +1 -1
- package/src/index.js +49 -1499
- package/src/observability/logging.js +51 -0
- package/src/rag/config.js +96 -0
- package/src/rag/index.js +266 -0
- package/src/rag/lexical-provider.js +170 -0
- package/src/rag/logger.js +46 -0
- package/src/rag/profile-config.js +48 -0
- package/src/rag/providers.js +585 -0
- package/src/rag/search-utils.js +166 -0
- package/src/rag/vector-cache.js +323 -0
- package/src/server/create-server.js +168 -0
- package/src/server/helpers/server-helpers.js +33 -0
- package/src/{resource-index → server/resource-index}/paths.js +2 -2
- package/src/{resource-index → server/resource-index}/samples.js +9 -1
- package/src/{resource-index.js → server/resource-index.js} +158 -93
- package/src/server/resources/register-resources.js +56 -0
- package/src/server/runtime-config.js +66 -0
- package/src/server/tools/register-index-tools.js +130 -0
- package/src/server/tools/register-project-tools.js +305 -0
- package/src/server/tools/register-quickstart-tools.js +572 -0
- package/src/server/tools/register-sample-tools.js +333 -0
- package/src/server/tools/register-version-tools.js +136 -0
- package/src/server/transports/http.js +84 -0
- package/src/server/transports/stdio.js +12 -0
- package/src/data-bootstrap.js +0 -255
- package/src/rag.js +0 -1203
- /package/src/{gemini-retry.js → rag/gemini-retry.js} +0 -0
- /package/src/{normalizers.js → server/normalizers.js} +0 -0
- /package/src/{resource-index → server/resource-index}/builders.js +0 -0
- /package/src/{resource-index → server/resource-index}/config.js +0 -0
- /package/src/{resource-index → server/resource-index}/docs-loader.js +0 -0
- /package/src/{resource-index → server/resource-index}/uri.js +0 -0
- /package/src/{resource-index → server/resource-index}/version-policy.js +0 -0
package/src/data-bootstrap.js
DELETED
|
@@ -1,255 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import {
|
|
3
|
-
existsSync,
|
|
4
|
-
mkdirSync,
|
|
5
|
-
readdirSync,
|
|
6
|
-
readFileSync,
|
|
7
|
-
renameSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
writeFileSync
|
|
10
|
-
} from "node:fs";
|
|
11
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
12
|
-
import { homedir, tmpdir } from "node:os";
|
|
13
|
-
import extractZip from "extract-zip";
|
|
14
|
-
import { bundledDataRoot } from "./data-root.js";
|
|
15
|
-
|
|
16
|
-
const manifestPath = join(bundledDataRoot, "metadata", "data-manifest.json");
|
|
17
|
-
const sdkRegistryPath = join(bundledDataRoot, "metadata", "dynamsoft_sdks.json");
|
|
18
|
-
|
|
19
|
-
function logData(message) {
|
|
20
|
-
console.error(`[data] ${message}`);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function readBoolEnv(key, fallback = false) {
|
|
24
|
-
const value = process.env[key];
|
|
25
|
-
if (value === undefined || value === "") return fallback;
|
|
26
|
-
return ["1", "true", "yes", "on"].includes(String(value).toLowerCase());
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function readIntEnv(key, fallback) {
|
|
30
|
-
const value = process.env[key];
|
|
31
|
-
if (value === undefined || value === "") return fallback;
|
|
32
|
-
const parsed = Number.parseInt(String(value), 10);
|
|
33
|
-
return Number.isNaN(parsed) ? fallback : parsed;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function readManifest(rootDir = bundledDataRoot) {
|
|
37
|
-
const path = join(rootDir, "metadata", "data-manifest.json");
|
|
38
|
-
if (!existsSync(path)) return null;
|
|
39
|
-
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
40
|
-
if (!parsed || !Array.isArray(parsed.repos)) return null;
|
|
41
|
-
return parsed;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
function isDirectoryReady(path) {
|
|
45
|
-
try {
|
|
46
|
-
if (!existsSync(path)) return false;
|
|
47
|
-
const entries = readdirSync(path, { withFileTypes: true });
|
|
48
|
-
// A non-initialized submodule usually has only a `.git` marker entry.
|
|
49
|
-
// Treat that as not ready so runtime bootstrap can download real content.
|
|
50
|
-
return entries.some((entry) => entry.name !== ".git");
|
|
51
|
-
} catch {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function isDataRootReady(rootDir) {
|
|
57
|
-
const registryPath = join(rootDir, "metadata", "dynamsoft_sdks.json");
|
|
58
|
-
if (!existsSync(registryPath)) return false;
|
|
59
|
-
|
|
60
|
-
const manifest = readManifest(rootDir) || readManifest(bundledDataRoot);
|
|
61
|
-
if (!manifest) return false;
|
|
62
|
-
|
|
63
|
-
for (const repo of manifest.repos) {
|
|
64
|
-
const target = join(rootDir, repo.path);
|
|
65
|
-
if (!isDirectoryReady(target)) return false;
|
|
66
|
-
}
|
|
67
|
-
return true;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function getManifestSignature(manifest) {
|
|
71
|
-
const payload = manifest.repos.map((repo) => `${repo.path}@${repo.commit}`).join("|");
|
|
72
|
-
return createHash("sha256").update(payload).digest("hex");
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function parseGithubSlug(repo) {
|
|
76
|
-
if (repo.owner && repo.name) {
|
|
77
|
-
return { owner: repo.owner, name: repo.name };
|
|
78
|
-
}
|
|
79
|
-
const url = String(repo.url || "");
|
|
80
|
-
const httpsMatch = url.match(/github\.com[/:]([^/]+)\/([^/.]+)(?:\.git)?$/i);
|
|
81
|
-
if (!httpsMatch) return null;
|
|
82
|
-
return { owner: httpsMatch[1], name: httpsMatch[2] };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
async function downloadFile(url, outputPath, timeoutMs) {
|
|
86
|
-
const startedAt = Date.now();
|
|
87
|
-
const controller = new AbortController();
|
|
88
|
-
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
89
|
-
try {
|
|
90
|
-
const response = await fetch(url, { signal: controller.signal });
|
|
91
|
-
if (!response.ok) {
|
|
92
|
-
throw new Error(`HTTP ${response.status} for ${url}`);
|
|
93
|
-
}
|
|
94
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
95
|
-
writeFileSync(outputPath, Buffer.from(arrayBuffer));
|
|
96
|
-
const elapsedMs = Date.now() - startedAt;
|
|
97
|
-
logData(`downloaded file=${basename(outputPath)} size=${arrayBuffer.byteLength}B elapsed_ms=${elapsedMs}`);
|
|
98
|
-
} finally {
|
|
99
|
-
clearTimeout(timer);
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
function copyBundledMetadata(targetRoot) {
|
|
104
|
-
const metadataDir = join(targetRoot, "metadata");
|
|
105
|
-
mkdirSync(metadataDir, { recursive: true });
|
|
106
|
-
writeFileSync(join(metadataDir, "dynamsoft_sdks.json"), readFileSync(sdkRegistryPath, "utf8"));
|
|
107
|
-
writeFileSync(join(metadataDir, "data-manifest.json"), readFileSync(manifestPath, "utf8"));
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
async function populateFromManifest(targetRoot, manifest, timeoutMs) {
|
|
111
|
-
const tempZipRoot = join(tmpdir(), `simple-dynamsoft-mcp-zips-${Date.now()}`);
|
|
112
|
-
mkdirSync(tempZipRoot, { recursive: true });
|
|
113
|
-
logData(`populate start repos=${manifest.repos.length} timeout_ms=${timeoutMs} staging=${targetRoot}`);
|
|
114
|
-
|
|
115
|
-
try {
|
|
116
|
-
for (const repo of manifest.repos) {
|
|
117
|
-
const slug = parseGithubSlug(repo);
|
|
118
|
-
if (!slug) {
|
|
119
|
-
throw new Error(`Unable to parse GitHub slug for ${repo.path}`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const archiveUrl = repo.archiveUrl || `https://codeload.github.com/${slug.owner}/${slug.name}/zip/${repo.commit}`;
|
|
123
|
-
const zipPath = join(tempZipRoot, `${repo.path.replace(/[\\/]/g, "_")}.zip`);
|
|
124
|
-
const extractRoot = join(tempZipRoot, `${repo.path.replace(/[\\/]/g, "_")}-extract`);
|
|
125
|
-
const targetPath = join(targetRoot, repo.path);
|
|
126
|
-
const targetParent = dirname(targetPath);
|
|
127
|
-
|
|
128
|
-
logData(`repo start path=${repo.path} commit=${String(repo.commit || "").slice(0, 12)} host=codeload.github.com`);
|
|
129
|
-
mkdirSync(targetParent, { recursive: true });
|
|
130
|
-
await downloadFile(archiveUrl, zipPath, timeoutMs);
|
|
131
|
-
await extractZip(zipPath, { dir: extractRoot });
|
|
132
|
-
|
|
133
|
-
let extractedFolder = join(extractRoot, `${slug.name}-${repo.commit}`);
|
|
134
|
-
if (!existsSync(extractedFolder)) {
|
|
135
|
-
const children = readdirSync(extractRoot, { withFileTypes: true })
|
|
136
|
-
.filter((entry) => entry.isDirectory())
|
|
137
|
-
.map((entry) => join(extractRoot, entry.name));
|
|
138
|
-
if (children.length === 1) {
|
|
139
|
-
extractedFolder = children[0];
|
|
140
|
-
} else {
|
|
141
|
-
const fallbackName = basename(extractRoot);
|
|
142
|
-
throw new Error(`Archive layout is not recognized for ${repo.path} (${fallbackName})`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (existsSync(targetPath)) rmSync(targetPath, { recursive: true, force: true });
|
|
147
|
-
renameSync(extractedFolder, targetPath);
|
|
148
|
-
logData(`repo ready path=${repo.path}`);
|
|
149
|
-
}
|
|
150
|
-
logData(`populate done repos=${manifest.repos.length}`);
|
|
151
|
-
} finally {
|
|
152
|
-
rmSync(tempZipRoot, { recursive: true, force: true });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
function finalizeCacheRoot(cacheRoot, stagingRoot, signature) {
|
|
157
|
-
const parent = dirname(cacheRoot);
|
|
158
|
-
mkdirSync(parent, { recursive: true });
|
|
159
|
-
|
|
160
|
-
if (existsSync(cacheRoot)) {
|
|
161
|
-
rmSync(cacheRoot, { recursive: true, force: true });
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
renameSync(stagingRoot, cacheRoot);
|
|
165
|
-
writeFileSync(join(cacheRoot, ".manifest-signature"), signature);
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
async function ensureDownloadedData(cacheRoot) {
|
|
169
|
-
const manifest = readManifest(bundledDataRoot);
|
|
170
|
-
if (!manifest) {
|
|
171
|
-
throw new Error(`Missing manifest at ${manifestPath}. Run npm run data:lock.`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const signature = getManifestSignature(manifest);
|
|
175
|
-
const signaturePath = join(cacheRoot, ".manifest-signature");
|
|
176
|
-
const refresh = readBoolEnv("MCP_DATA_REFRESH_ON_START", false);
|
|
177
|
-
logData(
|
|
178
|
-
`download plan repos=${manifest.repos.length} cache_root=${cacheRoot} refresh=${refresh} signature=${signature.slice(0, 12)}`
|
|
179
|
-
);
|
|
180
|
-
|
|
181
|
-
if (!refresh && existsSync(signaturePath)) {
|
|
182
|
-
const existingSignature = readFileSync(signaturePath, "utf8").trim();
|
|
183
|
-
if (existingSignature === signature && isDataRootReady(cacheRoot)) {
|
|
184
|
-
logData(`cache hit signature=${signature.slice(0, 12)} root=${cacheRoot}`);
|
|
185
|
-
return { downloaded: false };
|
|
186
|
-
}
|
|
187
|
-
logData(`cache refresh required reason=signature_or_readiness_mismatch root=${cacheRoot}`);
|
|
188
|
-
} else if (refresh) {
|
|
189
|
-
logData(`cache refresh forced by MCP_DATA_REFRESH_ON_START root=${cacheRoot}`);
|
|
190
|
-
} else {
|
|
191
|
-
logData(`cache miss signature_file=${signaturePath}`);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
const timeoutMs = readIntEnv("MCP_DATA_DOWNLOAD_TIMEOUT_MS", 180000);
|
|
195
|
-
const stagingRoot = join(dirname(cacheRoot), `.tmp-data-${Date.now()}`);
|
|
196
|
-
rmSync(stagingRoot, { recursive: true, force: true });
|
|
197
|
-
mkdirSync(stagingRoot, { recursive: true });
|
|
198
|
-
logData(`download start staging_root=${stagingRoot} timeout_ms=${timeoutMs}`);
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
copyBundledMetadata(stagingRoot);
|
|
202
|
-
await populateFromManifest(stagingRoot, manifest, timeoutMs);
|
|
203
|
-
finalizeCacheRoot(cacheRoot, stagingRoot, signature);
|
|
204
|
-
logData(`download complete root=${cacheRoot} repos=${manifest.repos.length}`);
|
|
205
|
-
return { downloaded: true };
|
|
206
|
-
} catch (error) {
|
|
207
|
-
rmSync(stagingRoot, { recursive: true, force: true });
|
|
208
|
-
logData(`download failed root=${cacheRoot} error=${error.message}`);
|
|
209
|
-
throw error;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
async function ensureDataReady() {
|
|
214
|
-
const explicitRoot = process.env.MCP_DATA_DIR ? resolve(process.env.MCP_DATA_DIR) : "";
|
|
215
|
-
logData(`resolve start explicit_root=${explicitRoot || "(none)"} bundled_root=${bundledDataRoot}`);
|
|
216
|
-
if (explicitRoot) {
|
|
217
|
-
if (!isDataRootReady(explicitRoot)) {
|
|
218
|
-
logData(`custom data root invalid root=${explicitRoot}`);
|
|
219
|
-
throw new Error(`MCP_DATA_DIR is set but data is incomplete: ${explicitRoot}`);
|
|
220
|
-
}
|
|
221
|
-
process.env.MCP_RESOLVED_DATA_DIR = explicitRoot;
|
|
222
|
-
logData(`resolve done mode=custom root=${explicitRoot}`);
|
|
223
|
-
return { dataRoot: explicitRoot, mode: "custom", downloaded: false };
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
if (isDataRootReady(bundledDataRoot)) {
|
|
227
|
-
process.env.MCP_RESOLVED_DATA_DIR = bundledDataRoot;
|
|
228
|
-
logData(`resolve done mode=bundled root=${bundledDataRoot}`);
|
|
229
|
-
return { dataRoot: bundledDataRoot, mode: "bundled", downloaded: false };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
const autoDownload = readBoolEnv("MCP_DATA_AUTO_DOWNLOAD", true);
|
|
233
|
-
logData(`bundled data not ready auto_download=${autoDownload}`);
|
|
234
|
-
if (!autoDownload) {
|
|
235
|
-
throw new Error(
|
|
236
|
-
"Bundled data is not available and MCP_DATA_AUTO_DOWNLOAD is disabled. " +
|
|
237
|
-
"Set MCP_DATA_AUTO_DOWNLOAD=true or provide MCP_DATA_DIR."
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
const defaultCacheRoot = join(process.env.LOCALAPPDATA || join(homedir(), ".cache"), "simple-dynamsoft-mcp", "data");
|
|
242
|
-
const cacheRoot = resolve(process.env.MCP_DATA_CACHE_DIR || defaultCacheRoot);
|
|
243
|
-
const result = await ensureDownloadedData(cacheRoot);
|
|
244
|
-
process.env.MCP_RESOLVED_DATA_DIR = cacheRoot;
|
|
245
|
-
logData(`resolve done mode=downloaded root=${cacheRoot} source=${result?.downloaded ? "fresh-download" : "cache"}`);
|
|
246
|
-
return {
|
|
247
|
-
dataRoot: cacheRoot,
|
|
248
|
-
mode: "downloaded",
|
|
249
|
-
downloaded: Boolean(result?.downloaded)
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
export {
|
|
254
|
-
ensureDataReady
|
|
255
|
-
};
|