simple-dynamsoft-mcp 2.0.5 → 2.2.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 +23 -0
- package/data/dynamsoft_sdks.json +1 -0
- package/data/web-twain-api-docs.json +1881 -0
- package/package.json +1 -1
- package/src/index.js +422 -0
- package/test/server.test.js +84 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-dynamsoft-mcp",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "MCP server for Dynamsoft SDKs - Barcode Reader (Mobile/Python/Web) and Dynamic Web TWAIN. Provides documentation, code snippets, and API guidance.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
package/src/index.js
CHANGED
|
@@ -12,6 +12,9 @@ const projectRoot = join(__dirname, "..");
|
|
|
12
12
|
const registryUrl = new URL("../data/dynamsoft_sdks.json", import.meta.url);
|
|
13
13
|
const registry = JSON.parse(readFileSync(registryUrl, "utf8"));
|
|
14
14
|
|
|
15
|
+
const dwtDocsUrl = new URL("../data/web-twain-api-docs.json", import.meta.url);
|
|
16
|
+
const dwtDocs = JSON.parse(readFileSync(dwtDocsUrl, "utf8"));
|
|
17
|
+
|
|
15
18
|
const codeSnippetRoot = join(projectRoot, "code-snippet");
|
|
16
19
|
|
|
17
20
|
// ============================================================================
|
|
@@ -213,6 +216,78 @@ function discoverPythonSamples() {
|
|
|
213
216
|
return samples;
|
|
214
217
|
}
|
|
215
218
|
|
|
219
|
+
function discoverWebSamples() {
|
|
220
|
+
const categories = {
|
|
221
|
+
"root": [],
|
|
222
|
+
"frameworks": [],
|
|
223
|
+
"scenarios": []
|
|
224
|
+
};
|
|
225
|
+
const webPath = join(codeSnippetRoot, "dynamsoft-barcode-reader", "web");
|
|
226
|
+
|
|
227
|
+
if (!existsSync(webPath)) return categories;
|
|
228
|
+
|
|
229
|
+
// Find HTML files in root
|
|
230
|
+
for (const entry of readdirSync(webPath, { withFileTypes: true })) {
|
|
231
|
+
if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
232
|
+
categories["root"].push(entry.name.replace(".html", ""));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Find samples in subdirectories
|
|
237
|
+
for (const subdir of ["frameworks", "scenarios"]) {
|
|
238
|
+
const subdirPath = join(webPath, subdir);
|
|
239
|
+
if (existsSync(subdirPath)) {
|
|
240
|
+
for (const entry of readdirSync(subdirPath, { withFileTypes: true })) {
|
|
241
|
+
if (entry.isDirectory()) {
|
|
242
|
+
categories[subdir].push(entry.name);
|
|
243
|
+
} else if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
244
|
+
categories[subdir].push(entry.name.replace(".html", ""));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Remove empty categories
|
|
251
|
+
for (const [key, value] of Object.entries(categories)) {
|
|
252
|
+
if (value.length === 0) delete categories[key];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
return categories;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function getWebSamplePath(category, sampleName) {
|
|
259
|
+
const webPath = join(codeSnippetRoot, "dynamsoft-barcode-reader", "web");
|
|
260
|
+
|
|
261
|
+
if (category === "root" || !category) {
|
|
262
|
+
// Try root level
|
|
263
|
+
const htmlPath = join(webPath, `${sampleName}.html`);
|
|
264
|
+
if (existsSync(htmlPath)) return htmlPath;
|
|
265
|
+
} else {
|
|
266
|
+
// Try in subdirectory
|
|
267
|
+
const dirPath = join(webPath, category, sampleName);
|
|
268
|
+
if (existsSync(dirPath) && statSync(dirPath).isDirectory()) {
|
|
269
|
+
// Look for index.html or main html file
|
|
270
|
+
const indexPath = join(dirPath, "index.html");
|
|
271
|
+
if (existsSync(indexPath)) return indexPath;
|
|
272
|
+
// Look for any html file
|
|
273
|
+
for (const entry of readdirSync(dirPath, { withFileTypes: true })) {
|
|
274
|
+
if (entry.isFile() && entry.name.endsWith(".html")) {
|
|
275
|
+
return join(dirPath, entry.name);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
// Try as html file directly
|
|
280
|
+
const htmlPath = join(webPath, category, `${sampleName}.html`);
|
|
281
|
+
if (existsSync(htmlPath)) return htmlPath;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Fallback: search all
|
|
285
|
+
const rootPath = join(webPath, `${sampleName}.html`);
|
|
286
|
+
if (existsSync(rootPath)) return rootPath;
|
|
287
|
+
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
|
|
216
291
|
function discoverDwtSamples() {
|
|
217
292
|
const categories = {};
|
|
218
293
|
const dwtPath = join(codeSnippetRoot, "dynamic-web-twain");
|
|
@@ -382,7 +457,9 @@ server.registerTool(
|
|
|
382
457
|
lines.push("- `list_sdks` - List all SDKs");
|
|
383
458
|
lines.push("- `get_sdk_info` - Get detailed SDK info for a platform");
|
|
384
459
|
lines.push("- `list_samples` - List code samples (mobile)");
|
|
460
|
+
lines.push("- `list_web_samples` - List web barcode reader samples");
|
|
385
461
|
lines.push("- `get_code_snippet` - Get actual source code");
|
|
462
|
+
lines.push("- `get_web_sample` - Get web barcode reader sample code");
|
|
386
463
|
lines.push("- `get_quick_start` - Get complete working example");
|
|
387
464
|
lines.push("- `get_gradle_config` - Get Android build config");
|
|
388
465
|
lines.push("- `get_license_info` - Get license setup code");
|
|
@@ -391,6 +468,8 @@ server.registerTool(
|
|
|
391
468
|
lines.push("- `get_python_sample` - Get Python SDK sample code");
|
|
392
469
|
lines.push("- `get_dwt_sample` - Get Dynamic Web TWAIN sample");
|
|
393
470
|
lines.push("- `list_dwt_categories` - List DWT sample categories");
|
|
471
|
+
lines.push("- `search_dwt_docs` - Search DWT API documentation");
|
|
472
|
+
lines.push("- `get_dwt_api_doc` - Get specific DWT documentation article");
|
|
394
473
|
|
|
395
474
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
396
475
|
}
|
|
@@ -576,6 +655,99 @@ server.registerTool(
|
|
|
576
655
|
}
|
|
577
656
|
);
|
|
578
657
|
|
|
658
|
+
// ============================================================================
|
|
659
|
+
// TOOL: list_web_samples
|
|
660
|
+
// ============================================================================
|
|
661
|
+
|
|
662
|
+
server.registerTool(
|
|
663
|
+
"list_web_samples",
|
|
664
|
+
{
|
|
665
|
+
title: "List Web Barcode Samples",
|
|
666
|
+
description: "List available JavaScript/Web barcode reader code samples",
|
|
667
|
+
inputSchema: {}
|
|
668
|
+
},
|
|
669
|
+
async () => {
|
|
670
|
+
const categories = discoverWebSamples();
|
|
671
|
+
const sdkEntry = registry.sdks["dbr-web"];
|
|
672
|
+
|
|
673
|
+
const lines = [
|
|
674
|
+
"# Web Barcode Reader Samples",
|
|
675
|
+
"",
|
|
676
|
+
`**SDK Version:** ${sdkEntry.version}`,
|
|
677
|
+
`**Install:** \`npm install dynamsoft-barcode-reader-bundle\``,
|
|
678
|
+
`**CDN:** \`${sdkEntry.platforms.web.installation.cdn}\``,
|
|
679
|
+
"",
|
|
680
|
+
"## Available Samples",
|
|
681
|
+
""
|
|
682
|
+
];
|
|
683
|
+
|
|
684
|
+
for (const [category, samples] of Object.entries(categories)) {
|
|
685
|
+
const categoryTitle = category === "root" ? "Basic Samples" : category.charAt(0).toUpperCase() + category.slice(1);
|
|
686
|
+
lines.push(`### ${categoryTitle}`);
|
|
687
|
+
lines.push(samples.map(s => `- ${s}`).join("\n"));
|
|
688
|
+
lines.push("");
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
lines.push("Use `get_web_sample` with sample_name to get code.");
|
|
692
|
+
|
|
693
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
694
|
+
}
|
|
695
|
+
);
|
|
696
|
+
|
|
697
|
+
// ============================================================================
|
|
698
|
+
// TOOL: get_web_sample
|
|
699
|
+
// ============================================================================
|
|
700
|
+
|
|
701
|
+
server.registerTool(
|
|
702
|
+
"get_web_sample",
|
|
703
|
+
{
|
|
704
|
+
title: "Get Web Barcode Sample",
|
|
705
|
+
description: "Get JavaScript/Web barcode reader sample code",
|
|
706
|
+
inputSchema: {
|
|
707
|
+
sample_name: z.string().describe("Sample name, e.g. hello-world, read-an-image"),
|
|
708
|
+
category: z.string().optional().describe("Category: root, frameworks, scenarios (optional)")
|
|
709
|
+
}
|
|
710
|
+
},
|
|
711
|
+
async ({ sample_name, category }) => {
|
|
712
|
+
const samplePath = getWebSamplePath(category, sample_name);
|
|
713
|
+
|
|
714
|
+
if (!samplePath) {
|
|
715
|
+
const categories = discoverWebSamples();
|
|
716
|
+
const allSamples = Object.entries(categories)
|
|
717
|
+
.map(([cat, samples]) => samples.map(s => `${cat}/${s}`))
|
|
718
|
+
.flat();
|
|
719
|
+
return {
|
|
720
|
+
content: [{
|
|
721
|
+
type: "text",
|
|
722
|
+
text: `Sample "${sample_name}" not found.\n\nAvailable samples:\n${allSamples.map(s => `- ${s}`).join("\n")}\n\nUse \`list_web_samples\` to see all available samples.`
|
|
723
|
+
}]
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
const content = readCodeFile(samplePath);
|
|
728
|
+
if (!content) {
|
|
729
|
+
return { content: [{ type: "text", text: `Could not read "${sample_name}".` }] };
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
const sdkEntry = registry.sdks["dbr-web"];
|
|
733
|
+
|
|
734
|
+
const output = [
|
|
735
|
+
`# Web Barcode Reader: ${sample_name}`,
|
|
736
|
+
"",
|
|
737
|
+
`**SDK Version:** ${sdkEntry.version}`,
|
|
738
|
+
`**Install:** \`npm install dynamsoft-barcode-reader-bundle\``,
|
|
739
|
+
`**CDN:** \`${sdkEntry.platforms.web.installation.cdn}\``,
|
|
740
|
+
`**Trial License:** \`${registry.trial_license}\``,
|
|
741
|
+
"",
|
|
742
|
+
"```html",
|
|
743
|
+
content,
|
|
744
|
+
"```"
|
|
745
|
+
];
|
|
746
|
+
|
|
747
|
+
return { content: [{ type: "text", text: output.join("\n") }] };
|
|
748
|
+
}
|
|
749
|
+
);
|
|
750
|
+
|
|
579
751
|
// ============================================================================
|
|
580
752
|
// TOOL: list_dwt_categories
|
|
581
753
|
// ============================================================================
|
|
@@ -609,6 +781,161 @@ server.registerTool(
|
|
|
609
781
|
}
|
|
610
782
|
|
|
611
783
|
lines.push("Use `get_dwt_sample` with category and sample_name to get code.");
|
|
784
|
+
lines.push("");
|
|
785
|
+
lines.push("**API Documentation Tools:**");
|
|
786
|
+
lines.push("- `search_dwt_docs` - Search DWT API documentation by keyword");
|
|
787
|
+
lines.push("- `get_dwt_api_doc` - Get specific DWT documentation article");
|
|
788
|
+
|
|
789
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
790
|
+
}
|
|
791
|
+
);
|
|
792
|
+
|
|
793
|
+
// ============================================================================
|
|
794
|
+
// TOOL: search_dwt_docs
|
|
795
|
+
// ============================================================================
|
|
796
|
+
|
|
797
|
+
server.registerTool(
|
|
798
|
+
"search_dwt_docs",
|
|
799
|
+
{
|
|
800
|
+
title: "Search DWT Documentation",
|
|
801
|
+
description: "Search Dynamic Web TWAIN API documentation by keyword. Returns matching articles with titles, URLs, and content excerpts.",
|
|
802
|
+
inputSchema: {
|
|
803
|
+
query: z.string().describe("Search keyword or phrase (e.g., 'LoadImage', 'PDF', 'scanner', 'OCR')"),
|
|
804
|
+
max_results: z.number().optional().describe("Maximum number of results to return (default: 5)")
|
|
805
|
+
}
|
|
806
|
+
},
|
|
807
|
+
async ({ query, max_results = 5 }) => {
|
|
808
|
+
const searchTerms = query.toLowerCase().split(/\s+/);
|
|
809
|
+
const results = [];
|
|
810
|
+
|
|
811
|
+
for (const article of dwtDocs.articles) {
|
|
812
|
+
const titleLower = article.title.toLowerCase();
|
|
813
|
+
const contentLower = article.content.toLowerCase();
|
|
814
|
+
const breadcrumbLower = (article.breadcrumb || "").toLowerCase();
|
|
815
|
+
|
|
816
|
+
// Score based on matches in title (higher weight), breadcrumb, and content
|
|
817
|
+
let score = 0;
|
|
818
|
+
for (const term of searchTerms) {
|
|
819
|
+
if (titleLower.includes(term)) score += 10;
|
|
820
|
+
if (breadcrumbLower.includes(term)) score += 5;
|
|
821
|
+
if (contentLower.includes(term)) score += 1;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
if (score > 0) {
|
|
825
|
+
// Extract a relevant excerpt (first 300 chars that contain a search term)
|
|
826
|
+
let excerpt = article.content.substring(0, 300);
|
|
827
|
+
for (const term of searchTerms) {
|
|
828
|
+
const idx = contentLower.indexOf(term);
|
|
829
|
+
if (idx > 50) {
|
|
830
|
+
const start = Math.max(0, idx - 50);
|
|
831
|
+
excerpt = "..." + article.content.substring(start, start + 300) + "...";
|
|
832
|
+
break;
|
|
833
|
+
}
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
results.push({
|
|
837
|
+
title: article.title,
|
|
838
|
+
url: article.url,
|
|
839
|
+
breadcrumb: article.breadcrumb,
|
|
840
|
+
score,
|
|
841
|
+
excerpt: excerpt.replace(/\n+/g, " ").trim()
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
// Sort by score descending
|
|
847
|
+
results.sort((a, b) => b.score - a.score);
|
|
848
|
+
const topResults = results.slice(0, max_results);
|
|
849
|
+
|
|
850
|
+
if (topResults.length === 0) {
|
|
851
|
+
return {
|
|
852
|
+
content: [{
|
|
853
|
+
type: "text",
|
|
854
|
+
text: `No documentation found for "${query}".\n\nTry different keywords like:\n- API names: LoadImage, SaveAsPDF, AcquireImage\n- Features: scanner, PDF, OCR, barcode\n- Topics: initialization, upload, viewer`
|
|
855
|
+
}]
|
|
856
|
+
};
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
const lines = [
|
|
860
|
+
`# DWT Documentation Search: "${query}"`,
|
|
861
|
+
"",
|
|
862
|
+
`Found ${results.length} matches. Showing top ${topResults.length}:`,
|
|
863
|
+
""
|
|
864
|
+
];
|
|
865
|
+
|
|
866
|
+
for (let i = 0; i < topResults.length; i++) {
|
|
867
|
+
const r = topResults[i];
|
|
868
|
+
lines.push(`## ${i + 1}. ${r.title}`);
|
|
869
|
+
lines.push(`**Category:** ${r.breadcrumb}`);
|
|
870
|
+
lines.push(`**URL:** ${r.url}`);
|
|
871
|
+
lines.push("");
|
|
872
|
+
lines.push(`> ${r.excerpt}`);
|
|
873
|
+
lines.push("");
|
|
874
|
+
}
|
|
875
|
+
|
|
876
|
+
lines.push("Use `get_dwt_api_doc` with the article title to get full documentation.");
|
|
877
|
+
|
|
878
|
+
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
879
|
+
}
|
|
880
|
+
);
|
|
881
|
+
|
|
882
|
+
// ============================================================================
|
|
883
|
+
// TOOL: get_dwt_api_doc
|
|
884
|
+
// ============================================================================
|
|
885
|
+
|
|
886
|
+
server.registerTool(
|
|
887
|
+
"get_dwt_api_doc",
|
|
888
|
+
{
|
|
889
|
+
title: "Get DWT API Documentation",
|
|
890
|
+
description: "Get full Dynamic Web TWAIN documentation article by title or URL. Use search_dwt_docs first to find relevant articles.",
|
|
891
|
+
inputSchema: {
|
|
892
|
+
title: z.string().describe("Article title or partial title to match (e.g., 'Loading Documents', 'OCR', 'PDF')")
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
async ({ title }) => {
|
|
896
|
+
const titleLower = title.toLowerCase();
|
|
897
|
+
|
|
898
|
+
// Try exact match first, then partial match
|
|
899
|
+
let article = dwtDocs.articles.find(a => a.title.toLowerCase() === titleLower);
|
|
900
|
+
|
|
901
|
+
if (!article) {
|
|
902
|
+
// Try partial match on title
|
|
903
|
+
article = dwtDocs.articles.find(a => a.title.toLowerCase().includes(titleLower));
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (!article) {
|
|
907
|
+
// Try matching URL
|
|
908
|
+
article = dwtDocs.articles.find(a => a.url.toLowerCase().includes(titleLower));
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
if (!article) {
|
|
912
|
+
// Suggest similar articles
|
|
913
|
+
const suggestions = dwtDocs.articles
|
|
914
|
+
.filter(a => {
|
|
915
|
+
const words = titleLower.split(/\s+/);
|
|
916
|
+
return words.some(w => a.title.toLowerCase().includes(w) || a.breadcrumb.toLowerCase().includes(w));
|
|
917
|
+
})
|
|
918
|
+
.slice(0, 5)
|
|
919
|
+
.map(a => `- ${a.title}`);
|
|
920
|
+
|
|
921
|
+
return {
|
|
922
|
+
content: [{
|
|
923
|
+
type: "text",
|
|
924
|
+
text: `Article "${title}" not found.\n\n${suggestions.length > 0 ? `Similar articles:\n${suggestions.join("\n")}` : "Use search_dwt_docs to find relevant articles."}`
|
|
925
|
+
}]
|
|
926
|
+
};
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const lines = [
|
|
930
|
+
`# ${article.title}`,
|
|
931
|
+
"",
|
|
932
|
+
`**Category:** ${article.breadcrumb}`,
|
|
933
|
+
`**URL:** ${article.url}`,
|
|
934
|
+
"",
|
|
935
|
+
"---",
|
|
936
|
+
"",
|
|
937
|
+
article.content
|
|
938
|
+
];
|
|
612
939
|
|
|
613
940
|
return { content: [{ type: "text", text: lines.join("\n") }] };
|
|
614
941
|
}
|
|
@@ -906,6 +1233,49 @@ server.registerTool(
|
|
|
906
1233
|
};
|
|
907
1234
|
}
|
|
908
1235
|
|
|
1236
|
+
// Handle Web Barcode Reader SDK
|
|
1237
|
+
if (sdkId === "dbr-web") {
|
|
1238
|
+
const sdkEntry = registry.sdks["dbr-web"];
|
|
1239
|
+
const sampleName = use_case?.includes("image") ? "read-an-image" : "hello-world";
|
|
1240
|
+
const samplePath = getWebSamplePath("root", sampleName);
|
|
1241
|
+
|
|
1242
|
+
if (!samplePath || !existsSync(samplePath)) {
|
|
1243
|
+
return { content: [{ type: "text", text: `Sample not found. Use list_web_samples to see available.` }] };
|
|
1244
|
+
}
|
|
1245
|
+
|
|
1246
|
+
const content = readCodeFile(samplePath);
|
|
1247
|
+
|
|
1248
|
+
return {
|
|
1249
|
+
content: [{
|
|
1250
|
+
type: "text", text: [
|
|
1251
|
+
"# Quick Start: Web Barcode Reader",
|
|
1252
|
+
"",
|
|
1253
|
+
`**SDK Version:** ${sdkEntry.version}`,
|
|
1254
|
+
`**Trial License:** \`${registry.trial_license}\``,
|
|
1255
|
+
"",
|
|
1256
|
+
"## Option 1: CDN",
|
|
1257
|
+
"```html",
|
|
1258
|
+
`<script src="${sdkEntry.platforms.web.installation.cdn}"></script>`,
|
|
1259
|
+
"```",
|
|
1260
|
+
"",
|
|
1261
|
+
"## Option 2: NPM",
|
|
1262
|
+
"```bash",
|
|
1263
|
+
"npm install dynamsoft-barcode-reader-bundle",
|
|
1264
|
+
"```",
|
|
1265
|
+
"",
|
|
1266
|
+
`## Sample: ${sampleName}.html`,
|
|
1267
|
+
"```html",
|
|
1268
|
+
content,
|
|
1269
|
+
"```",
|
|
1270
|
+
"",
|
|
1271
|
+
"## Notes",
|
|
1272
|
+
"- Trial license requires network connection",
|
|
1273
|
+
`- User Guide: ${sdkEntry.platforms.web.docs["user-guide"]}`
|
|
1274
|
+
].join("\n")
|
|
1275
|
+
}]
|
|
1276
|
+
};
|
|
1277
|
+
}
|
|
1278
|
+
|
|
909
1279
|
// Handle Mobile SDK (original logic)
|
|
910
1280
|
const level = normalizeApiLevel(api_level);
|
|
911
1281
|
const platformKey = platform || "android";
|
|
@@ -1406,6 +1776,29 @@ for (const sampleName of pythonSamples) {
|
|
|
1406
1776
|
);
|
|
1407
1777
|
}
|
|
1408
1778
|
|
|
1779
|
+
// Register Web barcode reader sample resources
|
|
1780
|
+
const webCategories = discoverWebSamples();
|
|
1781
|
+
for (const [category, samples] of Object.entries(webCategories)) {
|
|
1782
|
+
for (const sampleName of samples) {
|
|
1783
|
+
const resourceUri = `dynamsoft://samples/web/${category}/${sampleName}`;
|
|
1784
|
+
const resourceName = `web-${category}-${sampleName}`.toLowerCase().replace(/[^a-z0-9-]/g, "-");
|
|
1785
|
+
server.registerResource(
|
|
1786
|
+
resourceName,
|
|
1787
|
+
resourceUri,
|
|
1788
|
+
{
|
|
1789
|
+
title: `Web: ${sampleName}`,
|
|
1790
|
+
description: `Web barcode reader ${category}: ${sampleName}`,
|
|
1791
|
+
mimeType: "text/html"
|
|
1792
|
+
},
|
|
1793
|
+
async (uri) => {
|
|
1794
|
+
const samplePath = getWebSamplePath(category, sampleName);
|
|
1795
|
+
const content = samplePath && existsSync(samplePath) ? readCodeFile(samplePath) : "Sample not found";
|
|
1796
|
+
return { contents: [{ uri: uri.href, text: content, mimeType: "text/html" }] };
|
|
1797
|
+
}
|
|
1798
|
+
);
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1409
1802
|
// Register DWT sample resources
|
|
1410
1803
|
const dwtCategories = discoverDwtSamples();
|
|
1411
1804
|
for (const [category, samples] of Object.entries(dwtCategories)) {
|
|
@@ -1429,6 +1822,35 @@ for (const [category, samples] of Object.entries(dwtCategories)) {
|
|
|
1429
1822
|
}
|
|
1430
1823
|
}
|
|
1431
1824
|
|
|
1825
|
+
// Register DWT API documentation resources
|
|
1826
|
+
for (let i = 0; i < dwtDocs.articles.length; i++) {
|
|
1827
|
+
const article = dwtDocs.articles[i];
|
|
1828
|
+
const resourceName = `dwt-doc-${i}`.toLowerCase();
|
|
1829
|
+
const resourceUri = `dynamsoft://docs/dwt/${encodeURIComponent(article.title)}`;
|
|
1830
|
+
server.registerResource(
|
|
1831
|
+
resourceName,
|
|
1832
|
+
resourceUri,
|
|
1833
|
+
{
|
|
1834
|
+
title: `DWT Doc: ${article.title}`,
|
|
1835
|
+
description: `${article.breadcrumb}: ${article.title}`,
|
|
1836
|
+
mimeType: "text/markdown"
|
|
1837
|
+
},
|
|
1838
|
+
async (uri) => {
|
|
1839
|
+
const content = [
|
|
1840
|
+
`# ${article.title}`,
|
|
1841
|
+
"",
|
|
1842
|
+
`**Category:** ${article.breadcrumb}`,
|
|
1843
|
+
`**URL:** ${article.url}`,
|
|
1844
|
+
"",
|
|
1845
|
+
"---",
|
|
1846
|
+
"",
|
|
1847
|
+
article.content
|
|
1848
|
+
].join("\n");
|
|
1849
|
+
return { contents: [{ uri: uri.href, text: content, mimeType: "text/markdown" }] };
|
|
1850
|
+
}
|
|
1851
|
+
);
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1432
1854
|
// ============================================================================
|
|
1433
1855
|
// Start Server
|
|
1434
1856
|
// ============================================================================
|
package/test/server.test.js
CHANGED
|
@@ -121,7 +121,7 @@ await test('Server responds to initialize request', async () => {
|
|
|
121
121
|
});
|
|
122
122
|
|
|
123
123
|
// Test 2: List tools
|
|
124
|
-
await test('tools/list returns all
|
|
124
|
+
await test('tools/list returns all 18 tools', async () => {
|
|
125
125
|
const response = await sendRequest({
|
|
126
126
|
jsonrpc: '2.0',
|
|
127
127
|
id: 1,
|
|
@@ -130,14 +130,15 @@ await test('tools/list returns all 14 tools', async () => {
|
|
|
130
130
|
|
|
131
131
|
assert(response.result, 'Should have result');
|
|
132
132
|
assert(response.result.tools, 'Should have tools array');
|
|
133
|
-
assert(response.result.tools.length ===
|
|
133
|
+
assert(response.result.tools.length === 18, `Expected 18 tools, got ${response.result.tools.length}`);
|
|
134
134
|
|
|
135
135
|
const toolNames = response.result.tools.map(t => t.name);
|
|
136
136
|
const expectedTools = [
|
|
137
137
|
'list_sdks', 'get_sdk_info', 'list_samples', 'list_python_samples',
|
|
138
|
-
'
|
|
139
|
-
'
|
|
140
|
-
'get_license_info', 'get_api_usage', 'search_samples',
|
|
138
|
+
'list_web_samples', 'list_dwt_categories', 'get_code_snippet',
|
|
139
|
+
'get_web_sample', 'get_python_sample', 'get_dwt_sample', 'get_quick_start',
|
|
140
|
+
'get_gradle_config', 'get_license_info', 'get_api_usage', 'search_samples',
|
|
141
|
+
'generate_project', 'search_dwt_docs', 'get_dwt_api_doc'
|
|
141
142
|
];
|
|
142
143
|
|
|
143
144
|
for (const expected of expectedTools) {
|
|
@@ -347,7 +348,81 @@ await test('generate_project returns project structure', async () => {
|
|
|
347
348
|
assert(text.includes('AndroidManifest.xml') || text.includes('build.gradle'), 'Should include project files');
|
|
348
349
|
});
|
|
349
350
|
|
|
350
|
-
// Test 14:
|
|
351
|
+
// Test 14: list_web_samples tool
|
|
352
|
+
await test('list_web_samples returns web barcode samples', async () => {
|
|
353
|
+
const response = await sendRequest({
|
|
354
|
+
jsonrpc: '2.0',
|
|
355
|
+
id: 1,
|
|
356
|
+
method: 'tools/call',
|
|
357
|
+
params: {
|
|
358
|
+
name: 'list_web_samples',
|
|
359
|
+
arguments: {}
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
assert(response.result, 'Should have result');
|
|
364
|
+
assert(response.result.content, 'Should have content');
|
|
365
|
+
const text = response.result.content[0].text;
|
|
366
|
+
assert(text.includes('Web Barcode Reader Samples'), 'Should include web samples header');
|
|
367
|
+
});
|
|
368
|
+
|
|
369
|
+
// Test 15: get_web_sample tool
|
|
370
|
+
await test('get_web_sample returns web barcode sample code', async () => {
|
|
371
|
+
const response = await sendRequest({
|
|
372
|
+
jsonrpc: '2.0',
|
|
373
|
+
id: 1,
|
|
374
|
+
method: 'tools/call',
|
|
375
|
+
params: {
|
|
376
|
+
name: 'get_web_sample',
|
|
377
|
+
arguments: { sample_name: 'hello-world' }
|
|
378
|
+
}
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
assert(response.result, 'Should have result');
|
|
382
|
+
assert(response.result.content, 'Should have content');
|
|
383
|
+
const text = response.result.content[0].text;
|
|
384
|
+
assert(text.includes('Web Barcode Reader') || text.includes('html') || text.includes('not found'), 'Should return sample or indicate not found');
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Test 16: search_dwt_docs tool
|
|
388
|
+
await test('search_dwt_docs finds documentation articles', async () => {
|
|
389
|
+
const response = await sendRequest({
|
|
390
|
+
jsonrpc: '2.0',
|
|
391
|
+
id: 1,
|
|
392
|
+
method: 'tools/call',
|
|
393
|
+
params: {
|
|
394
|
+
name: 'search_dwt_docs',
|
|
395
|
+
arguments: { query: 'PDF' }
|
|
396
|
+
}
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
assert(response.result, 'Should have result');
|
|
400
|
+
assert(response.result.content, 'Should have content');
|
|
401
|
+
const text = response.result.content[0].text;
|
|
402
|
+
assert(text.includes('DWT Documentation Search'), 'Should include search header');
|
|
403
|
+
assert(text.includes('PDF') || text.includes('pdf'), 'Should find PDF-related articles');
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
// Test 17: get_dwt_api_doc tool
|
|
407
|
+
await test('get_dwt_api_doc returns documentation article', async () => {
|
|
408
|
+
const response = await sendRequest({
|
|
409
|
+
jsonrpc: '2.0',
|
|
410
|
+
id: 1,
|
|
411
|
+
method: 'tools/call',
|
|
412
|
+
params: {
|
|
413
|
+
name: 'get_dwt_api_doc',
|
|
414
|
+
arguments: { title: 'OCR' }
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
assert(response.result, 'Should have result');
|
|
419
|
+
assert(response.result.content, 'Should have content');
|
|
420
|
+
const text = response.result.content[0].text;
|
|
421
|
+
// Should return either the article or suggestions
|
|
422
|
+
assert(text.includes('OCR') || text.includes('not found'), 'Should handle OCR query');
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
// Test 18: resources/list returns registered resources
|
|
351
426
|
await test('resources/list returns registered resources', async () => {
|
|
352
427
|
const response = await sendRequest({
|
|
353
428
|
jsonrpc: '2.0',
|
|
@@ -362,9 +437,10 @@ await test('resources/list returns registered resources', async () => {
|
|
|
362
437
|
// Check for expected resource types
|
|
363
438
|
const uris = response.result.resources.map(r => r.uri);
|
|
364
439
|
assert(uris.some(u => u.includes('sdk-info')), 'Should have sdk-info resources');
|
|
440
|
+
assert(uris.some(u => u.includes('docs/dwt')), 'Should have DWT doc resources');
|
|
365
441
|
});
|
|
366
442
|
|
|
367
|
-
// Test
|
|
443
|
+
// Test 19: Invalid tool call returns error
|
|
368
444
|
await test('Invalid tool call returns proper error', async () => {
|
|
369
445
|
const response = await sendRequest({
|
|
370
446
|
jsonrpc: '2.0',
|
|
@@ -380,7 +456,7 @@ await test('Invalid tool call returns proper error', async () => {
|
|
|
380
456
|
'Should return error for invalid tool');
|
|
381
457
|
});
|
|
382
458
|
|
|
383
|
-
// Test
|
|
459
|
+
// Test 20: Tool with invalid arguments returns error
|
|
384
460
|
await test('Tool with missing required arguments returns error', async () => {
|
|
385
461
|
const response = await sendRequest({
|
|
386
462
|
jsonrpc: '2.0',
|