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.
Files changed (47) hide show
  1. package/.env.example +35 -9
  2. package/README.md +156 -497
  3. package/package.json +13 -7
  4. package/scripts/prebuild-rag-index.mjs +1 -1
  5. package/scripts/run-gemini-tests.mjs +1 -1
  6. package/scripts/sync-submodules.mjs +1 -1
  7. package/scripts/verify-doc-resources.mjs +79 -0
  8. package/src/data/bootstrap.js +475 -0
  9. package/src/data/download-utils.js +99 -0
  10. package/src/data/hydration-mode.js +15 -0
  11. package/src/data/hydration-policy.js +39 -0
  12. package/src/data/repo-map.js +149 -0
  13. package/src/{data-root.js → data/root.js} +1 -1
  14. package/src/{submodule-sync.js → data/submodule-sync.js} +1 -1
  15. package/src/index.js +49 -1499
  16. package/src/observability/logging.js +51 -0
  17. package/src/rag/config.js +96 -0
  18. package/src/rag/index.js +266 -0
  19. package/src/rag/lexical-provider.js +170 -0
  20. package/src/rag/logger.js +46 -0
  21. package/src/rag/profile-config.js +48 -0
  22. package/src/rag/providers.js +585 -0
  23. package/src/rag/search-utils.js +166 -0
  24. package/src/rag/vector-cache.js +323 -0
  25. package/src/server/create-server.js +168 -0
  26. package/src/server/helpers/server-helpers.js +33 -0
  27. package/src/{resource-index → server/resource-index}/paths.js +2 -2
  28. package/src/{resource-index → server/resource-index}/samples.js +9 -1
  29. package/src/{resource-index.js → server/resource-index.js} +158 -93
  30. package/src/server/resources/register-resources.js +56 -0
  31. package/src/server/runtime-config.js +66 -0
  32. package/src/server/tools/register-index-tools.js +130 -0
  33. package/src/server/tools/register-project-tools.js +305 -0
  34. package/src/server/tools/register-quickstart-tools.js +572 -0
  35. package/src/server/tools/register-sample-tools.js +333 -0
  36. package/src/server/tools/register-version-tools.js +136 -0
  37. package/src/server/transports/http.js +84 -0
  38. package/src/server/transports/stdio.js +12 -0
  39. package/src/data-bootstrap.js +0 -255
  40. package/src/rag.js +0 -1203
  41. /package/src/{gemini-retry.js → rag/gemini-retry.js} +0 -0
  42. /package/src/{normalizers.js → server/normalizers.js} +0 -0
  43. /package/src/{resource-index → server/resource-index}/builders.js +0 -0
  44. /package/src/{resource-index → server/resource-index}/config.js +0 -0
  45. /package/src/{resource-index → server/resource-index}/docs-loader.js +0 -0
  46. /package/src/{resource-index → server/resource-index}/uri.js +0 -0
  47. /package/src/{resource-index → server/resource-index}/version-policy.js +0 -0
@@ -0,0 +1,305 @@
1
+ import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
2
+ import { dirname, join, relative } from "node:path";
3
+ import { z } from "zod";
4
+
5
+ export function registerProjectTools({
6
+ server,
7
+ ensureScopeHydrated,
8
+ ensureLatestMajor,
9
+ normalizeProduct,
10
+ normalizePlatform,
11
+ normalizeEdition,
12
+ normalizeApiLevel,
13
+ normalizeSampleName,
14
+ parseResourceUri,
15
+ parseSampleUri,
16
+ formatScopeLabel,
17
+ getSampleIdFromUri,
18
+ discoverDwtSamples,
19
+ getMobileSamplePath,
20
+ getWebSamplePath,
21
+ getDbrServerSamplePath,
22
+ getDcvMobileSamplePath,
23
+ getDcvServerSamplePath,
24
+ getDcvWebSamplePath,
25
+ getDwtSamplePath,
26
+ getDdvSamplePath,
27
+ getSampleSuggestions
28
+ }) {
29
+ server.registerTool(
30
+ "generate_project",
31
+ {
32
+ title: "Generate Project",
33
+ description: "Generate a project structure from a sample and return files inline (no zip/download).",
34
+ inputSchema: {
35
+ product: z.string().describe("Product: dcv, dbr, dwt, or ddv"),
36
+ edition: z.string().optional().describe("Edition: mobile, web, server/desktop"),
37
+ platform: z.string().optional().describe("Platform: android, ios, maui, react-native, flutter, js, python, cpp, java, dotnet, nodejs, angular, blazor, capacitor, electron, es6, native-ts, next, nuxt, pwa, react, requirejs, svelte, vue, webview"),
38
+ version: z.string().optional().describe("Version constraint"),
39
+ sample_id: z.string().optional().describe("Sample identifier (name or path)"),
40
+ resource_uri: z.string().optional().describe("Resource URI returned by search"),
41
+ api_level: z.string().optional().describe("API level: high-level or low-level (mobile only)")
42
+ }
43
+ },
44
+ async ({ product, edition, platform, version, sample_id, resource_uri, api_level }) => {
45
+ const normalizedProduct = normalizeProduct(product);
46
+ const normalizedPlatform = normalizePlatform(platform);
47
+ const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
48
+
49
+ await ensureScopeHydrated({
50
+ product: normalizedProduct,
51
+ edition: normalizedEdition,
52
+ platform: normalizedPlatform,
53
+ type: "sample"
54
+ });
55
+
56
+ const policy = ensureLatestMajor({
57
+ product: normalizedProduct,
58
+ version,
59
+ query: sample_id,
60
+ edition: normalizedEdition,
61
+ platform: normalizedPlatform
62
+ });
63
+
64
+ if (!policy.ok) {
65
+ return { isError: true, content: [{ type: "text", text: policy.message }] };
66
+ }
67
+
68
+ let sampleInfo = null;
69
+ if (resource_uri) {
70
+ const parsed = parseResourceUri(resource_uri);
71
+ if (!parsed) {
72
+ return {
73
+ isError: true,
74
+ content: [{
75
+ type: "text",
76
+ text: "resource_uri must be a sample://... URI. Use search or list_samples to get a valid sample URI."
77
+ }]
78
+ };
79
+ }
80
+ if (parsed.scheme !== "sample") {
81
+ return {
82
+ isError: true,
83
+ content: [{
84
+ type: "text",
85
+ text: "resource_uri must use the sample:// scheme. For doc:// URIs, use resources/read instead."
86
+ }]
87
+ };
88
+ }
89
+ sampleInfo = parseSampleUri(resource_uri);
90
+ if (!sampleInfo) {
91
+ return {
92
+ isError: true,
93
+ content: [{
94
+ type: "text",
95
+ text: "Invalid sample URI format. Use search or list_samples to obtain a valid sample:// URI."
96
+ }]
97
+ };
98
+ }
99
+ }
100
+
101
+ let samplePath = null;
102
+ let sampleLabel = "";
103
+ let sampleQuery = "";
104
+
105
+ if (sampleInfo) {
106
+ sampleLabel = sampleInfo.sampleName || resource_uri;
107
+ sampleQuery = sampleInfo.sampleName || sample_id || "";
108
+ if (sampleInfo.product === "dbr" && sampleInfo.edition === "mobile") {
109
+ samplePath = getMobileSamplePath(sampleInfo.platform, sampleInfo.level, sampleInfo.sampleName);
110
+ } else if (sampleInfo.product === "dbr" && sampleInfo.edition === "web") {
111
+ samplePath = getWebSamplePath(sampleInfo.category, sampleInfo.sampleName);
112
+ } else if (sampleInfo.product === "dbr" && (sampleInfo.edition === "python" || sampleInfo.edition === "server")) {
113
+ samplePath = getDbrServerSamplePath(sampleInfo.platform, sampleInfo.sampleName);
114
+ } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "mobile") {
115
+ samplePath = getDcvMobileSamplePath(sampleInfo.platform, sampleInfo.sampleName);
116
+ } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "server") {
117
+ samplePath = getDcvServerSamplePath(sampleInfo.platform, sampleInfo.sampleName);
118
+ } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "web") {
119
+ samplePath = getDcvWebSamplePath(sampleInfo.sampleName);
120
+ } else if (sampleInfo.product === "dwt") {
121
+ samplePath = getDwtSamplePath(sampleInfo.category, sampleInfo.sampleName);
122
+ } else if (sampleInfo.product === "ddv") {
123
+ samplePath = getDdvSamplePath(sampleInfo.sampleName);
124
+ }
125
+ } else if (sample_id) {
126
+ if (!normalizedProduct || !normalizedEdition) {
127
+ return {
128
+ isError: true,
129
+ content: [{
130
+ type: "text",
131
+ text: "Specify product/edition or provide resource_uri. Use list_samples or get_index to discover valid scopes."
132
+ }]
133
+ };
134
+ }
135
+
136
+ const level = normalizeApiLevel(api_level);
137
+ const sampleName = normalizeSampleName(sample_id);
138
+ sampleLabel = sampleName;
139
+ sampleQuery = sampleName;
140
+
141
+ if (normalizedProduct === "dbr" && normalizedEdition === "mobile") {
142
+ const targetPlatform = normalizedPlatform || "android";
143
+ const primaryPath = getMobileSamplePath(targetPlatform, level, sampleName);
144
+ const altLevel = level === "high-level" ? "low-level" : "high-level";
145
+ const alternatePath = getMobileSamplePath(targetPlatform, altLevel, sampleName);
146
+ samplePath = existsSync(primaryPath) ? primaryPath : (existsSync(alternatePath) ? alternatePath : null);
147
+ } else if (normalizedProduct === "dcv" && normalizedEdition === "mobile") {
148
+ const platformCandidates = normalizedPlatform
149
+ ? [normalizedPlatform]
150
+ : ["android", "ios", "react-native", "flutter", "maui", "spm"];
151
+ for (const platformCandidate of platformCandidates) {
152
+ const candidate = getDcvMobileSamplePath(platformCandidate, sampleName);
153
+ if (candidate && existsSync(candidate)) {
154
+ samplePath = candidate;
155
+ break;
156
+ }
157
+ }
158
+ } else if (normalizedProduct === "dcv" && normalizedEdition === "web") {
159
+ samplePath = getDcvWebSamplePath(sampleName);
160
+ } else if (normalizedProduct === "dcv" && normalizedEdition === "server") {
161
+ samplePath = getDcvServerSamplePath(normalizedPlatform || "python", sampleName);
162
+ } else if (normalizedProduct === "dbr" && normalizedEdition === "web") {
163
+ samplePath = getWebSamplePath(undefined, sampleName);
164
+ } else if (normalizedProduct === "dbr" && normalizedEdition === "server") {
165
+ samplePath = getDbrServerSamplePath(normalizedPlatform || "python", sampleName);
166
+ } else if (normalizedProduct === "dwt") {
167
+ const categories = discoverDwtSamples();
168
+ let foundCategory = "";
169
+ for (const [category, samples] of Object.entries(categories)) {
170
+ if (samples.includes(sampleName)) {
171
+ foundCategory = category;
172
+ break;
173
+ }
174
+ }
175
+ samplePath = foundCategory ? getDwtSamplePath(foundCategory, sampleName) : null;
176
+ } else if (normalizedProduct === "ddv") {
177
+ samplePath = getDdvSamplePath(sampleName);
178
+ }
179
+ } else {
180
+ return { isError: true, content: [{ type: "text", text: "Provide sample_id or resource_uri." }] };
181
+ }
182
+
183
+ if (!samplePath || !existsSync(samplePath)) {
184
+ const suggestions = await getSampleSuggestions({
185
+ query: sampleQuery,
186
+ product: normalizedProduct,
187
+ edition: normalizedEdition,
188
+ platform: normalizedPlatform,
189
+ limit: 5
190
+ });
191
+
192
+ const content = [{
193
+ type: "text",
194
+ text: [
195
+ `Sample not found for "${sampleLabel}".`,
196
+ suggestions.length ? "Related samples:" : "No related samples found. Try search or get_index."
197
+ ].join("\n")
198
+ }];
199
+
200
+ for (const entry of suggestions) {
201
+ const versionLabel = entry.version ? `v${entry.version}` : "n/a";
202
+ const scopeLabel = formatScopeLabel(entry);
203
+ const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
204
+ const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
205
+ content.push({
206
+ type: "resource_link",
207
+ uri: entry.uri,
208
+ name: entry.title,
209
+ description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel} - ${entry.summary}${sampleHint}`,
210
+ mimeType: entry.mimeType,
211
+ annotations: {
212
+ audience: ["assistant"],
213
+ priority: 0.6
214
+ }
215
+ });
216
+ }
217
+
218
+ if (suggestions.length) {
219
+ const plainLines = suggestions.map((entry, index) => {
220
+ const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
221
+ const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
222
+ return `- ${index + 1}. ${entry.uri}${sampleNote}`;
223
+ });
224
+ content.push({
225
+ type: "text",
226
+ text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
227
+ });
228
+ }
229
+
230
+ return { isError: true, content };
231
+ }
232
+
233
+ const textExtensions = [
234
+ ".java", ".kt", ".swift", ".m", ".h", ".xml", ".gradle", ".properties",
235
+ ".pro", ".json", ".plist", ".storyboard", ".xib", ".gitignore", ".md",
236
+ ".js", ".jsx", ".ts", ".tsx", ".vue", ".cjs", ".html", ".css"
237
+ ];
238
+
239
+ const files = [];
240
+ const stat = statSync(samplePath);
241
+ const rootDir = stat.isDirectory() ? samplePath : dirname(samplePath);
242
+
243
+ function addFile(fullPath) {
244
+ const ext = "." + fullPath.split(".").pop();
245
+ const baseName = fullPath.split(/[\\/]/).pop();
246
+ if (!textExtensions.includes(ext) && !["gradlew", "Podfile"].includes(baseName)) {
247
+ return;
248
+ }
249
+ try {
250
+ const content = readFileSync(fullPath, "utf-8");
251
+ const normalized = content.replace(/\r\n/g, "\n");
252
+ files.push({
253
+ path: relative(rootDir, fullPath),
254
+ content: normalized,
255
+ ext: ext.replace(".", "")
256
+ });
257
+ } catch (e) {
258
+ // Ignore binary or unreadable files
259
+ }
260
+ }
261
+
262
+ function walk(dir) {
263
+ const entries = readdirSync(dir);
264
+ for (const entry of entries) {
265
+ if (["build", ".gradle", ".idea", ".git", "node_modules", "Pods", "DerivedData", "__pycache__"].includes(entry)) {
266
+ continue;
267
+ }
268
+ const fullPath = join(dir, entry);
269
+ const entryStat = statSync(fullPath);
270
+ if (entryStat.isDirectory()) {
271
+ walk(fullPath);
272
+ } else {
273
+ addFile(fullPath);
274
+ }
275
+ }
276
+ }
277
+
278
+ if (stat.isDirectory()) {
279
+ walk(samplePath);
280
+ } else {
281
+ addFile(samplePath);
282
+ }
283
+
284
+ const validFiles = files.filter((f) => f.content.length < 50000);
285
+
286
+ const output = [
287
+ `# Project Generation: ${sampleLabel}`,
288
+ "",
289
+ "This output contains the file structure for the project.",
290
+ "Note: This tool returns files inline and does not create a downloadable zip.",
291
+ ""
292
+ ];
293
+
294
+ for (const file of validFiles) {
295
+ output.push(`## ${file.path}`);
296
+ output.push("```" + (file.ext || "text"));
297
+ output.push(file.content);
298
+ output.push("```");
299
+ output.push("");
300
+ }
301
+
302
+ return { content: [{ type: "text", text: output.join("\n") }] };
303
+ }
304
+ );
305
+ }