simple-dynamsoft-mcp 7.0.0 → 7.1.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/README.md +17 -7
- package/data/metadata/data-manifest.json +4 -4
- package/package.json +1 -1
- package/src/server/create-server.js +5 -8
- package/src/server/tools/register-index-tools.js +117 -4
- package/src/server/tools/register-project-tools.js +6 -6
- package/src/server/tools/register-sample-tools.js +1 -238
package/README.md
CHANGED
|
@@ -223,13 +223,23 @@ Location:
|
|
|
223
223
|
|
|
224
224
|
The server exposes this minimal tool surface:
|
|
225
225
|
|
|
226
|
-
- `get_index`
|
|
227
|
-
- `search`
|
|
228
|
-
- `list_samples`
|
|
229
|
-
- `
|
|
230
|
-
- `
|
|
231
|
-
- `
|
|
232
|
-
|
|
226
|
+
- `get_index` -- compact product/version/sample index with selection guidance
|
|
227
|
+
- `search` -- semantic search across docs and samples (also accepts exact sample IDs)
|
|
228
|
+
- `list_samples` -- browse available samples for a product/edition/platform
|
|
229
|
+
- `resolve_version` -- resolve current version for a product/edition/platform
|
|
230
|
+
- `get_quickstart` -- opinionated quickstart: picks a sample by scenario, returns code + install instructions
|
|
231
|
+
- `get_sample_files` -- get full project files for a known sample (discovered via list_samples or search)
|
|
232
|
+
|
|
233
|
+
## Companion: Dynamsoft SDK Skills
|
|
234
|
+
|
|
235
|
+
For AI agents that support skills (Claude Code, OpenCode, Codex), install [dynamsoft-sdk-skills](https://github.com/user/dynamsoft-sdk-skills) for guided integration workflows:
|
|
236
|
+
|
|
237
|
+
npx dynamsoft-sdk-skills install --all
|
|
238
|
+
|
|
239
|
+
- **Skills** provide integration patterns, gotchas, and decision trees (loaded into agent context)
|
|
240
|
+
- **MCP Server** provides runtime tools: version resolution, doc search, sample browsing, and retrieval of full sample project files
|
|
241
|
+
|
|
242
|
+
Both work independently, but together the skills guide agents to use MCP tools at the right moments.
|
|
233
243
|
|
|
234
244
|
## Quick Troubleshooting
|
|
235
245
|
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"branch": "main",
|
|
9
9
|
"owner": "dynamsoft-docs",
|
|
10
10
|
"repo": "barcode-reader-docs-js",
|
|
11
|
-
"commit": "
|
|
12
|
-
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/barcode-reader-docs-js/zip/
|
|
11
|
+
"commit": "acab784e29ff035b355dcd6d6ebe284fcb34eef3",
|
|
12
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/barcode-reader-docs-js/zip/acab784e29ff035b355dcd6d6ebe284fcb34eef3"
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"name": "data/documentation/barcode-reader-docs-mobile",
|
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"branch": "master",
|
|
89
89
|
"owner": "dynamsoft-docs",
|
|
90
90
|
"repo": "web-twain-docs",
|
|
91
|
-
"commit": "
|
|
92
|
-
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/
|
|
91
|
+
"commit": "779f3794064a224c3a26740470250389ca9aa0ce",
|
|
92
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/779f3794064a224c3a26740470250389ca9aa0ce"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"name": "data/samples/dynamic-web-twain",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-dynamsoft-mcp",
|
|
3
|
-
"version": "7.
|
|
3
|
+
"version": "7.1.0",
|
|
4
4
|
"description": "MCP server for Dynamsoft SDKs - Capture Vision, Barcode Reader (Mobile/Python/Web), Dynamic Web TWAIN, and Document Viewer. Provides documentation, code snippets, and API guidance.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -75,7 +75,10 @@ registerIndexTools({
|
|
|
75
75
|
buildIndexData,
|
|
76
76
|
getSampleIdFromUri,
|
|
77
77
|
formatScopeLabel,
|
|
78
|
-
searchResources
|
|
78
|
+
searchResources,
|
|
79
|
+
normalizeSampleName,
|
|
80
|
+
getSampleEntries,
|
|
81
|
+
getSampleSuggestions
|
|
79
82
|
});
|
|
80
83
|
|
|
81
84
|
registerSampleTools({
|
|
@@ -85,16 +88,10 @@ registerSampleTools({
|
|
|
85
88
|
normalizeProduct,
|
|
86
89
|
normalizePlatform,
|
|
87
90
|
normalizeEdition,
|
|
88
|
-
normalizeSampleName,
|
|
89
|
-
parseSampleUri,
|
|
90
|
-
resourceIndex,
|
|
91
91
|
getSampleEntries,
|
|
92
92
|
getSampleIdFromUri,
|
|
93
93
|
getDisplayEdition,
|
|
94
|
-
getDisplayPlatform
|
|
95
|
-
formatScopeLabel,
|
|
96
|
-
searchResources,
|
|
97
|
-
getSampleSuggestions
|
|
94
|
+
getDisplayPlatform
|
|
98
95
|
});
|
|
99
96
|
|
|
100
97
|
registerVersionTools({
|
|
@@ -11,7 +11,10 @@ export function registerIndexTools({
|
|
|
11
11
|
buildIndexData,
|
|
12
12
|
getSampleIdFromUri,
|
|
13
13
|
formatScopeLabel,
|
|
14
|
-
searchResources
|
|
14
|
+
searchResources,
|
|
15
|
+
normalizeSampleName,
|
|
16
|
+
getSampleEntries,
|
|
17
|
+
getSampleSuggestions
|
|
15
18
|
}) {
|
|
16
19
|
server.registerTool(
|
|
17
20
|
"get_index",
|
|
@@ -29,7 +32,7 @@ export function registerIndexTools({
|
|
|
29
32
|
"search",
|
|
30
33
|
{
|
|
31
34
|
title: "Search",
|
|
32
|
-
description: "
|
|
35
|
+
description: "Search across docs and samples with semantic (RAG) search and fuzzy fallback. Accepts keywords or exact sample IDs. Returns resource links for lazy loading. Prefer DCV for MRZ/VIN/document-normalization/driver-license scenarios; DBR for barcode-only.",
|
|
33
36
|
inputSchema: {
|
|
34
37
|
query: z.string().describe("Keywords to search across docs and samples."),
|
|
35
38
|
product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
|
|
@@ -68,16 +71,126 @@ export function registerIndexTools({
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
const maxResults = Math.min(limit || 5, 10);
|
|
74
|
+
|
|
75
|
+
// Exact sample-ID fast path (when searching for samples)
|
|
76
|
+
const effectiveType = type || "any";
|
|
77
|
+
if (effectiveType === "sample" || effectiveType === "any") {
|
|
78
|
+
const normalizedQuery = normalizeSampleName(query);
|
|
79
|
+
const scopedSamples = getSampleEntries({
|
|
80
|
+
product: normalizedProduct,
|
|
81
|
+
edition: normalizedEdition,
|
|
82
|
+
platform: normalizedPlatform
|
|
83
|
+
});
|
|
84
|
+
const exactMatches = scopedSamples.filter((entry) => {
|
|
85
|
+
const entryId = getSampleIdFromUri(entry.uri);
|
|
86
|
+
return entryId && entryId.toLowerCase() === normalizedQuery.toLowerCase();
|
|
87
|
+
});
|
|
88
|
+
if (exactMatches.length > 0) {
|
|
89
|
+
const selected = exactMatches.slice(0, maxResults);
|
|
90
|
+
const content = [
|
|
91
|
+
{
|
|
92
|
+
type: "text",
|
|
93
|
+
text: `Found ${selected.length} exact match(es) for "${query}". Read the links you need with resources/read.`
|
|
94
|
+
}
|
|
95
|
+
];
|
|
96
|
+
|
|
97
|
+
for (const entry of selected) {
|
|
98
|
+
const versionLabel = entry.version ? `v${entry.version}` : "n/a";
|
|
99
|
+
const scopeLabel = formatScopeLabel(entry);
|
|
100
|
+
const sampleId = getSampleIdFromUri(entry.uri);
|
|
101
|
+
const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
|
|
102
|
+
const scoreLabel = formatScoreLabel(entry);
|
|
103
|
+
content.push({
|
|
104
|
+
type: "resource_link",
|
|
105
|
+
uri: entry.uri,
|
|
106
|
+
name: entry.title,
|
|
107
|
+
description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${scoreLabel} - ${entry.summary}${sampleHint}`,
|
|
108
|
+
mimeType: entry.mimeType,
|
|
109
|
+
annotations: {
|
|
110
|
+
audience: ["assistant"],
|
|
111
|
+
priority: 0.8
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const plainLines = selected.map((entry, index) => {
|
|
117
|
+
const sampleId = getSampleIdFromUri(entry.uri);
|
|
118
|
+
const action = "get_sample_files resource_uri";
|
|
119
|
+
const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
|
|
120
|
+
const scoreNote = formatScoreNote(entry);
|
|
121
|
+
return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote} (${action})`;
|
|
122
|
+
});
|
|
123
|
+
content.push({
|
|
124
|
+
type: "text",
|
|
125
|
+
text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
return { content };
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
71
132
|
const topResults = await searchResources({
|
|
72
133
|
query,
|
|
73
134
|
product: normalizedProduct,
|
|
74
135
|
edition: normalizedEdition,
|
|
75
136
|
platform: normalizedPlatform,
|
|
76
|
-
type:
|
|
137
|
+
type: effectiveType,
|
|
77
138
|
limit: maxResults
|
|
78
139
|
});
|
|
79
140
|
|
|
80
141
|
if (topResults.length === 0) {
|
|
142
|
+
// Only try sample suggestions when searching samples or any type
|
|
143
|
+
if (effectiveType === "sample" || effectiveType === "any") {
|
|
144
|
+
const suggestions = await getSampleSuggestions({
|
|
145
|
+
query,
|
|
146
|
+
product: normalizedProduct,
|
|
147
|
+
edition: normalizedEdition,
|
|
148
|
+
platform: normalizedPlatform,
|
|
149
|
+
limit: maxResults
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (suggestions.length > 0) {
|
|
153
|
+
const content = [
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: `No exact results for "${query}". Related samples:`
|
|
157
|
+
}
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
for (const entry of suggestions) {
|
|
161
|
+
const versionLabel = entry.version ? `v${entry.version}` : "n/a";
|
|
162
|
+
const scopeLabel = formatScopeLabel(entry);
|
|
163
|
+
const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
|
|
164
|
+
const sampleHint = sampleId ? ` | sample_id: ${sampleId}` : "";
|
|
165
|
+
const scoreLabel = formatScoreLabel(entry);
|
|
166
|
+
content.push({
|
|
167
|
+
type: "resource_link",
|
|
168
|
+
uri: entry.uri,
|
|
169
|
+
name: entry.title,
|
|
170
|
+
description: `${entry.type.toUpperCase()} | ${scopeLabel} | ${versionLabel}${scoreLabel} - ${entry.summary}${sampleHint}`,
|
|
171
|
+
mimeType: entry.mimeType,
|
|
172
|
+
annotations: {
|
|
173
|
+
audience: ["assistant"],
|
|
174
|
+
priority: 0.6
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const plainLines = suggestions.map((entry, index) => {
|
|
180
|
+
const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
|
|
181
|
+
const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
|
|
182
|
+
const scoreNote = formatScoreNote(entry);
|
|
183
|
+
return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote}`;
|
|
184
|
+
});
|
|
185
|
+
content.push({
|
|
186
|
+
type: "text",
|
|
187
|
+
text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
return { content };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
81
194
|
return {
|
|
82
195
|
content: [{
|
|
83
196
|
type: "text",
|
|
@@ -114,7 +227,7 @@ export function registerIndexTools({
|
|
|
114
227
|
|
|
115
228
|
const plainLines = topResults.map((entry, index) => {
|
|
116
229
|
const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
|
|
117
|
-
const action = entry.type === "sample" ? "
|
|
230
|
+
const action = entry.type === "sample" ? "get_sample_files resource_uri" : "resources/read uri";
|
|
118
231
|
const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
|
|
119
232
|
const scoreNote = formatScoreNote(entry);
|
|
120
233
|
return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote} (${action})`;
|
|
@@ -27,10 +27,10 @@ export function registerProjectTools({
|
|
|
27
27
|
getSampleSuggestions
|
|
28
28
|
}) {
|
|
29
29
|
server.registerTool(
|
|
30
|
-
"
|
|
30
|
+
"get_sample_files",
|
|
31
31
|
{
|
|
32
|
-
title: "
|
|
33
|
-
description: "
|
|
32
|
+
title: "Get Sample Files",
|
|
33
|
+
description: "Get project files for a known sample (by sample_id or resource_uri) and return them inline. Use list_samples or search to discover sample IDs first.",
|
|
34
34
|
inputSchema: {
|
|
35
35
|
product: z.string().describe("Product: dcv, dbr, dwt, or ddv"),
|
|
36
36
|
edition: z.string().optional().describe("Edition: mobile, web, server/desktop"),
|
|
@@ -284,10 +284,10 @@ export function registerProjectTools({
|
|
|
284
284
|
const validFiles = files.filter((f) => f.content.length < 50000);
|
|
285
285
|
|
|
286
286
|
const output = [
|
|
287
|
-
`#
|
|
287
|
+
`# Sample Files: ${sampleLabel}`,
|
|
288
288
|
"",
|
|
289
|
-
"
|
|
290
|
-
"Note:
|
|
289
|
+
"Below are the retrieved sample project files.",
|
|
290
|
+
"Note: Files are returned inline and no downloadable zip is created.",
|
|
291
291
|
""
|
|
292
292
|
];
|
|
293
293
|
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { formatScoreLabel, formatScoreNote } from "../helpers/server-helpers.js";
|
|
3
2
|
|
|
4
3
|
export function registerSampleTools({
|
|
5
4
|
server,
|
|
@@ -8,16 +7,10 @@ export function registerSampleTools({
|
|
|
8
7
|
normalizeProduct,
|
|
9
8
|
normalizePlatform,
|
|
10
9
|
normalizeEdition,
|
|
11
|
-
normalizeSampleName,
|
|
12
|
-
parseSampleUri,
|
|
13
|
-
resourceIndex,
|
|
14
10
|
getSampleEntries,
|
|
15
11
|
getSampleIdFromUri,
|
|
16
12
|
getDisplayEdition,
|
|
17
|
-
getDisplayPlatform
|
|
18
|
-
formatScopeLabel,
|
|
19
|
-
searchResources,
|
|
20
|
-
getSampleSuggestions
|
|
13
|
+
getDisplayPlatform
|
|
21
14
|
}) {
|
|
22
15
|
server.registerTool(
|
|
23
16
|
"list_samples",
|
|
@@ -100,234 +93,4 @@ export function registerSampleTools({
|
|
|
100
93
|
};
|
|
101
94
|
}
|
|
102
95
|
);
|
|
103
|
-
|
|
104
|
-
server.registerTool(
|
|
105
|
-
"resolve_sample",
|
|
106
|
-
{
|
|
107
|
-
title: "Resolve Sample",
|
|
108
|
-
description: "Resolve a sample_id (or sample URI) to matching sample URIs.",
|
|
109
|
-
inputSchema: {
|
|
110
|
-
sample_id: z.string().describe("Sample identifier or sample:// URI"),
|
|
111
|
-
product: z.string().optional().describe("Product: dcv, dbr, dwt, ddv"),
|
|
112
|
-
edition: z.string().optional().describe("Edition: core, mobile, web, server/desktop"),
|
|
113
|
-
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"),
|
|
114
|
-
limit: z.number().int().min(1).max(10).optional().describe("Max results (default 5)")
|
|
115
|
-
}
|
|
116
|
-
},
|
|
117
|
-
async ({ sample_id, product, edition, platform, limit }) => {
|
|
118
|
-
if (!sample_id || !sample_id.trim()) {
|
|
119
|
-
return { isError: true, content: [{ type: "text", text: "sample_id is required." }] };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
const normalizedProduct = normalizeProduct(product);
|
|
123
|
-
const normalizedPlatform = normalizePlatform(platform);
|
|
124
|
-
const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
|
|
125
|
-
|
|
126
|
-
await ensureScopeHydrated({
|
|
127
|
-
product: normalizedProduct,
|
|
128
|
-
edition: normalizedEdition,
|
|
129
|
-
platform: normalizedPlatform,
|
|
130
|
-
type: "sample"
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const policy = ensureLatestMajor({
|
|
134
|
-
product: normalizedProduct,
|
|
135
|
-
version: undefined,
|
|
136
|
-
query: sample_id,
|
|
137
|
-
edition: normalizedEdition,
|
|
138
|
-
platform: normalizedPlatform
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
if (!policy.ok) {
|
|
142
|
-
return { isError: true, content: [{ type: "text", text: policy.message }] };
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
if (sample_id.includes("://")) {
|
|
146
|
-
const parsed = parseSampleUri(sample_id);
|
|
147
|
-
if (!parsed) {
|
|
148
|
-
return {
|
|
149
|
-
isError: true,
|
|
150
|
-
content: [{
|
|
151
|
-
type: "text",
|
|
152
|
-
text: "sample_id looks like a URI but is not a valid sample:// URI. For doc:// URIs, use resources/read."
|
|
153
|
-
}]
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
const entry = resourceIndex.find((item) => item.uri === sample_id && item.type === "sample");
|
|
157
|
-
if (!entry) {
|
|
158
|
-
return {
|
|
159
|
-
isError: true,
|
|
160
|
-
content: [{
|
|
161
|
-
type: "text",
|
|
162
|
-
text: `Sample URI not found in index: ${sample_id}. Use list_samples or search.`
|
|
163
|
-
}]
|
|
164
|
-
};
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const payload = [{
|
|
168
|
-
sample_id: getSampleIdFromUri(entry.uri),
|
|
169
|
-
uri: entry.uri,
|
|
170
|
-
product: entry.product,
|
|
171
|
-
edition: getDisplayEdition(entry.edition),
|
|
172
|
-
platform: getDisplayPlatform(entry.platform),
|
|
173
|
-
version: entry.version,
|
|
174
|
-
title: entry.title,
|
|
175
|
-
summary: entry.summary
|
|
176
|
-
}];
|
|
177
|
-
|
|
178
|
-
const output = {
|
|
179
|
-
query: sample_id,
|
|
180
|
-
returned: payload.length,
|
|
181
|
-
samples: payload
|
|
182
|
-
};
|
|
183
|
-
|
|
184
|
-
return {
|
|
185
|
-
content: [{
|
|
186
|
-
type: "text",
|
|
187
|
-
text: [
|
|
188
|
-
`Found ${payload.length} match(es) for "${sample_id}".`,
|
|
189
|
-
"Plain URIs (copy/paste):",
|
|
190
|
-
`- 1. ${entry.uri} (sample_id: ${payload[0].sample_id})`,
|
|
191
|
-
"",
|
|
192
|
-
"JSON:",
|
|
193
|
-
JSON.stringify(output, null, 2)
|
|
194
|
-
].join("\n")
|
|
195
|
-
}, {
|
|
196
|
-
type: "resource_link",
|
|
197
|
-
uri: entry.uri,
|
|
198
|
-
name: entry.title,
|
|
199
|
-
description: `SAMPLE | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${payload[0].sample_id}`,
|
|
200
|
-
mimeType: entry.mimeType,
|
|
201
|
-
annotations: {
|
|
202
|
-
audience: ["assistant"],
|
|
203
|
-
priority: 0.8
|
|
204
|
-
}
|
|
205
|
-
}]
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
const sampleQuery = normalizeSampleName(sample_id);
|
|
210
|
-
const maxResults = Math.min(limit || 5, 10);
|
|
211
|
-
|
|
212
|
-
const scopedSamples = getSampleEntries({
|
|
213
|
-
product: normalizedProduct,
|
|
214
|
-
edition: normalizedEdition,
|
|
215
|
-
platform: normalizedPlatform
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
let matches = scopedSamples.filter((entry) => {
|
|
219
|
-
const entryId = getSampleIdFromUri(entry.uri);
|
|
220
|
-
return entryId && entryId.toLowerCase() === sampleQuery.toLowerCase();
|
|
221
|
-
});
|
|
222
|
-
|
|
223
|
-
if (matches.length === 0) {
|
|
224
|
-
matches = await searchResources({
|
|
225
|
-
query: sample_id,
|
|
226
|
-
product: normalizedProduct,
|
|
227
|
-
edition: normalizedEdition,
|
|
228
|
-
platform: normalizedPlatform,
|
|
229
|
-
type: "sample",
|
|
230
|
-
limit: maxResults
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
const selected = matches.slice(0, maxResults);
|
|
235
|
-
if (selected.length === 0) {
|
|
236
|
-
const suggestions = await getSampleSuggestions({
|
|
237
|
-
query: sample_id,
|
|
238
|
-
product: normalizedProduct,
|
|
239
|
-
edition: normalizedEdition,
|
|
240
|
-
platform: normalizedPlatform,
|
|
241
|
-
limit: maxResults
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
const content = [{
|
|
245
|
-
type: "text",
|
|
246
|
-
text: suggestions.length
|
|
247
|
-
? `No exact sample match for "${sample_id}". Related samples:`
|
|
248
|
-
: `No samples found for "${sample_id}". Try list_samples or search.`
|
|
249
|
-
}];
|
|
250
|
-
|
|
251
|
-
for (const entry of suggestions) {
|
|
252
|
-
const sampleId = getSampleIdFromUri(entry.uri);
|
|
253
|
-
content.push({
|
|
254
|
-
type: "resource_link",
|
|
255
|
-
uri: entry.uri,
|
|
256
|
-
name: entry.title,
|
|
257
|
-
description: `${entry.type.toUpperCase()} | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${sampleId || "n/a"}`,
|
|
258
|
-
mimeType: entry.mimeType,
|
|
259
|
-
annotations: {
|
|
260
|
-
audience: ["assistant"],
|
|
261
|
-
priority: 0.6
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (suggestions.length) {
|
|
267
|
-
const plainLines = suggestions.map((entry, index) => {
|
|
268
|
-
const sampleId = getSampleIdFromUri(entry.uri);
|
|
269
|
-
const sampleNote = sampleId ? ` (sample_id: ${sampleId})` : "";
|
|
270
|
-
const scoreNote = formatScoreNote(entry);
|
|
271
|
-
return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote}`;
|
|
272
|
-
});
|
|
273
|
-
content.push({
|
|
274
|
-
type: "text",
|
|
275
|
-
text: ["Plain URIs (copy/paste):", ...plainLines].join("\n")
|
|
276
|
-
});
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
return { isError: true, content };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
const payload = selected.map((entry) => ({
|
|
283
|
-
sample_id: getSampleIdFromUri(entry.uri),
|
|
284
|
-
uri: entry.uri,
|
|
285
|
-
product: entry.product,
|
|
286
|
-
edition: getDisplayEdition(entry.edition),
|
|
287
|
-
platform: getDisplayPlatform(entry.platform),
|
|
288
|
-
version: entry.version,
|
|
289
|
-
title: entry.title,
|
|
290
|
-
summary: entry.summary
|
|
291
|
-
}));
|
|
292
|
-
|
|
293
|
-
const lines = [
|
|
294
|
-
`Found ${selected.length} match(es) for "${sample_id}".`,
|
|
295
|
-
"Plain URIs (copy/paste):",
|
|
296
|
-
...selected.map((entry, index) => {
|
|
297
|
-
const sampleId = getSampleIdFromUri(entry.uri);
|
|
298
|
-
const sampleNote = sampleId ? ` (sample_id: ${sampleId})` : "";
|
|
299
|
-
const scoreNote = formatScoreNote(entry);
|
|
300
|
-
return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote}`;
|
|
301
|
-
})
|
|
302
|
-
];
|
|
303
|
-
|
|
304
|
-
const output = {
|
|
305
|
-
query: sample_id,
|
|
306
|
-
returned: payload.length,
|
|
307
|
-
samples: payload
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
const content = [{
|
|
311
|
-
type: "text",
|
|
312
|
-
text: `${lines.join("\n")}\n\nJSON:\n${JSON.stringify(output, null, 2)}`
|
|
313
|
-
}];
|
|
314
|
-
|
|
315
|
-
for (const entry of selected) {
|
|
316
|
-
const sampleId = getSampleIdFromUri(entry.uri);
|
|
317
|
-
content.push({
|
|
318
|
-
type: "resource_link",
|
|
319
|
-
uri: entry.uri,
|
|
320
|
-
name: entry.title,
|
|
321
|
-
description: `${entry.type.toUpperCase()} | ${formatScopeLabel(entry)} | v${entry.version}${formatScoreLabel(entry)} | sample_id: ${sampleId || "n/a"}`,
|
|
322
|
-
mimeType: entry.mimeType,
|
|
323
|
-
annotations: {
|
|
324
|
-
audience: ["assistant"],
|
|
325
|
-
priority: 0.8
|
|
326
|
-
}
|
|
327
|
-
});
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
return { content };
|
|
331
|
-
}
|
|
332
|
-
);
|
|
333
96
|
}
|