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
package/src/index.js CHANGED
@@ -1,1518 +1,68 @@
1
1
  #!/usr/bin/env node
2
- import { readFileSync, readdirSync, existsSync, statSync } from "node:fs";
3
- import { join, relative, dirname, extname } from "node:path";
4
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
- import {
7
- ListResourcesRequestSchema,
8
- ReadResourceRequestSchema,
9
- SubscribeRequestSchema,
10
- UnsubscribeRequestSchema
11
- } from "@modelcontextprotocol/sdk/types.js";
12
- import { z } from "zod";
13
- import { ensureDataReady } from "./data-bootstrap.js";
14
- import { maybeSyncSubmodulesOnStart } from "./submodule-sync.js";
2
+ import { readFileSync } from "node:fs";
3
+ import { ensureDataReady } from "./data/bootstrap.js";
4
+ import { maybeSyncSubmodulesOnStart } from "./data/submodule-sync.js";
5
+ import { createMcpServerInstance } from "./server/create-server.js";
6
+ import { MCP_HTTP_PATH, resolveRuntimeConfig } from "./server/runtime-config.js";
7
+ import { startStdioServer } from "./server/transports/stdio.js";
8
+ import { startHttpServer } from "./server/transports/http.js";
9
+ import { logEvent } from "./observability/logging.js";
15
10
 
16
11
  const pkgUrl = new URL("../package.json", import.meta.url);
17
12
  const pkg = JSON.parse(readFileSync(pkgUrl, "utf8"));
18
13
 
19
14
  await maybeSyncSubmodulesOnStart();
20
15
  const dataStatus = await ensureDataReady();
21
- if (dataStatus.mode === "downloaded") {
22
- console.error(
23
- `[data] mode=downloaded path=${dataStatus.dataRoot} source=${dataStatus.downloaded ? "fresh-download" : "cache"}`
24
- );
25
- } else {
26
- console.error(`[data] mode=${dataStatus.mode} path=${dataStatus.dataRoot}`);
27
- }
28
-
29
- const {
30
- registry,
31
- LATEST_VERSIONS,
32
- LATEST_MAJOR,
33
- discoverDwtSamples,
34
- discoverDcvMobileSamples,
35
- discoverDcvServerSamples,
36
- discoverDcvWebSamples,
37
- findCodeFilesInSample,
38
- getMobileSamplePath,
39
- getDbrServerSamplePath,
40
- getDcvMobileSamplePath,
41
- getDcvServerSamplePath,
42
- getDcvWebSamplePath,
43
- getDwtSamplePath,
44
- getDdvSamplePath,
45
- readCodeFile,
46
- getMainCodeFile,
47
- ensureLatestMajor,
48
- parseResourceUri,
49
- parseSampleUri,
50
- getSampleIdFromUri,
51
- getSampleEntries,
52
- buildIndexData,
53
- getDisplayEdition,
54
- getDisplayPlatform,
55
- formatScopeLabel,
56
- getPinnedResources,
57
- readResourceContent,
58
- normalizePlatform,
59
- normalizeApiLevel,
60
- normalizeSampleName,
61
- normalizeProduct,
62
- normalizeEdition,
63
- resourceIndex,
64
- getWebSamplePath
65
- } = await import("./resource-index.js");
66
-
67
- const {
68
- searchResources,
69
- getSampleSuggestions,
70
- prewarmRagIndex,
71
- ragConfig
72
- } = await import("./rag.js");
73
-
74
- // ============================================================================
75
- // MCP Server
76
- // ============================================================================
77
-
78
- const server = new McpServer({
79
- name: "simple-dynamsoft-mcp",
80
- version: pkg.version,
81
- description: "MCP server for latest major versions of Dynamsoft SDKs: Capture Vision, Barcode Reader, Dynamic Web TWAIN, and Document Viewer. Includes guidance for choosing DBR vs DCV by scenario."
16
+ logEvent("data", "startup_mode", {
17
+ mode: dataStatus.mode,
18
+ path: dataStatus.dataRoot,
19
+ cache_source: dataStatus.mode.includes("downloaded") ? (dataStatus.downloaded ? "fresh-download" : "cache") : "n/a"
82
20
  });
83
21
 
84
- function formatScoreLabel(entry) {
85
- if (!Number.isFinite(entry?.score)) return "";
86
- return ` | score: ${entry.score.toFixed(3)}`;
87
- }
88
-
89
- function formatScoreNote(entry) {
90
- if (!Number.isFinite(entry?.score)) return "";
91
- return ` score=${entry.score.toFixed(3)}`;
92
- }
93
-
94
- // ============================================================================
95
- // TOOL: get_index
96
- // ============================================================================
97
-
98
- server.registerTool(
99
- "get_index",
100
- {
101
- title: "Get Index",
102
- description: "Get a compact index of products, editions, versions, samples/docs, plus DBR-vs-DCV selection guidance.",
103
- inputSchema: {}
104
- },
105
- async () => ({
106
- content: [{ type: "text", text: JSON.stringify(buildIndexData(), null, 2) }]
107
- })
108
- );
109
-
110
- // ============================================================================
111
- // TOOL: search
112
- // ============================================================================
113
-
114
- server.registerTool(
115
- "search",
116
- {
117
- title: "Search",
118
- description: "Semantic (RAG) search across docs and samples with fuzzy fallback; returns resource links for lazy loading. Prefer DCV for MRZ/VIN/document-normalization/driver-license scenarios; DBR for barcode-only.",
119
- inputSchema: {
120
- query: z.string().describe("Keywords to search across docs and samples."),
121
- product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
122
- edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
123
- 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, spm, core"),
124
- version: z.string().optional().describe("Version constraint (major or full version)"),
125
- type: z.enum(["doc", "sample", "index", "policy", "any"]).optional(),
126
- limit: z.number().int().min(1).max(10).optional().describe("Max results (default 5)")
127
- }
128
- },
129
- async ({ query, product, edition, platform, version, type, limit }) => {
130
- if (!query || !query.trim()) {
131
- return { isError: true, content: [{ type: "text", text: "Query is required." }] };
132
- }
133
- const normalizedProduct = normalizeProduct(product);
134
- const normalizedPlatform = normalizePlatform(platform);
135
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
136
-
137
- const policy = ensureLatestMajor({
138
- product: normalizedProduct,
139
- version,
140
- query,
141
- edition: normalizedEdition,
142
- platform: normalizedPlatform
143
- });
144
-
145
- if (!policy.ok) {
146
- return { isError: true, content: [{ type: "text", text: policy.message }] };
147
- }
148
-
149
- const maxResults = Math.min(limit || 5, 10);
150
- const topResults = await searchResources({
151
- query,
152
- product: normalizedProduct,
153
- edition: normalizedEdition,
154
- platform: normalizedPlatform,
155
- type: type || "any",
156
- limit: maxResults
157
- });
158
-
159
- if (topResults.length === 0) {
160
- return {
161
- content: [{
162
- type: "text",
163
- text: `No results for "${query}". Try get_index for available products or adjust filters.`
164
- }]
165
- };
166
- }
167
-
168
- const content = [
169
- {
170
- type: "text",
171
- text: `Found ${topResults.length} result(s) for "${query}". Read the links you need with resources/read.`
172
- }
173
- ];
174
-
175
- for (const entry of topResults) {
176
- const versionLabel = entry.version ? `v${entry.version}` : "n/a";
177
- const scopeLabel = formatScopeLabel(entry);
178
- const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
179
- const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
180
- const scoreLabel = formatScoreLabel(entry);
181
- content.push({
182
- type: "resource_link",
183
- uri: entry.uri,
184
- name: entry.title,
185
- description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${scoreLabel} - ${entry.summary}${sampleHint}`,
186
- mimeType: entry.mimeType,
187
- annotations: {
188
- audience: ["assistant"],
189
- priority: 0.8
190
- }
191
- });
192
- }
193
-
194
- const plainLines = topResults.map((entry, index) => {
195
- const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
196
- const action = entry.type === "sample" ? "generate_project resource_uri" : "resources/read uri";
197
- const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
198
- const scoreNote = formatScoreNote(entry);
199
- return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote} (${action})`;
200
- });
201
- content.push({
202
- type: "text",
203
- text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
204
- });
205
-
206
- return { content };
207
- }
208
- );
209
-
210
- // ============================================================================
211
- // TOOL: list_samples
212
- // ============================================================================
213
-
214
- server.registerTool(
215
- "list_samples",
216
- {
217
- title: "List Samples",
218
- description: "List available sample IDs and URIs for a given scope. Use DCV scope for MRZ/VIN/document normalization scenarios.",
219
- inputSchema: {
220
- product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
221
- edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
222
- 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, spm, core"),
223
- limit: z.number().int().min(1).max(200).optional().describe("Max results (default 50)")
224
- }
225
- },
226
- async ({ product, edition, platform, limit }) => {
227
- const normalizedProduct = normalizeProduct(product);
228
- const normalizedPlatform = normalizePlatform(platform);
229
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
230
-
231
- const policy = ensureLatestMajor({
232
- product: normalizedProduct,
233
- version: undefined,
234
- query: "",
235
- edition: normalizedEdition,
236
- platform: normalizedPlatform
237
- });
238
-
239
- if (!policy.ok) {
240
- return { isError: true, content: [{ type: "text", text: policy.message }] };
241
- }
242
-
243
- const samples = getSampleEntries({
244
- product: normalizedProduct,
245
- edition: normalizedEdition,
246
- platform: normalizedPlatform
247
- });
248
-
249
- const maxResults = Math.min(limit || 50, 200);
250
- const selected = samples.slice(0, maxResults);
251
-
252
- const payload = selected.map((entry) => ({
253
- sample_id: getSampleIdFromUri(entry.uri),
254
- uri: entry.uri,
255
- product: entry.product,
256
- edition: getDisplayEdition(entry.edition),
257
- platform: getDisplayPlatform(entry.platform),
258
- version: entry.version,
259
- title: entry.title,
260
- summary: entry.summary
261
- }));
262
-
263
- const lines = [
264
- `Total matches: ${samples.length}`,
265
- `Returned: ${payload.length}`,
266
- "",
267
- "Plain URIs (copy/paste):",
268
- ...payload.map((item, index) => {
269
- const sampleNote = item.sample_id ? ` (sample_id: ${item.sample_id})` : "";
270
- return `- ${index + 1}. ${item.uri}${sampleNote}`;
271
- })
272
- ];
273
-
274
- const output = {
275
- total: samples.length,
276
- returned: payload.length,
277
- samples: payload
278
- };
279
-
280
- return {
281
- content: [{
282
- type: "text",
283
- text: `${lines.join("\n")}\n\nJSON:\n${JSON.stringify(output, null, 2)}`
284
- }]
285
- };
286
- }
287
- );
288
-
289
- // ============================================================================
290
- // TOOL: resolve_sample
291
- // ============================================================================
292
-
293
- server.registerTool(
294
- "resolve_sample",
295
- {
296
- title: "Resolve Sample",
297
- description: "Resolve a sample_id (or sample URI) to matching sample URIs.",
298
- inputSchema: {
299
- sample_id: z.string().describe("Sample identifier or sample:// URI"),
300
- product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
301
- edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
302
- 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, spm, core"),
303
- limit: z.number().int().min(1).max(10).optional().describe("Max results (default 5)")
304
- }
305
- },
306
- async ({ sample_id, product, edition, platform, limit }) => {
307
- if (!sample_id || !sample_id.trim()) {
308
- return { isError: true, content: [{ type: "text", text: "sample_id is required." }] };
309
- }
310
-
311
- const normalizedProduct = normalizeProduct(product);
312
- const normalizedPlatform = normalizePlatform(platform);
313
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
314
-
315
- const policy = ensureLatestMajor({
316
- product: normalizedProduct,
317
- version: undefined,
318
- query: sample_id,
319
- edition: normalizedEdition,
320
- platform: normalizedPlatform
321
- });
322
-
323
- if (!policy.ok) {
324
- return { isError: true, content: [{ type: "text", text: policy.message }] };
325
- }
326
-
327
- if (sample_id.includes("://")) {
328
- const parsed = parseSampleUri(sample_id);
329
- if (!parsed) {
330
- return {
331
- isError: true,
332
- content: [{
333
- type: "text",
334
- text: "sample_id looks like a URI but is not a valid sample:// URI. For doc:// URIs, use resources/read."
335
- }]
336
- };
337
- }
338
- const entry = resourceIndex.find((item) => item.uri === sample_id && item.type === "sample");
339
- if (!entry) {
340
- return {
341
- isError: true,
342
- content: [{
343
- type: "text",
344
- text: `Sample URI not found in index: ${sample_id}. Use list_samples or search.`
345
- }]
346
- };
347
- }
348
-
349
- const payload = [{
350
- sample_id: getSampleIdFromUri(entry.uri),
351
- uri: entry.uri,
352
- product: entry.product,
353
- edition: getDisplayEdition(entry.edition),
354
- platform: getDisplayPlatform(entry.platform),
355
- version: entry.version,
356
- title: entry.title,
357
- summary: entry.summary
358
- }];
359
-
360
- const output = {
361
- query: sample_id,
362
- returned: payload.length,
363
- samples: payload
364
- };
365
-
366
- return {
367
- content: [{
368
- type: "text",
369
- text: [
370
- `Found ${payload.length} match(es) for "${sample_id}".`,
371
- "Plain URIs (copy/paste):",
372
- `- 1. ${entry.uri} (sample_id: ${payload[0].sample_id})`,
373
- "",
374
- "JSON:",
375
- JSON.stringify(output, null, 2)
376
- ].join("\n")
377
- }, {
378
- type: "resource_link",
379
- uri: entry.uri,
380
- name: entry.title,
381
- description: `SAMPLE | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${payload[0].sample_id}`,
382
- mimeType: entry.mimeType,
383
- annotations: {
384
- audience: ["assistant"],
385
- priority: 0.8
386
- }
387
- }]
388
- };
389
- }
390
-
391
- const sampleQuery = normalizeSampleName(sample_id);
392
- const maxResults = Math.min(limit || 5, 10);
393
-
394
- const scopedSamples = getSampleEntries({
395
- product: normalizedProduct,
396
- edition: normalizedEdition,
397
- platform: normalizedPlatform
398
- });
399
-
400
- let matches = scopedSamples.filter((entry) => {
401
- const entryId = getSampleIdFromUri(entry.uri);
402
- return entryId && entryId.toLowerCase() === sampleQuery.toLowerCase();
403
- });
404
-
405
- if (matches.length === 0) {
406
- matches = await searchResources({
407
- query: sample_id,
408
- product: normalizedProduct,
409
- edition: normalizedEdition,
410
- platform: normalizedPlatform,
411
- type: "sample",
412
- limit: maxResults
413
- });
414
- }
415
-
416
- const selected = matches.slice(0, maxResults);
417
- if (selected.length === 0) {
418
- const suggestions = await getSampleSuggestions({
419
- query: sample_id,
420
- product: normalizedProduct,
421
- edition: normalizedEdition,
422
- platform: normalizedPlatform,
423
- limit: maxResults
424
- });
425
-
426
- const content = [{
427
- type: "text",
428
- text: suggestions.length
429
- ? `No exact sample match for "${sample_id}". Related samples:`
430
- : `No samples found for "${sample_id}". Try list_samples or search.`
431
- }];
432
-
433
- for (const entry of suggestions) {
434
- const sampleId = getSampleIdFromUri(entry.uri);
435
- content.push({
436
- type: "resource_link",
437
- uri: entry.uri,
438
- name: entry.title,
439
- description: `${entry.type.toUpperCase()} | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${sampleId || "n/a"}`,
440
- mimeType: entry.mimeType,
441
- annotations: {
442
- audience: ["assistant"],
443
- priority: 0.6
444
- }
445
- });
446
- }
447
-
448
- if (suggestions.length) {
449
- const plainLines = suggestions.map((entry, index) => {
450
- const sampleId = getSampleIdFromUri(entry.uri);
451
- const sampleNote = sampleId ? ` (sample_id: ${sampleId})` : "";
452
- const scoreNote = formatScoreNote(entry);
453
- return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote}`;
454
- });
455
- content.push({
456
- type: "text",
457
- text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
458
- });
459
- }
460
-
461
- return { isError: true, content };
462
- }
463
-
464
- const payload = selected.map((entry) => ({
465
- sample_id: getSampleIdFromUri(entry.uri),
466
- uri: entry.uri,
467
- product: entry.product,
468
- edition: getDisplayEdition(entry.edition),
469
- platform: getDisplayPlatform(entry.platform),
470
- version: entry.version,
471
- title: entry.title,
472
- summary: entry.summary
473
- }));
474
-
475
- const lines = [
476
- `Found ${selected.length} match(es) for "${sample_id}".`,
477
- "Plain URIs (copy/paste):",
478
- ...selected.map((entry, index) => {
479
- const sampleId = getSampleIdFromUri(entry.uri);
480
- const sampleNote = sampleId ? ` (sample_id: ${sampleId})` : "";
481
- const scoreNote = formatScoreNote(entry);
482
- return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote}`;
483
- })
484
- ];
485
-
486
- const output = {
487
- query: sample_id,
488
- returned: payload.length,
489
- samples: payload
490
- };
491
-
492
- const content = [{
493
- type: "text",
494
- text: `${lines.join("\n")}\n\nJSON:\n${JSON.stringify(output, null, 2)}`
495
- }];
496
-
497
- for (const entry of selected) {
498
- const sampleId = getSampleIdFromUri(entry.uri);
499
- content.push({
500
- type: "resource_link",
501
- uri: entry.uri,
502
- name: entry.title,
503
- description: `${entry.type.toUpperCase()} | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${sampleId || "n/a"}`,
504
- mimeType: entry.mimeType,
505
- annotations: {
506
- audience: ["assistant"],
507
- priority: 0.8
508
- }
509
- });
510
- }
511
-
512
- return { content };
513
- }
514
- );
515
-
516
- // ============================================================================
517
- // TOOL: resolve_version
518
- // ============================================================================
519
-
520
- server.registerTool(
521
- "resolve_version",
522
- {
523
- title: "Resolve Version",
524
- description: "Resolve a concrete latest-major version for a product/edition/platform.",
525
- inputSchema: {
526
- product: z.string().describe("Product: dcv, dbr, dwt, or ddv"),
527
- edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
528
- 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, spm, core"),
529
- constraint: z.string().optional().describe("Version constraint, e.g., latest, 11.x, 10"),
530
- feature: z.string().optional().describe("Optional feature hint")
531
- }
532
- },
533
- async ({ product, edition, platform, constraint, feature }) => {
534
- const normalizedProduct = normalizeProduct(product);
535
- const normalizedPlatform = normalizePlatform(platform);
536
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
537
-
538
- if (!["dcv", "dbr", "dwt", "ddv"].includes(normalizedProduct)) {
539
- return {
540
- isError: true,
541
- content: [{ type: "text", text: `Unknown product "${product}". Use dcv, dbr, dwt, or ddv.` }]
542
- };
543
- }
544
-
545
- const policy = ensureLatestMajor({
546
- product: normalizedProduct,
547
- version: constraint,
548
- query: feature,
549
- edition: normalizedEdition,
550
- platform: normalizedPlatform
551
- });
552
-
553
- if (!policy.ok) {
554
- return { isError: true, content: [{ type: "text", text: policy.message }] };
555
- }
556
-
557
- if (normalizedProduct === "dcv") {
558
- if (!normalizedEdition) {
559
- const lines = [
560
- "# DCV Version Resolution",
561
- `- Latest major: v${LATEST_MAJOR.dcv}`,
562
- `- Core: ${LATEST_VERSIONS.dcv.core}`,
563
- `- Web: ${LATEST_VERSIONS.dcv.web}`,
564
- `- Mobile: ${LATEST_VERSIONS.dcv.mobile}`,
565
- `- Server/Desktop: ${LATEST_VERSIONS.dcv.server}`,
566
- "",
567
- "Specify edition/platform to resolve a single version."
568
- ];
569
- return { content: [{ type: "text", text: lines.join("\n") }] };
570
- }
571
-
572
- const resolved = LATEST_VERSIONS.dcv[normalizedEdition];
573
- if (!resolved) {
574
- return {
575
- isError: true,
576
- content: [{ type: "text", text: `Edition "${normalizedEdition}" is not hosted by this MCP server.` }]
577
- };
578
- }
579
-
580
- const displayPlatform = normalizedPlatform === "web" ? "js" : normalizedPlatform;
581
- const lines = [
582
- "# DCV Version Resolution",
583
- `- Edition: ${normalizedEdition}`,
584
- displayPlatform ? `- Platform: ${displayPlatform}` : "",
585
- `- Latest major: v${LATEST_MAJOR.dcv}`,
586
- `- Resolved version: ${resolved}`
587
- ].filter(Boolean);
588
-
589
- return { content: [{ type: "text", text: lines.join("\n") }] };
590
- }
591
-
592
- if (normalizedProduct === "dbr") {
593
- if (!normalizedEdition) {
594
- const lines = [
595
- "# DBR Version Resolution",
596
- `- Latest major: v${LATEST_MAJOR.dbr}`,
597
- `- Mobile: ${LATEST_VERSIONS.dbr.mobile}`,
598
- `- Web: ${LATEST_VERSIONS.dbr.web}`,
599
- `- Server/Desktop: ${LATEST_VERSIONS.dbr.server}`,
600
- "",
601
- "Specify edition/platform to resolve a single version."
602
- ];
603
- return { content: [{ type: "text", text: lines.join("\n") }] };
604
- }
605
-
606
- const resolved = LATEST_VERSIONS.dbr[normalizedEdition];
607
- if (!resolved) {
608
- return {
609
- isError: true,
610
- content: [{ type: "text", text: `Edition "${normalizedEdition}" is not hosted by this MCP server.` }]
611
- };
612
- }
613
-
614
- const displayPlatform = normalizedPlatform === "web" ? "js" : normalizedPlatform;
615
- const lines = [
616
- "# DBR Version Resolution",
617
- `- Edition: ${normalizedEdition}`,
618
- displayPlatform ? `- Platform: ${displayPlatform}` : "",
619
- `- Latest major: v${LATEST_MAJOR.dbr}`,
620
- `- Resolved version: ${resolved}`
621
- ].filter(Boolean);
622
-
623
- return { content: [{ type: "text", text: lines.join("\n") }] };
624
- }
625
-
626
- if (normalizedProduct === "dwt") {
627
- const lines = [
628
- "# DWT Version Resolution",
629
- `- Latest major: v${LATEST_MAJOR.dwt}`,
630
- `- Resolved version: ${LATEST_VERSIONS.dwt.web}`
631
- ];
632
- return { content: [{ type: "text", text: lines.join("\n") }] };
633
- }
634
-
635
- const lines = [
636
- "# DDV Version Resolution",
637
- `- Latest major: v${LATEST_MAJOR.ddv}`,
638
- `- Resolved version: ${LATEST_VERSIONS.ddv.web}`
639
- ];
640
-
641
- return { content: [{ type: "text", text: lines.join("\n") }] };
642
- }
643
- );
644
-
645
- // ============================================================================
646
- // TOOL: get_quickstart
647
- // ============================================================================
648
-
649
- server.registerTool(
650
- "get_quickstart",
651
- {
652
- title: "Get Quickstart",
653
- description: "Opinionated quickstart for a target product/edition/platform. DCV supports MRZ/VIN/document-normalization/driver-license workflows.",
654
- inputSchema: {
655
- product: z.string().describe("Product: dcv, dbr, dwt, or ddv"),
656
- edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
657
- 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, spm, core"),
658
- language: z.string().optional().describe("Language hint: kotlin, java, swift, js, ts, python, cpp, csharp, react, vue, angular"),
659
- version: z.string().optional().describe("Version constraint"),
660
- api_level: z.string().optional().describe("API level: high-level or low-level (mobile only)"),
661
- scenario: z.string().optional().describe("Scenario: camera, image, single, multiple, MRZ, VIN, document scan/normalization, driver license, react, etc.")
662
- }
663
- },
664
- async ({ product, edition, platform, language, version, api_level, scenario }) => {
665
- const normalizedProduct = normalizeProduct(product);
666
- const normalizedPlatform = normalizePlatform(platform);
667
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
668
- const policy = ensureLatestMajor({
669
- product: normalizedProduct,
670
- version,
671
- query: scenario,
672
- edition: normalizedEdition,
673
- platform: normalizedPlatform
674
- });
675
-
676
- if (!policy.ok) {
677
- return { isError: true, content: [{ type: "text", text: policy.message }] };
678
- }
679
-
680
- if (normalizedProduct === "dcv") {
681
- const scenarioLower = `${scenario || ""} ${language || ""}`.toLowerCase();
682
- const effectiveEdition = normalizedEdition || (normalizedPlatform ? normalizeEdition("", normalizedPlatform, "dcv") : "server");
683
-
684
- function selectDcvServerSample(platformHint, hint) {
685
- const platformName = normalizePlatform(platformHint) || "python";
686
- if (platformName === "python") {
687
- if (hint.includes("mrz")) return "mrz_scanner";
688
- if (hint.includes("vin")) return "vin_scanner";
689
- if (hint.includes("driver") || hint.includes("license")) return "driver_license_scanner";
690
- if (hint.includes("gs1")) return "gs1_ai_scanner";
691
- return "document_scanner";
692
- }
693
- if (platformName === "nodejs") {
694
- if (hint.includes("lambda")) return "lambda";
695
- if (hint.includes("pdf")) return "pdf-advanced";
696
- if (hint.includes("koa")) return "koa";
697
- return "express";
698
- }
699
- if (hint.includes("mrz")) return "MRZScanner";
700
- if (hint.includes("vin")) return "VINScanner";
701
- if (hint.includes("driver") || hint.includes("license")) return "DriverLicenseScanner";
702
- if (hint.includes("gs1")) return "GS1AIScanner";
703
- return "DocumentScanner";
704
- }
705
-
706
- function selectMobileSample(sampleNames, hint) {
707
- const lowerToName = new Map(sampleNames.map((name) => [String(name).toLowerCase(), name]));
708
- const candidates = hint.includes("mrz")
709
- ? ["scanmrz", "mrzscanner"]
710
- : hint.includes("vin")
711
- ? ["scanvin", "vinscanner"]
712
- : (hint.includes("driver") || hint.includes("license"))
713
- ? ["driverlicensescanner"]
714
- : ["scandocument", "documentscanner"];
715
- for (const candidate of candidates) {
716
- if (lowerToName.has(candidate)) return lowerToName.get(candidate);
717
- }
718
- return sampleNames[0] || "";
719
- }
720
-
721
- function readBestSampleContent(samplePath) {
722
- if (!samplePath || !existsSync(samplePath)) return { text: "", fence: "text" };
723
- const sampleStat = statSync(samplePath);
724
- if (sampleStat.isFile()) {
725
- return {
726
- text: readCodeFile(samplePath),
727
- fence: extname(samplePath).replace(".", "") || "text"
728
- };
729
- }
730
- const readmePath = join(samplePath, "README.md");
731
- if (existsSync(readmePath)) return { text: readCodeFile(readmePath), fence: "markdown" };
732
-
733
- const codeFiles = findCodeFilesInSample(samplePath);
734
- if (codeFiles.length > 0) {
735
- const preferredNames = ["index.html", "index.js", "index.ts", "main.dart", "App.tsx", "MainActivity.kt", "MainActivity.java"];
736
- const preferred = codeFiles.find((file) => preferredNames.includes(file.filename)) || codeFiles[0];
737
- return {
738
- text: readCodeFile(preferred.path),
739
- fence: preferred.extension ? preferred.extension.replace(".", "") : "text"
740
- };
741
- }
742
-
743
- return { text: "Sample found, but no code files detected.", fence: "text" };
744
- }
22
+ const resourceIndexApi = await import("./server/resource-index.js");
23
+ const ragApi = await import("./rag/index.js");
745
24
 
746
- function formatInstallLines(installation) {
747
- if (!installation || typeof installation !== "object") return [];
748
- const lines = [];
749
- for (const value of Object.values(installation)) {
750
- if (typeof value === "string" && value.trim()) lines.push(value);
751
- }
752
- return lines;
753
- }
754
-
755
- if (effectiveEdition === "server") {
756
- const sdkEntry = registry.sdks["dcv-server"];
757
- const targetPlatform = normalizePlatform(normalizedPlatform || sdkEntry.default_platform || "python");
758
- const sampleName = selectDcvServerSample(targetPlatform, scenarioLower);
759
- const samplePath = getDcvServerSamplePath(targetPlatform, sampleName);
760
-
761
- if (!samplePath || !existsSync(samplePath)) {
762
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
763
- }
764
-
765
- const { text: sampleContent, fence } = readBestSampleContent(samplePath);
766
- const installLines = formatInstallLines(sdkEntry.platforms?.[targetPlatform]?.installation);
767
-
768
- return {
769
- content: [{
770
- type: "text",
771
- text: [
772
- `# Quick Start: DCV Server (${targetPlatform})`,
773
- "",
774
- `**SDK Version:** ${sdkEntry.version}`,
775
- `**Trial License:** \`${registry.trial_license}\``,
776
- "",
777
- installLines.length ? "## Install" : "",
778
- installLines.length ? "```bash" : "",
779
- ...installLines,
780
- installLines.length ? "```" : "",
781
- installLines.length ? "" : "",
782
- `## ${sampleName}`,
783
- "```" + fence,
784
- sampleContent,
785
- "```",
786
- "",
787
- `Docs: ${sdkEntry.platforms?.[targetPlatform]?.docs?.["user-guide"] || "N/A"}`
788
- ].filter(Boolean).join("\n")
789
- }]
790
- };
791
- }
792
-
793
- if (effectiveEdition === "web") {
794
- const sdkEntry = registry.sdks["dcv-web"];
795
- const available = discoverDcvWebSamples();
796
- const sampleName = scenarioLower.includes("vin") ? "VINScanner" : (available[0] || "VINScanner");
797
- const samplePath = getDcvWebSamplePath(sampleName);
798
- if (!samplePath || !existsSync(samplePath)) {
799
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
800
- }
801
- const { text: sampleContent, fence } = readBestSampleContent(samplePath);
802
- const installLines = formatInstallLines(sdkEntry.platforms?.web?.installation);
803
-
804
- return {
805
- content: [{
806
- type: "text",
807
- text: [
808
- "# Quick Start: DCV Web",
809
- "",
810
- `**SDK Version:** ${sdkEntry.version}`,
811
- `**Trial License:** \`${registry.trial_license}\``,
812
- "",
813
- installLines.length ? "## Install" : "",
814
- installLines.length ? "```bash" : "",
815
- ...installLines,
816
- installLines.length ? "```" : "",
817
- installLines.length ? "" : "",
818
- `## ${sampleName}`,
819
- "```" + fence,
820
- sampleContent,
821
- "```",
822
- "",
823
- `Docs: ${sdkEntry.platforms?.web?.docs?.["user-guide"] || "N/A"}`
824
- ].filter(Boolean).join("\n")
825
- }]
826
- };
827
- }
828
-
829
- if (effectiveEdition === "mobile") {
830
- const sdkEntry = registry.sdks["dcv-mobile"];
831
- const targetPlatform = normalizePlatform(normalizedPlatform || sdkEntry.default_platform || "android");
832
- const sampleNames = discoverDcvMobileSamples(targetPlatform);
833
- const sampleName = selectMobileSample(sampleNames, scenarioLower);
834
- const samplePath = getDcvMobileSamplePath(targetPlatform, sampleName);
835
-
836
- if (!samplePath || !existsSync(samplePath)) {
837
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName || "N/A"}.` }] };
838
- }
839
-
840
- const { text: sampleContent, fence } = readBestSampleContent(samplePath);
841
- const installLines = formatInstallLines(sdkEntry.platforms?.[targetPlatform]?.installation);
842
-
843
- return {
844
- content: [{
845
- type: "text",
846
- text: [
847
- `# Quick Start: DCV Mobile (${targetPlatform})`,
848
- "",
849
- `**SDK Version:** ${sdkEntry.version}`,
850
- `**Trial License:** \`${registry.trial_license}\``,
851
- "",
852
- installLines.length ? "## Install" : "",
853
- installLines.length ? "```bash" : "",
854
- ...installLines,
855
- installLines.length ? "```" : "",
856
- installLines.length ? "" : "",
857
- `## ${sampleName}`,
858
- "```" + fence,
859
- sampleContent,
860
- "```",
861
- "",
862
- `Docs: ${sdkEntry.platforms?.[targetPlatform]?.docs?.["user-guide"] || "N/A"}`
863
- ].filter(Boolean).join("\n")
864
- }]
865
- };
866
- }
867
-
868
- if (effectiveEdition === "core") {
869
- const sdkEntry = registry.sdks["dcv-core"];
870
- return {
871
- content: [{
872
- type: "text",
873
- text: [
874
- "# Quick Start: DCV Core",
875
- "",
876
- `**SDK Version:** ${sdkEntry.version}`,
877
- "",
878
- "DCV core docs aggregate architecture, parameters, and cross-product workflows.",
879
- `Docs: ${sdkEntry.platforms?.core?.docs?.introduction || "https://www.dynamsoft.com/capture-vision/docs/core/"}`
880
- ].join("\n")
881
- }]
882
- };
883
- }
884
- }
885
-
886
- if (normalizedProduct === "dbr" && normalizedEdition === "server") {
887
- const sdkEntry = registry.sdks["dbr-server"];
888
- const scenarioLower = (scenario || "").toLowerCase();
889
- const sampleName = scenarioLower.includes("video") ? "video_decoding" : "read_an_image";
890
- const samplePath = getDbrServerSamplePath("python", sampleName);
891
-
892
- if (!existsSync(samplePath)) {
893
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
894
- }
895
-
896
- const content = readCodeFile(samplePath);
897
-
898
- return {
899
- content: [{
900
- type: "text",
901
- text: [
902
- "# Quick Start: DBR Server (Python)",
903
- "",
904
- `**SDK Version:** ${sdkEntry.version}`,
905
- `**Trial License:** \`${registry.trial_license}\``,
906
- "",
907
- "## Install",
908
- "```bash",
909
- sdkEntry.platforms.python.installation.pip,
910
- "```",
911
- "",
912
- `## ${sampleName}.py`,
913
- "```python",
914
- content,
915
- "```",
916
- "",
917
- `Docs: ${sdkEntry.platforms.python.docs["user-guide"]}`
918
- ].join("\n")
919
- }]
920
- };
921
- }
922
-
923
- if (normalizedProduct === "dbr" && normalizedEdition === "web") {
924
- const sdkEntry = registry.sdks["dbr-web"];
925
- const scenarioLower = (scenario || "").toLowerCase();
926
- const sampleName = scenarioLower.includes("image") ? "read-an-image" : "hello-world";
927
- const samplePath = getWebSamplePath("root", sampleName);
928
-
929
- if (!samplePath || !existsSync(samplePath)) {
930
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
931
- }
932
-
933
- const content = readCodeFile(samplePath);
934
-
935
- return {
936
- content: [{
937
- type: "text",
938
- text: [
939
- "# Quick Start: DBR Web",
940
- "",
941
- `**SDK Version:** ${sdkEntry.version}`,
942
- `**Trial License:** \`${registry.trial_license}\``,
943
- "",
944
- "## Option 1: CDN",
945
- "```html",
946
- `<script src="${sdkEntry.platforms.web.installation.cdn}"></script>`,
947
- "```",
948
- "",
949
- "## Option 2: NPM",
950
- "```bash",
951
- sdkEntry.platforms.web.installation.npm,
952
- "```",
953
- "",
954
- `## ${sampleName}.html`,
955
- "```html",
956
- content,
957
- "```",
958
- "",
959
- `Docs: ${sdkEntry.platforms.web.docs["user-guide"]}`
960
- ].join("\n")
961
- }]
962
- };
963
- }
964
-
965
- if (normalizedProduct === "dbr" && normalizedEdition === "mobile") {
966
- const sdkEntry = registry.sdks["dbr-mobile"];
967
- const targetPlatform = normalizedPlatform || "android";
968
- const level = normalizeApiLevel(api_level || scenario);
969
- const scenarioLower = (scenario || "").toLowerCase();
970
-
971
- let sampleName = "ScanSingleBarcode";
972
- if (scenarioLower.includes("multiple") || scenarioLower.includes("batch")) sampleName = "ScanMultipleBarcodes";
973
- else if (scenarioLower.includes("image") || scenarioLower.includes("file")) sampleName = "DecodeFromAnImage";
974
-
975
- if (level === "low-level") {
976
- if (sampleName === "ScanSingleBarcode" || sampleName === "ScanMultipleBarcodes") {
977
- sampleName = "DecodeWithCameraEnhancer";
978
- }
979
- }
980
-
981
- const samplePath = getMobileSamplePath(targetPlatform, level, sampleName);
982
- if (!existsSync(samplePath)) {
983
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
984
- }
985
-
986
- const mainFile = getMainCodeFile(targetPlatform, samplePath);
987
- if (!mainFile) {
988
- return { isError: true, content: [{ type: "text", text: "Could not find main code file." }] };
989
- }
990
-
991
- const content = readCodeFile(mainFile.path);
992
- const langExt = mainFile.filename.split(".").pop();
993
-
994
- let deps = "";
995
- if (targetPlatform === "android") {
996
- deps = `
997
- ## Dependencies
998
-
999
- **Project build.gradle**
1000
- \`\`\`groovy
1001
- allprojects {
1002
- repositories {
1003
- google()
1004
- mavenCentral()
1005
- maven { url "${registry.maven_url}" }
1006
- }
1007
- }
1008
- \`\`\`
1009
-
1010
- **App build.gradle**
1011
- \`\`\`groovy
1012
- dependencies {
1013
- implementation 'com.dynamsoft:barcodereaderbundle:${sdkEntry.version}'
1014
- }
1015
- \`\`\`
1016
-
1017
- **AndroidManifest.xml**
1018
- \`\`\`xml
1019
- <uses-permission android:name="android.permission.CAMERA" />
1020
- \`\`\``;
1021
- } else {
1022
- deps = `
1023
- ## Dependencies
1024
-
1025
- **Podfile**
1026
- \`\`\`ruby
1027
- platform :ios, '11.0'
1028
- use_frameworks!
1029
-
1030
- target 'YourApp' do
1031
- pod 'DynamsoftBarcodeReaderBundle'
1032
- end
1033
- \`\`\`
1034
-
1035
- **Info.plist**
1036
- \`\`\`xml
1037
- <key>NSCameraUsageDescription</key>
1038
- <string>Camera access for barcode scanning</string>
1039
- \`\`\``;
1040
- }
1041
-
1042
- const output = [
1043
- "# Quick Start: DBR Mobile",
1044
- "",
1045
- `**SDK Version:** ${sdkEntry.version}`,
1046
- `**API Level:** ${level}`,
1047
- `**Trial License:** \`${registry.trial_license}\``,
1048
- "",
1049
- deps,
1050
- "",
1051
- `## ${mainFile.filename}`,
1052
- "```" + langExt,
1053
- content,
1054
- "```",
1055
- "",
1056
- `Docs: ${sdkEntry.platforms[targetPlatform]?.docs[level]?.["user-guide"] || "N/A"}`
1057
- ];
1058
-
1059
- return { content: [{ type: "text", text: output.join("\n") }] };
1060
- }
1061
-
1062
- if (normalizedProduct === "dwt") {
1063
- const sdkEntry = registry.sdks["dwt"];
1064
- const samplePath = getDwtSamplePath("scan", "basic-scan");
1065
-
1066
- if (!samplePath || !existsSync(samplePath)) {
1067
- return { isError: true, content: [{ type: "text", text: "Sample not found: basic-scan." }] };
1068
- }
1069
-
1070
- const content = readCodeFile(samplePath);
1071
-
1072
- return {
1073
- content: [{
1074
- type: "text",
1075
- text: [
1076
- "# Quick Start: Dynamic Web TWAIN",
1077
- "",
1078
- `**SDK Version:** ${sdkEntry.version}`,
1079
- `**Trial License:** \`${registry.trial_license}\``,
1080
- "",
1081
- "## Option 1: CDN",
1082
- "```html",
1083
- `<script src="${sdkEntry.platforms.web.installation.cdn}"></script>`,
1084
- "```",
1085
- "",
1086
- "## Option 2: NPM",
1087
- "```bash",
1088
- sdkEntry.platforms.web.installation.npm,
1089
- "```",
1090
- "",
1091
- "## basic-scan.html",
1092
- "```html",
1093
- content,
1094
- "```",
1095
- "",
1096
- `Docs: ${sdkEntry.platforms.web.docs["user-guide"]}`
1097
- ].join("\n")
1098
- }]
1099
- };
1100
- }
1101
-
1102
- if (normalizedProduct === "ddv") {
1103
- const sdkEntry = registry.sdks["ddv"];
1104
- const hint = `${scenario || ""} ${language || ""}`.toLowerCase();
1105
- let sampleName = "hello-world";
1106
-
1107
- if (hint.includes("react")) sampleName = "react-vite";
1108
- else if (hint.includes("vue")) sampleName = "vue";
1109
- else if (hint.includes("angular")) sampleName = "angular";
1110
- else if (hint.includes("next")) sampleName = "next";
1111
-
1112
- const samplePath = getDdvSamplePath(sampleName);
1113
- if (!samplePath || !existsSync(samplePath)) {
1114
- return { isError: true, content: [{ type: "text", text: `Sample not found: ${sampleName}.` }] };
1115
- }
1116
-
1117
- let sampleContent = "";
1118
- let fence = "text";
1119
- const stat = statSync(samplePath);
1120
- if (stat.isDirectory()) {
1121
- const readmePath = join(samplePath, "README.md");
1122
- if (existsSync(readmePath)) {
1123
- sampleContent = readCodeFile(readmePath);
1124
- fence = "markdown";
1125
- } else {
1126
- const codeFiles = findCodeFilesInSample(samplePath);
1127
- if (codeFiles.length > 0) {
1128
- const preferredNames = [
1129
- "main.tsx",
1130
- "main.jsx",
1131
- "main.ts",
1132
- "main.js",
1133
- "App.tsx",
1134
- "App.jsx",
1135
- "App.vue"
1136
- ];
1137
- const preferred = codeFiles.find((file) => preferredNames.includes(file.filename)) || codeFiles[0];
1138
- sampleContent = readCodeFile(preferred.path);
1139
- fence = preferred.extension ? preferred.extension.replace(".", "") : "text";
1140
- } else {
1141
- sampleContent = "Sample found, but no code files detected.";
1142
- }
1143
- }
1144
- } else {
1145
- sampleContent = readCodeFile(samplePath);
1146
- fence = extname(samplePath).replace(".", "") || "text";
1147
- }
1148
-
1149
- return {
1150
- content: [{
1151
- type: "text",
1152
- text: [
1153
- "# Quick Start: Dynamsoft Document Viewer",
1154
- "",
1155
- `**SDK Version:** ${sdkEntry.version}`,
1156
- `**Trial License:** \`${registry.trial_license}\``,
1157
- "",
1158
- "## Option 1: CDN",
1159
- "```html",
1160
- `<script src="${sdkEntry.platforms.web.installation.cdn}"></script>`,
1161
- "```",
1162
- "",
1163
- "## Option 2: NPM",
1164
- "```bash",
1165
- sdkEntry.platforms.web.installation.npm,
1166
- "```",
1167
- "",
1168
- `## ${sampleName}`,
1169
- "```" + fence,
1170
- sampleContent,
1171
- "```",
1172
- "",
1173
- `Docs: ${sdkEntry.platforms.web.docs["user-guide"]}`
1174
- ].join("\n")
1175
- }]
1176
- };
1177
- }
1178
-
1179
- return {
1180
- isError: true,
1181
- content: [{ type: "text", text: "Unsupported product/edition for quickstart." }]
1182
- };
1183
- }
1184
- );
1185
-
1186
- // ============================================================================
1187
- // TOOL: generate_project
1188
- // ============================================================================
1189
-
1190
- server.registerTool(
1191
- "generate_project",
1192
- {
1193
- title: "Generate Project",
1194
- description: "Generate a project structure from a sample and return files inline (no zip/download).",
1195
- inputSchema: {
1196
- product: z.string().describe("Product: dcv, dbr, dwt, or ddv"),
1197
- edition: z.string().optional().describe("Edition: mobile, web, server/desktop"),
1198
- 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"),
1199
- version: z.string().optional().describe("Version constraint"),
1200
- sample_id: z.string().optional().describe("Sample identifier (name or path)"),
1201
- resource_uri: z.string().optional().describe("Resource URI returned by search"),
1202
- api_level: z.string().optional().describe("API level: high-level or low-level (mobile only)")
1203
- }
1204
- },
1205
- async ({ product, edition, platform, version, sample_id, resource_uri, api_level }) => {
1206
- const normalizedProduct = normalizeProduct(product);
1207
- const normalizedPlatform = normalizePlatform(platform);
1208
- const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
1209
-
1210
- const policy = ensureLatestMajor({
1211
- product: normalizedProduct,
1212
- version,
1213
- query: sample_id,
1214
- edition: normalizedEdition,
1215
- platform: normalizedPlatform
1216
- });
1217
-
1218
- if (!policy.ok) {
1219
- return { isError: true, content: [{ type: "text", text: policy.message }] };
1220
- }
1221
-
1222
- let sampleInfo = null;
1223
- if (resource_uri) {
1224
- const parsed = parseResourceUri(resource_uri);
1225
- if (!parsed) {
1226
- return {
1227
- isError: true,
1228
- content: [{
1229
- type: "text",
1230
- text: "resource_uri must be a sample://... URI. Use search or list_samples to get a valid sample URI."
1231
- }]
1232
- };
1233
- }
1234
- if (parsed.scheme !== "sample") {
1235
- return {
1236
- isError: true,
1237
- content: [{
1238
- type: "text",
1239
- text: "resource_uri must use the sample:// scheme. For doc:// URIs, use resources/read instead."
1240
- }]
1241
- };
1242
- }
1243
- sampleInfo = parseSampleUri(resource_uri);
1244
- if (!sampleInfo) {
1245
- return {
1246
- isError: true,
1247
- content: [{
1248
- type: "text",
1249
- text: "Invalid sample URI format. Use search or list_samples to obtain a valid sample:// URI."
1250
- }]
1251
- };
1252
- }
1253
- }
1254
-
1255
- let samplePath = null;
1256
- let sampleLabel = "";
1257
- let sampleQuery = "";
1258
-
1259
- if (sampleInfo) {
1260
- sampleLabel = sampleInfo.sampleName || resource_uri;
1261
- sampleQuery = sampleInfo.sampleName || sample_id || "";
1262
- if (sampleInfo.product === "dbr" && sampleInfo.edition === "mobile") {
1263
- samplePath = getMobileSamplePath(sampleInfo.platform, sampleInfo.level, sampleInfo.sampleName);
1264
- } else if (sampleInfo.product === "dbr" && sampleInfo.edition === "web") {
1265
- samplePath = getWebSamplePath(sampleInfo.category, sampleInfo.sampleName);
1266
- } else if (sampleInfo.product === "dbr" && (sampleInfo.edition === "python" || sampleInfo.edition === "server")) {
1267
- samplePath = getDbrServerSamplePath(sampleInfo.platform, sampleInfo.sampleName);
1268
- } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "mobile") {
1269
- samplePath = getDcvMobileSamplePath(sampleInfo.platform, sampleInfo.sampleName);
1270
- } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "server") {
1271
- samplePath = getDcvServerSamplePath(sampleInfo.platform, sampleInfo.sampleName);
1272
- } else if (sampleInfo.product === "dcv" && sampleInfo.edition === "web") {
1273
- samplePath = getDcvWebSamplePath(sampleInfo.sampleName);
1274
- } else if (sampleInfo.product === "dwt") {
1275
- samplePath = getDwtSamplePath(sampleInfo.category, sampleInfo.sampleName);
1276
- } else if (sampleInfo.product === "ddv") {
1277
- samplePath = getDdvSamplePath(sampleInfo.sampleName);
1278
- }
1279
- } else if (sample_id) {
1280
- if (!normalizedProduct || !normalizedEdition) {
1281
- return {
1282
- isError: true,
1283
- content: [{
1284
- type: "text",
1285
- text: "Specify product/edition or provide resource_uri. Use list_samples or get_index to discover valid scopes."
1286
- }]
1287
- };
1288
- }
1289
-
1290
- const level = normalizeApiLevel(api_level);
1291
- const sampleName = normalizeSampleName(sample_id);
1292
- sampleLabel = sampleName;
1293
- sampleQuery = sampleName;
1294
-
1295
- if (normalizedProduct === "dbr" && normalizedEdition === "mobile") {
1296
- const targetPlatform = normalizedPlatform || "android";
1297
- const primaryPath = getMobileSamplePath(targetPlatform, level, sampleName);
1298
- const altLevel = level === "high-level" ? "low-level" : "high-level";
1299
- const alternatePath = getMobileSamplePath(targetPlatform, altLevel, sampleName);
1300
- samplePath = existsSync(primaryPath) ? primaryPath : (existsSync(alternatePath) ? alternatePath : null);
1301
- } else if (normalizedProduct === "dcv" && normalizedEdition === "mobile") {
1302
- const platformCandidates = normalizedPlatform
1303
- ? [normalizedPlatform]
1304
- : ["android", "ios", "react-native", "flutter", "maui", "spm"];
1305
- for (const platformCandidate of platformCandidates) {
1306
- const candidate = getDcvMobileSamplePath(platformCandidate, sampleName);
1307
- if (candidate && existsSync(candidate)) {
1308
- samplePath = candidate;
1309
- break;
1310
- }
1311
- }
1312
- } else if (normalizedProduct === "dcv" && normalizedEdition === "web") {
1313
- samplePath = getDcvWebSamplePath(sampleName);
1314
- } else if (normalizedProduct === "dcv" && normalizedEdition === "server") {
1315
- samplePath = getDcvServerSamplePath(normalizedPlatform || "python", sampleName);
1316
- } else if (normalizedProduct === "dbr" && normalizedEdition === "web") {
1317
- samplePath = getWebSamplePath(undefined, sampleName);
1318
- } else if (normalizedProduct === "dbr" && normalizedEdition === "server") {
1319
- samplePath = getDbrServerSamplePath(normalizedPlatform || "python", sampleName);
1320
- } else if (normalizedProduct === "dwt") {
1321
- const categories = discoverDwtSamples();
1322
- let foundCategory = "";
1323
- for (const [category, samples] of Object.entries(categories)) {
1324
- if (samples.includes(sampleName)) {
1325
- foundCategory = category;
1326
- break;
1327
- }
1328
- }
1329
- samplePath = foundCategory ? getDwtSamplePath(foundCategory, sampleName) : null;
1330
- } else if (normalizedProduct === "ddv") {
1331
- samplePath = getDdvSamplePath(sampleName);
1332
- }
1333
- } else {
1334
- return { isError: true, content: [{ type: "text", text: "Provide sample_id or resource_uri." }] };
1335
- }
1336
-
1337
- if (!samplePath || !existsSync(samplePath)) {
1338
- const suggestions = await getSampleSuggestions({
1339
- query: sampleQuery,
1340
- product: normalizedProduct,
1341
- edition: normalizedEdition,
1342
- platform: normalizedPlatform,
1343
- limit: 5
1344
- });
1345
-
1346
- const content = [{
1347
- type: "text",
1348
- text: [
1349
- `Sample not found for "${sampleLabel}".`,
1350
- suggestions.length ? "Related samples:" : "No related samples found. Try search or get_index."
1351
- ].join("\n")
1352
- }];
1353
-
1354
- for (const entry of suggestions) {
1355
- const versionLabel = entry.version ? `v${entry.version}` : "n/a";
1356
- const scopeLabel = formatScopeLabel(entry);
1357
- const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
1358
- const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
1359
- content.push({
1360
- type: "resource_link",
1361
- uri: entry.uri,
1362
- name: entry.title,
1363
- description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel} - ${entry.summary}${sampleHint}`,
1364
- mimeType: entry.mimeType,
1365
- annotations: {
1366
- audience: ["assistant"],
1367
- priority: 0.6
1368
- }
1369
- });
1370
- }
1371
-
1372
- if (suggestions.length) {
1373
- const plainLines = suggestions.map((entry, index) => {
1374
- const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
1375
- const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
1376
- return `- ${index + 1}. ${entry.uri}${sampleNote}`;
1377
- });
1378
- content.push({
1379
- type: "text",
1380
- text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
1381
- });
1382
- }
1383
-
1384
- return { isError: true, content };
1385
- }
1386
-
1387
- const textExtensions = [
1388
- ".java", ".kt", ".swift", ".m", ".h", ".xml", ".gradle", ".properties",
1389
- ".pro", ".json", ".plist", ".storyboard", ".xib", ".gitignore", ".md",
1390
- ".js", ".jsx", ".ts", ".tsx", ".vue", ".cjs", ".html", ".css"
1391
- ];
1392
-
1393
- const files = [];
1394
- const stat = statSync(samplePath);
1395
- const rootDir = stat.isDirectory() ? samplePath : dirname(samplePath);
1396
-
1397
- function addFile(fullPath) {
1398
- const ext = "." + fullPath.split(".").pop();
1399
- const baseName = fullPath.split(/[\\/]/).pop();
1400
- if (!textExtensions.includes(ext) && !["gradlew", "Podfile"].includes(baseName)) {
1401
- return;
1402
- }
1403
- try {
1404
- const content = readFileSync(fullPath, "utf-8");
1405
- const normalized = content.replace(/\r\n/g, "\n");
1406
- files.push({
1407
- path: relative(rootDir, fullPath),
1408
- content: normalized,
1409
- ext: ext.replace(".", "")
1410
- });
1411
- } catch (e) {
1412
- // Ignore binary or unreadable files
1413
- }
1414
- }
1415
-
1416
- function walk(dir) {
1417
- const entries = readdirSync(dir);
1418
- for (const entry of entries) {
1419
- if (["build", ".gradle", ".idea", ".git", "node_modules", "Pods", "DerivedData", "__pycache__"].includes(entry)) {
1420
- continue;
1421
- }
1422
- const fullPath = join(dir, entry);
1423
- const entryStat = statSync(fullPath);
1424
- if (entryStat.isDirectory()) {
1425
- walk(fullPath);
1426
- } else {
1427
- addFile(fullPath);
1428
- }
1429
- }
1430
- }
1431
-
1432
- if (stat.isDirectory()) {
1433
- walk(samplePath);
1434
- } else {
1435
- addFile(samplePath);
1436
- }
1437
-
1438
- const validFiles = files.filter((f) => f.content.length < 50000);
1439
-
1440
- const output = [
1441
- `# Project Generation: ${sampleLabel}`,
1442
- "",
1443
- "This output contains the file structure for the project.",
1444
- "Note: This tool returns files inline and does not create a downloadable zip.",
1445
- ""
1446
- ];
1447
-
1448
- for (const file of validFiles) {
1449
- output.push(`## ${file.path}`);
1450
- output.push("```" + (file.ext || "text"));
1451
- output.push(file.content);
1452
- output.push("```");
1453
- output.push("");
1454
- }
1455
-
1456
- return { content: [{ type: "text", text: output.join("\n") }] };
1457
- }
1458
- );
1459
- // ============================================================================
1460
- // MCP Resources (tool-discovered, lazy-read)
1461
- // ============================================================================
1462
-
1463
- server.server.registerCapabilities({
1464
- resources: {
1465
- listChanged: false,
1466
- subscribe: true
1467
- }
25
+ logEvent("profile", "resolved", {
26
+ profile: ragApi.ragConfig.profile,
27
+ provider: ragApi.ragConfig.provider,
28
+ provider_source: ragApi.ragConfig.providerSource,
29
+ fallback: ragApi.ragConfig.fallback,
30
+ fallback_source: ragApi.ragConfig.fallbackSource
1468
31
  });
1469
32
 
1470
- server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
1471
- // Only surface a small, pinned set to avoid bloating the context window.
1472
- const resources = getPinnedResources().map((r) => ({
1473
- uri: r.uri,
1474
- name: r.title,
1475
- description: r.summary,
1476
- mimeType: r.mimeType
1477
- }));
1478
- return { resources };
33
+ const createServer = () => createMcpServerInstance({
34
+ pkgVersion: pkg.version,
35
+ resourceIndexApi,
36
+ ragApi
1479
37
  });
1480
38
 
1481
- server.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
1482
- const parsed = parseResourceUri(request.params.uri);
1483
- if (parsed && ["dcv", "dbr", "dwt", "ddv"].includes(parsed.product)) {
1484
- const policy = ensureLatestMajor({
1485
- product: parsed.product,
1486
- version: parsed.version,
1487
- edition: parsed.edition,
1488
- platform: parsed.platform
1489
- });
1490
- if (!policy.ok) {
1491
- throw new Error(policy.message);
1492
- }
1493
- }
1494
- const resource = await readResourceContent(request.params.uri);
1495
- if (!resource) {
1496
- throw new Error(`Resource not found: ${request.params.uri}`);
39
+ async function maybePrewarm() {
40
+ if (!ragApi.ragConfig.prewarm) return;
41
+ if (ragApi.ragConfig.prewarmBlock) {
42
+ await ragApi.prewarmRagIndex();
43
+ } else {
44
+ void ragApi.prewarmRagIndex();
1497
45
  }
1498
- return { contents: [resource] };
1499
- });
1500
-
1501
- server.server.setRequestHandler(SubscribeRequestSchema, async () => ({}));
1502
- server.server.setRequestHandler(UnsubscribeRequestSchema, async () => ({}));
1503
-
1504
- // ============================================================================
1505
- // Start Server
1506
- // ============================================================================
46
+ }
1507
47
 
1508
- const transport = new StdioServerTransport();
1509
- await server.connect(transport);
48
+ let runtimeConfig;
49
+ try {
50
+ runtimeConfig = resolveRuntimeConfig();
51
+ } catch (error) {
52
+ const message = error instanceof Error ? error.message : String(error);
53
+ logEvent("transport", "config_error", { message }, { level: "error" });
54
+ process.exit(1);
55
+ }
1510
56
 
1511
- if (ragConfig.prewarm) {
1512
- if (ragConfig.prewarmBlock) {
1513
- await prewarmRagIndex();
1514
- } else {
1515
- void prewarmRagIndex();
1516
- }
57
+ if (runtimeConfig.transport === "http") {
58
+ await startHttpServer({
59
+ host: runtimeConfig.host,
60
+ port: runtimeConfig.port,
61
+ mcpPath: MCP_HTTP_PATH,
62
+ createServer
63
+ });
64
+ } else {
65
+ await startStdioServer({ createServer });
1517
66
  }
1518
67
 
68
+ await maybePrewarm();