simple-dynamsoft-mcp 2.1.0 → 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 CHANGED
@@ -26,9 +26,11 @@ https://github.com/user-attachments/assets/cc1c5f4b-1461-4462-897a-75abc20d62a6
26
26
  | `get_sdk_info` | Get detailed SDK info for a specific platform |
27
27
  | `list_samples` | List mobile code samples |
28
28
  | `list_python_samples` | List Python SDK samples |
29
+ | `list_web_samples` | List web barcode reader samples |
29
30
  | `list_dwt_categories` | List Dynamic Web TWAIN sample categories |
30
31
  | `get_code_snippet` | Get mobile sample source code |
31
32
  | `get_python_sample` | Get Python sample code |
33
+ | `get_web_sample` | Get web barcode reader sample HTML/JS code |
32
34
  | `get_dwt_sample` | Get Dynamic Web TWAIN sample |
33
35
  | `get_quick_start` | Complete quick start guide with dependencies |
34
36
  | `get_gradle_config` | Android Gradle configuration |
@@ -194,6 +196,12 @@ If you prefer running from source:
194
196
 
195
197
  **CDN:** `https://cdn.jsdelivr.net/npm/dynamsoft-barcode-reader-bundle@11.2.4000/dist/dbr.bundle.min.js`
196
198
 
199
+ **Samples:**
200
+ - **hello-world** - Basic barcode scanning from camera
201
+ - **read-an-image** - Decode from image files
202
+ - **frameworks/** - React, Vue, Angular, Next.js, PWA samples
203
+ - **scenarios/** - Multi-image reading, localize an item, driver license parsing
204
+
197
205
  ### Dynamic Web TWAIN (v19.3)
198
206
 
199
207
  **Installation:** `npm install dwt`
@@ -224,6 +232,12 @@ After connecting the MCP server, you can ask your AI assistant:
224
232
  - "Show me how to read barcodes from an image in Python"
225
233
  - "Get the Python sample for video decoding"
226
234
 
235
+ ### Web Barcode Reader
236
+ - "Create a web page that scans barcodes from a camera"
237
+ - "Show me the web barcode reader hello world sample"
238
+ - "Get the React sample for web barcode scanning"
239
+ - "How do I decode barcodes from an image in JavaScript?"
240
+
227
241
  ### Dynamic Web TWAIN
228
242
  - "Create a web page that scans documents from a TWAIN scanner"
229
243
  - "Show me how to save scanned documents as PDF"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-dynamsoft-mcp",
3
- "version": "2.1.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
@@ -216,6 +216,78 @@ function discoverPythonSamples() {
216
216
  return samples;
217
217
  }
218
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
+
219
291
  function discoverDwtSamples() {
220
292
  const categories = {};
221
293
  const dwtPath = join(codeSnippetRoot, "dynamic-web-twain");
@@ -385,7 +457,9 @@ server.registerTool(
385
457
  lines.push("- `list_sdks` - List all SDKs");
386
458
  lines.push("- `get_sdk_info` - Get detailed SDK info for a platform");
387
459
  lines.push("- `list_samples` - List code samples (mobile)");
460
+ lines.push("- `list_web_samples` - List web barcode reader samples");
388
461
  lines.push("- `get_code_snippet` - Get actual source code");
462
+ lines.push("- `get_web_sample` - Get web barcode reader sample code");
389
463
  lines.push("- `get_quick_start` - Get complete working example");
390
464
  lines.push("- `get_gradle_config` - Get Android build config");
391
465
  lines.push("- `get_license_info` - Get license setup code");
@@ -581,6 +655,99 @@ server.registerTool(
581
655
  }
582
656
  );
583
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
+
584
751
  // ============================================================================
585
752
  // TOOL: list_dwt_categories
586
753
  // ============================================================================
@@ -1066,6 +1233,49 @@ server.registerTool(
1066
1233
  };
1067
1234
  }
1068
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
+
1069
1279
  // Handle Mobile SDK (original logic)
1070
1280
  const level = normalizeApiLevel(api_level);
1071
1281
  const platformKey = platform || "android";
@@ -1566,6 +1776,29 @@ for (const sampleName of pythonSamples) {
1566
1776
  );
1567
1777
  }
1568
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
+
1569
1802
  // Register DWT sample resources
1570
1803
  const dwtCategories = discoverDwtSamples();
1571
1804
  for (const [category, samples] of Object.entries(dwtCategories)) {
@@ -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 16 tools', async () => {
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,15 +130,15 @@ await test('tools/list returns all 16 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 === 16, `Expected 16 tools, got ${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
- 'list_dwt_categories', 'get_code_snippet', 'get_python_sample',
139
- 'get_dwt_sample', 'get_quick_start', 'get_gradle_config',
140
- 'get_license_info', 'get_api_usage', 'search_samples', 'generate_project',
141
- 'search_dwt_docs', 'get_dwt_api_doc'
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'
142
142
  ];
143
143
 
144
144
  for (const expected of expectedTools) {
@@ -348,7 +348,43 @@ await test('generate_project returns project structure', async () => {
348
348
  assert(text.includes('AndroidManifest.xml') || text.includes('build.gradle'), 'Should include project files');
349
349
  });
350
350
 
351
- // Test 14: search_dwt_docs tool
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
352
388
  await test('search_dwt_docs finds documentation articles', async () => {
353
389
  const response = await sendRequest({
354
390
  jsonrpc: '2.0',
@@ -367,7 +403,7 @@ await test('search_dwt_docs finds documentation articles', async () => {
367
403
  assert(text.includes('PDF') || text.includes('pdf'), 'Should find PDF-related articles');
368
404
  });
369
405
 
370
- // Test 15: get_dwt_api_doc tool
406
+ // Test 17: get_dwt_api_doc tool
371
407
  await test('get_dwt_api_doc returns documentation article', async () => {
372
408
  const response = await sendRequest({
373
409
  jsonrpc: '2.0',
@@ -386,7 +422,7 @@ await test('get_dwt_api_doc returns documentation article', async () => {
386
422
  assert(text.includes('OCR') || text.includes('not found'), 'Should handle OCR query');
387
423
  });
388
424
 
389
- // Test 16: resources/list returns registered resources
425
+ // Test 18: resources/list returns registered resources
390
426
  await test('resources/list returns registered resources', async () => {
391
427
  const response = await sendRequest({
392
428
  jsonrpc: '2.0',
@@ -404,7 +440,7 @@ await test('resources/list returns registered resources', async () => {
404
440
  assert(uris.some(u => u.includes('docs/dwt')), 'Should have DWT doc resources');
405
441
  });
406
442
 
407
- // Test 17: Invalid tool call returns error
443
+ // Test 19: Invalid tool call returns error
408
444
  await test('Invalid tool call returns proper error', async () => {
409
445
  const response = await sendRequest({
410
446
  jsonrpc: '2.0',
@@ -420,7 +456,7 @@ await test('Invalid tool call returns proper error', async () => {
420
456
  'Should return error for invalid tool');
421
457
  });
422
458
 
423
- // Test 18: Tool with invalid arguments returns error
459
+ // Test 20: Tool with invalid arguments returns error
424
460
  await test('Tool with missing required arguments returns error', async () => {
425
461
  const response = await sendRequest({
426
462
  jsonrpc: '2.0',