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/index.js
CHANGED
|
@@ -1,1518 +1,68 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { readFileSync
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
85
|
-
|
|
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
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
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
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
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
|
-
|
|
1482
|
-
|
|
1483
|
-
if (
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
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
|
-
|
|
1499
|
-
});
|
|
1500
|
-
|
|
1501
|
-
server.server.setRequestHandler(SubscribeRequestSchema, async () => ({}));
|
|
1502
|
-
server.server.setRequestHandler(UnsubscribeRequestSchema, async () => ({}));
|
|
1503
|
-
|
|
1504
|
-
// ============================================================================
|
|
1505
|
-
// Start Server
|
|
1506
|
-
// ============================================================================
|
|
46
|
+
}
|
|
1507
47
|
|
|
1508
|
-
|
|
1509
|
-
|
|
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 (
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
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();
|