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
@@ -19,6 +19,13 @@ let cachedDbrWebFrameworkPlatforms = null;
19
19
  let cachedDdvWebFrameworkPlatforms = null;
20
20
  let cachedDcvWebFrameworkPlatforms = null;
21
21
 
22
+ function resetSampleDiscoveryCaches() {
23
+ cachedWebFrameworkPlatforms = null;
24
+ cachedDbrWebFrameworkPlatforms = null;
25
+ cachedDdvWebFrameworkPlatforms = null;
26
+ cachedDcvWebFrameworkPlatforms = null;
27
+ }
28
+
22
29
  function getCodeFileExtensions() {
23
30
  return CODE_FILE_EXTENSIONS;
24
31
  }
@@ -719,5 +726,6 @@ export {
719
726
  getMainCodeFile,
720
727
  getMimeTypeForExtension,
721
728
  getDbrServerSampleContent,
722
- getDcvServerSampleContent
729
+ getDcvServerSampleContent,
730
+ resetSampleDiscoveryCaches
723
731
  };
@@ -53,7 +53,8 @@ import {
53
53
  getMainCodeFile,
54
54
  getMimeTypeForExtension,
55
55
  getDbrServerSampleContent,
56
- getDcvServerSampleContent
56
+ getDcvServerSampleContent,
57
+ resetSampleDiscoveryCaches
57
58
  } from "./resource-index/samples.js";
58
59
  import { parseResourceUri, parseSampleUri, getSampleIdFromUri } from "./resource-index/uri.js";
59
60
  import {
@@ -126,95 +127,107 @@ function inferDcvServerPlatform(articlePath) {
126
127
  return "server";
127
128
  }
128
129
 
129
- const dbrWebDocs = withEditionScope(
130
- loadMarkdownDocs({
131
- rootDir: DOC_ROOTS.dbrWeb,
132
- urlBase: DOCS_CONFIG.dbrWeb.urlBase,
133
- excludeDirs: DOCS_CONFIG.dbrWeb.excludeDirs,
134
- excludeFiles: DOCS_CONFIG.dbrWeb.excludeFiles
135
- }).articles,
136
- "web",
137
- () => "web"
138
- );
139
-
140
- const dbrMobileDocs = withEditionScope(
141
- loadMarkdownDocs({
142
- rootDir: DOC_ROOTS.dbrMobile,
143
- urlBase: DOCS_CONFIG.dbrMobile.urlBase,
144
- excludeDirs: DOCS_CONFIG.dbrMobile.excludeDirs,
145
- excludeFiles: DOCS_CONFIG.dbrMobile.excludeFiles
146
- }).articles,
147
- "mobile",
148
- inferDbrMobilePlatform
149
- );
150
-
151
- const dbrServerDocs = withEditionScope(
152
- loadMarkdownDocs({
153
- rootDir: DOC_ROOTS.dbrServer,
154
- urlBase: DOCS_CONFIG.dbrServer.urlBase,
155
- excludeDirs: DOCS_CONFIG.dbrServer.excludeDirs,
156
- excludeFiles: DOCS_CONFIG.dbrServer.excludeFiles
157
- }).articles,
158
- "server",
159
- inferDbrServerPlatform
160
- );
161
-
162
- const dcvCoreDocs = withEditionScope(
163
- loadMarkdownDocs({
164
- rootDir: DOC_ROOTS.dcvCore,
165
- urlBase: DOCS_CONFIG.dcvCore.urlBase,
166
- excludeDirs: DOCS_CONFIG.dcvCore.excludeDirs,
167
- excludeFiles: DOCS_CONFIG.dcvCore.excludeFiles
168
- }).articles,
169
- "core",
170
- () => "core"
171
- );
172
-
173
- const dcvWebDocs = withEditionScope(
174
- loadMarkdownDocs({
175
- rootDir: DOC_ROOTS.dcvWeb,
176
- urlBase: DOCS_CONFIG.dcvWeb.urlBase,
177
- excludeDirs: DOCS_CONFIG.dcvWeb.excludeDirs,
178
- excludeFiles: DOCS_CONFIG.dcvWeb.excludeFiles
179
- }).articles,
180
- "web",
181
- () => "web"
182
- );
183
-
184
- const dcvMobileDocs = withEditionScope(
185
- loadMarkdownDocs({
186
- rootDir: DOC_ROOTS.dcvMobile,
187
- urlBase: DOCS_CONFIG.dcvMobile.urlBase,
188
- excludeDirs: DOCS_CONFIG.dcvMobile.excludeDirs,
189
- excludeFiles: DOCS_CONFIG.dcvMobile.excludeFiles
190
- }).articles,
191
- "mobile",
192
- inferDcvMobilePlatform
193
- );
194
-
195
- const dcvServerDocs = withEditionScope(
196
- loadMarkdownDocs({
197
- rootDir: DOC_ROOTS.dcvServer,
198
- urlBase: DOCS_CONFIG.dcvServer.urlBase,
199
- excludeDirs: DOCS_CONFIG.dcvServer.excludeDirs,
200
- excludeFiles: DOCS_CONFIG.dcvServer.excludeFiles
201
- }).articles,
202
- "server",
203
- inferDcvServerPlatform
204
- );
205
-
206
- const dwtDocs = loadMarkdownDocs({
207
- rootDir: DOC_ROOTS.dwtArticles,
208
- urlBase: DOCS_CONFIG.dwt.urlBase,
209
- includeDirNames: DOCS_CONFIG.dwt.includeDirNames
210
- });
211
-
212
- const ddvDocs = loadMarkdownDocs({
213
- rootDir: DOC_ROOTS.ddv,
214
- urlBase: DOCS_CONFIG.ddv.urlBase,
215
- excludeDirs: DOCS_CONFIG.ddv.excludeDirs,
216
- excludeFiles: DOCS_CONFIG.ddv.excludeFiles
217
- });
130
+ let dbrWebDocs = [];
131
+ let dbrMobileDocs = [];
132
+ let dbrServerDocs = [];
133
+ let dcvCoreDocs = [];
134
+ let dcvWebDocs = [];
135
+ let dcvMobileDocs = [];
136
+ let dcvServerDocs = [];
137
+ let dwtDocs = { articles: [] };
138
+ let ddvDocs = { articles: [] };
139
+
140
+ function loadDocumentationSets() {
141
+ dbrWebDocs = withEditionScope(
142
+ loadMarkdownDocs({
143
+ rootDir: DOC_ROOTS.dbrWeb,
144
+ urlBase: DOCS_CONFIG.dbrWeb.urlBase,
145
+ excludeDirs: DOCS_CONFIG.dbrWeb.excludeDirs,
146
+ excludeFiles: DOCS_CONFIG.dbrWeb.excludeFiles
147
+ }).articles,
148
+ "web",
149
+ () => "web"
150
+ );
151
+
152
+ dbrMobileDocs = withEditionScope(
153
+ loadMarkdownDocs({
154
+ rootDir: DOC_ROOTS.dbrMobile,
155
+ urlBase: DOCS_CONFIG.dbrMobile.urlBase,
156
+ excludeDirs: DOCS_CONFIG.dbrMobile.excludeDirs,
157
+ excludeFiles: DOCS_CONFIG.dbrMobile.excludeFiles
158
+ }).articles,
159
+ "mobile",
160
+ inferDbrMobilePlatform
161
+ );
162
+
163
+ dbrServerDocs = withEditionScope(
164
+ loadMarkdownDocs({
165
+ rootDir: DOC_ROOTS.dbrServer,
166
+ urlBase: DOCS_CONFIG.dbrServer.urlBase,
167
+ excludeDirs: DOCS_CONFIG.dbrServer.excludeDirs,
168
+ excludeFiles: DOCS_CONFIG.dbrServer.excludeFiles
169
+ }).articles,
170
+ "server",
171
+ inferDbrServerPlatform
172
+ );
173
+
174
+ dcvCoreDocs = withEditionScope(
175
+ loadMarkdownDocs({
176
+ rootDir: DOC_ROOTS.dcvCore,
177
+ urlBase: DOCS_CONFIG.dcvCore.urlBase,
178
+ excludeDirs: DOCS_CONFIG.dcvCore.excludeDirs,
179
+ excludeFiles: DOCS_CONFIG.dcvCore.excludeFiles
180
+ }).articles,
181
+ "core",
182
+ () => "core"
183
+ );
184
+
185
+ dcvWebDocs = withEditionScope(
186
+ loadMarkdownDocs({
187
+ rootDir: DOC_ROOTS.dcvWeb,
188
+ urlBase: DOCS_CONFIG.dcvWeb.urlBase,
189
+ excludeDirs: DOCS_CONFIG.dcvWeb.excludeDirs,
190
+ excludeFiles: DOCS_CONFIG.dcvWeb.excludeFiles
191
+ }).articles,
192
+ "web",
193
+ () => "web"
194
+ );
195
+
196
+ dcvMobileDocs = withEditionScope(
197
+ loadMarkdownDocs({
198
+ rootDir: DOC_ROOTS.dcvMobile,
199
+ urlBase: DOCS_CONFIG.dcvMobile.urlBase,
200
+ excludeDirs: DOCS_CONFIG.dcvMobile.excludeDirs,
201
+ excludeFiles: DOCS_CONFIG.dcvMobile.excludeFiles
202
+ }).articles,
203
+ "mobile",
204
+ inferDcvMobilePlatform
205
+ );
206
+
207
+ dcvServerDocs = withEditionScope(
208
+ loadMarkdownDocs({
209
+ rootDir: DOC_ROOTS.dcvServer,
210
+ urlBase: DOCS_CONFIG.dcvServer.urlBase,
211
+ excludeDirs: DOCS_CONFIG.dcvServer.excludeDirs,
212
+ excludeFiles: DOCS_CONFIG.dcvServer.excludeFiles
213
+ }).articles,
214
+ "server",
215
+ inferDcvServerPlatform
216
+ );
217
+
218
+ dwtDocs = loadMarkdownDocs({
219
+ rootDir: DOC_ROOTS.dwtArticles,
220
+ urlBase: DOCS_CONFIG.dwt.urlBase,
221
+ includeDirNames: DOCS_CONFIG.dwt.includeDirNames
222
+ });
223
+
224
+ ddvDocs = loadMarkdownDocs({
225
+ rootDir: DOC_ROOTS.ddv,
226
+ urlBase: DOCS_CONFIG.ddv.urlBase,
227
+ excludeDirs: DOCS_CONFIG.ddv.excludeDirs,
228
+ excludeFiles: DOCS_CONFIG.ddv.excludeFiles
229
+ });
230
+ }
218
231
 
219
232
  const dbrServerSdk = registry.sdks["dbr-server"];
220
233
  const dcvMobileSdk = registry.sdks["dcv-mobile"];
@@ -261,6 +274,7 @@ function buildVersionPolicyText() {
261
274
  }
262
275
 
263
276
  const resourceIndex = [];
277
+ const resourceIndexByUri = new Map();
264
278
 
265
279
  function addResourceToIndex(entry) {
266
280
  resourceIndex.push(entry);
@@ -340,9 +354,19 @@ function buildResourceIndex() {
340
354
  });
341
355
  }
342
356
 
343
- buildResourceIndex();
357
+ function refreshResourceIndex() {
358
+ resetSampleDiscoveryCaches();
359
+ loadDocumentationSets();
360
+ resourceIndex.length = 0;
361
+ buildResourceIndex();
362
+ resourceIndexByUri.clear();
363
+ for (const entry of resourceIndex) {
364
+ resourceIndexByUri.set(entry.uri, entry);
365
+ }
366
+ return { resourceCount: resourceIndex.length };
367
+ }
344
368
 
345
- const resourceIndexByUri = new Map(resourceIndex.map((entry) => [entry.uri, entry]));
369
+ refreshResourceIndex();
346
370
 
347
371
  function editionMatches(normalizedEdition, entryEdition) {
348
372
  if (!normalizedEdition) return true;
@@ -399,9 +423,49 @@ function getPinnedResources() {
399
423
  return resourceIndex.filter((entry) => entry.pinned);
400
424
  }
401
425
 
426
+ function safeDecodeURIComponent(value) {
427
+ try {
428
+ return decodeURIComponent(value);
429
+ } catch {
430
+ return value;
431
+ }
432
+ }
433
+
434
+ function buildResourceLookupCandidates(uri) {
435
+ const candidates = [];
436
+ if (typeof uri !== "string" || uri.length === 0) return candidates;
437
+ candidates.push(uri);
438
+
439
+ if (!uri.includes("://")) return candidates;
440
+ const [scheme, rest] = uri.split("://");
441
+ if (scheme !== "doc") return candidates;
442
+
443
+ const parts = String(rest || "").split("/").filter(Boolean);
444
+ if (parts.length < 5) return candidates;
445
+
446
+ const head = parts.slice(0, 4);
447
+ const slugRaw = parts.slice(4).join("/");
448
+ const decodedOnce = safeDecodeURIComponent(slugRaw);
449
+ const decodedTwice = safeDecodeURIComponent(decodedOnce);
450
+
451
+ for (const slug of [decodedOnce, decodedTwice]) {
452
+ const canonical = `${scheme}://${head.join("/")}/${encodeURIComponent(slug)}`;
453
+ if (!candidates.includes(canonical)) {
454
+ candidates.push(canonical);
455
+ }
456
+ }
457
+
458
+ return candidates;
459
+ }
460
+
402
461
  async function readResourceContent(uri) {
403
- const resource = resourceIndexByUri.get(uri);
462
+ let resource = null;
463
+ for (const candidate of buildResourceLookupCandidates(uri)) {
464
+ resource = resourceIndexByUri.get(candidate);
465
+ if (resource) break;
466
+ }
404
467
  if (!resource) return null;
468
+
405
469
  const content = await resource.loadContent();
406
470
  return {
407
471
  uri,
@@ -515,6 +579,7 @@ export {
515
579
  buildVersionPolicyText,
516
580
  buildIndexData,
517
581
  buildResourceIndex,
582
+ refreshResourceIndex,
518
583
  editionMatches,
519
584
  platformMatches,
520
585
  getDisplayEdition,
@@ -0,0 +1,56 @@
1
+ import {
2
+ ListResourcesRequestSchema,
3
+ ReadResourceRequestSchema,
4
+ SubscribeRequestSchema,
5
+ UnsubscribeRequestSchema
6
+ } from "@modelcontextprotocol/sdk/types.js";
7
+
8
+ export function registerResourceHandlers({
9
+ server,
10
+ getPinnedResources,
11
+ parseResourceUri,
12
+ ensureLatestMajor,
13
+ readResourceContent
14
+ }) {
15
+ server.server.registerCapabilities({
16
+ resources: {
17
+ listChanged: false,
18
+ subscribe: true
19
+ }
20
+ });
21
+
22
+ server.server.setRequestHandler(ListResourcesRequestSchema, async () => {
23
+ const resources = getPinnedResources().map((entry) => ({
24
+ uri: entry.uri,
25
+ name: entry.title,
26
+ description: entry.summary,
27
+ mimeType: entry.mimeType
28
+ }));
29
+ return { resources };
30
+ });
31
+
32
+ server.server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
33
+ const parsed = parseResourceUri(request.params.uri);
34
+ if (parsed && ["dcv", "dbr", "dwt", "ddv"].includes(parsed.product)) {
35
+ const policy = ensureLatestMajor({
36
+ product: parsed.product,
37
+ version: parsed.version,
38
+ edition: parsed.edition,
39
+ platform: parsed.platform
40
+ });
41
+ if (!policy.ok) {
42
+ throw new Error(policy.message);
43
+ }
44
+ }
45
+
46
+ const resource = await readResourceContent(request.params.uri);
47
+ if (!resource) {
48
+ throw new Error(`Resource not found: ${request.params.uri}`);
49
+ }
50
+
51
+ return { contents: [resource] };
52
+ });
53
+
54
+ server.server.setRequestHandler(SubscribeRequestSchema, async () => ({}));
55
+ server.server.setRequestHandler(UnsubscribeRequestSchema, async () => ({}));
56
+ }
@@ -0,0 +1,66 @@
1
+ const DEFAULT_TRANSPORT = "stdio";
2
+ const DEFAULT_HTTP_HOST = "127.0.0.1";
3
+ const DEFAULT_HTTP_PORT = 3333;
4
+ const SUPPORTED_TRANSPORTS = new Set(["stdio", "http"]);
5
+
6
+ const MCP_HTTP_PATH = "/mcp";
7
+
8
+ function parseCliArgs(argv) {
9
+ const parsed = {};
10
+ for (let i = 0; i < argv.length; i += 1) {
11
+ const token = argv[i];
12
+ if (!token.startsWith("--")) continue;
13
+ const option = token.slice(2);
14
+ if (!option) continue;
15
+
16
+ const equalsIndex = option.indexOf("=");
17
+ if (equalsIndex >= 0) {
18
+ const key = option.slice(0, equalsIndex);
19
+ const value = option.slice(equalsIndex + 1);
20
+ if (key) parsed[key] = value;
21
+ continue;
22
+ }
23
+
24
+ const next = argv[i + 1];
25
+ if (next && !next.startsWith("--")) {
26
+ parsed[option] = next;
27
+ i += 1;
28
+ continue;
29
+ }
30
+
31
+ parsed[option] = "true";
32
+ }
33
+
34
+ return parsed;
35
+ }
36
+
37
+ function resolveRuntimeConfig(argv = process.argv.slice(2)) {
38
+ const parsed = parseCliArgs(argv);
39
+ const transport = String(parsed.transport || DEFAULT_TRANSPORT).toLowerCase();
40
+
41
+ if (!SUPPORTED_TRANSPORTS.has(transport)) {
42
+ throw new Error(`Invalid --transport "${transport}". Expected "stdio" or "http".`);
43
+ }
44
+
45
+ if (transport === "http") {
46
+ const host = String(parsed.host || DEFAULT_HTTP_HOST);
47
+ const portRaw = String(parsed.port || String(DEFAULT_HTTP_PORT));
48
+ const port = Number.parseInt(portRaw, 10);
49
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
50
+ throw new Error(`Invalid --port "${portRaw}". Expected an integer between 1 and 65535.`);
51
+ }
52
+ return { transport, host, port };
53
+ }
54
+
55
+ return {
56
+ transport,
57
+ host: DEFAULT_HTTP_HOST,
58
+ port: DEFAULT_HTTP_PORT
59
+ };
60
+ }
61
+
62
+ export {
63
+ MCP_HTTP_PATH,
64
+ parseCliArgs,
65
+ resolveRuntimeConfig
66
+ };
@@ -0,0 +1,130 @@
1
+ import { z } from "zod";
2
+ import { formatScoreLabel, formatScoreNote } from "../helpers/server-helpers.js";
3
+
4
+ export function registerIndexTools({
5
+ server,
6
+ ensureScopeHydrated,
7
+ ensureLatestMajor,
8
+ normalizeProduct,
9
+ normalizePlatform,
10
+ normalizeEdition,
11
+ buildIndexData,
12
+ getSampleIdFromUri,
13
+ formatScopeLabel,
14
+ searchResources
15
+ }) {
16
+ server.registerTool(
17
+ "get_index",
18
+ {
19
+ title: "Get Index",
20
+ description: "Get a compact index of products, editions, versions, samples/docs, plus DBR-vs-DCV selection guidance.",
21
+ inputSchema: {}
22
+ },
23
+ async () => ({
24
+ content: [{ type: "text", text: JSON.stringify(buildIndexData(), null, 2) }]
25
+ })
26
+ );
27
+
28
+ server.registerTool(
29
+ "search",
30
+ {
31
+ title: "Search",
32
+ 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.",
33
+ inputSchema: {
34
+ query: z.string().describe("Keywords to search across docs and samples."),
35
+ product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
36
+ edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
37
+ platform: z.string().optional().describe("Platform: android, ios, maui, react-native, flutter, js, python, cpp, java, dotnet, nodejs, angular, blazor, capacitor, electron, es6, native-ts, next, nuxt, pwa, react, requirejs, svelte, vue, webview, spm, core"),
38
+ version: z.string().optional().describe("Version constraint (major or full version)"),
39
+ type: z.enum(["doc", "sample", "index", "policy", "any"]).optional(),
40
+ limit: z.number().int().min(1).max(10).optional().describe("Max results (default 5)")
41
+ }
42
+ },
43
+ async ({ query, product, edition, platform, version, type, limit }) => {
44
+ if (!query || !query.trim()) {
45
+ return { isError: true, content: [{ type: "text", text: "Query is required." }] };
46
+ }
47
+ const normalizedProduct = normalizeProduct(product);
48
+ const normalizedPlatform = normalizePlatform(platform);
49
+ const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
50
+
51
+ await ensureScopeHydrated({
52
+ product: normalizedProduct,
53
+ edition: normalizedEdition,
54
+ platform: normalizedPlatform,
55
+ type: type || "any"
56
+ });
57
+
58
+ const policy = ensureLatestMajor({
59
+ product: normalizedProduct,
60
+ version,
61
+ query,
62
+ edition: normalizedEdition,
63
+ platform: normalizedPlatform
64
+ });
65
+
66
+ if (!policy.ok) {
67
+ return { isError: true, content: [{ type: "text", text: policy.message }] };
68
+ }
69
+
70
+ const maxResults = Math.min(limit || 5, 10);
71
+ const topResults = await searchResources({
72
+ query,
73
+ product: normalizedProduct,
74
+ edition: normalizedEdition,
75
+ platform: normalizedPlatform,
76
+ type: type || "any",
77
+ limit: maxResults
78
+ });
79
+
80
+ if (topResults.length === 0) {
81
+ return {
82
+ content: [{
83
+ type: "text",
84
+ text: `No results for "${query}". Try get_index for available products or adjust filters.`
85
+ }]
86
+ };
87
+ }
88
+
89
+ const content = [
90
+ {
91
+ type: "text",
92
+ text: `Found ${topResults.length} result(s) for "${query}". Read the links you need with resources/read.`
93
+ }
94
+ ];
95
+
96
+ for (const entry of topResults) {
97
+ const versionLabel = entry.version ? `v${entry.version}` : "n/a";
98
+ const scopeLabel = formatScopeLabel(entry);
99
+ const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
100
+ const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
101
+ const scoreLabel = formatScoreLabel(entry);
102
+ content.push({
103
+ type: "resource_link",
104
+ uri: entry.uri,
105
+ name: entry.title,
106
+ description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${scoreLabel} - ${entry.summary}${sampleHint}`,
107
+ mimeType: entry.mimeType,
108
+ annotations: {
109
+ audience: ["assistant"],
110
+ priority: 0.8
111
+ }
112
+ });
113
+ }
114
+
115
+ const plainLines = topResults.map((entry, index) => {
116
+ const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
117
+ const action = entry.type === "sample" ? "generate_project resource_uri" : "resources/read uri";
118
+ const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
119
+ const scoreNote = formatScoreNote(entry);
120
+ return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote} (${action})`;
121
+ });
122
+ content.push({
123
+ type: "text",
124
+ text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
125
+ });
126
+
127
+ return { content };
128
+ }
129
+ );
130
+ }