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 +14 -0
- package/package.json +1 -1
- package/src/index.js +233 -0
- package/test/server.test.js +47 -11
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.
|
|
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)) {
|
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,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 ===
|
|
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
|
-
'
|
|
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:
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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',
|