simple-dynamsoft-mcp 7.4.41 → 7.5.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
@@ -296,12 +296,15 @@ mcpServers:
296
296
  The server exposes this minimal tool surface:
297
297
 
298
298
  - `get_index` -- compact product/version/sample index with selection guidance
299
- - `search` -- semantic search across docs and samples (also accepts exact sample IDs)
299
+ - `search` -- semantic search across docs and samples (also accepts exact sample IDs); returns `doc://` and `sample://` links
300
+ - `read_document` -- read the full text (title, public docs URL, body) of a `doc://` URI returned by `search`
300
301
  - `list_samples` -- browse available samples for a product/edition/platform
301
302
  - `resolve_version` -- resolve current version for a product/edition/platform
302
303
  - `get_quickstart` -- opinionated quickstart: picks a sample by scenario, returns code + install instructions
303
304
  - `get_sample_files` -- get full project files for a known sample (discovered via list_samples or search)
304
305
 
306
+ Typical doc Q&A flow: `search` -> `read_document` with the exact `doc://` URI -> answer from the returned text and cite its public URL. Clients that support MCP resources can also fetch the same `doc://` / `sample://` URIs with `resources/read`.
307
+
305
308
  ## Advanced Configuration And Operator Docs
306
309
 
307
310
  Advanced settings, CI/runbook details, and maintenance workflows live in:
@@ -108,8 +108,8 @@
108
108
  "branch": "master",
109
109
  "owner": "dynamsoft-docs",
110
110
  "repo": "web-twain-docs",
111
- "commit": "1a818a863d3682fea6fd55f1c658b55bde4c8b98",
112
- "archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/1a818a863d3682fea6fd55f1c658b55bde4c8b98"
111
+ "commit": "3cb367e1e733a6556902f328b6b51fad0280d2b9",
112
+ "archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/3cb367e1e733a6556902f328b6b51fad0280d2b9"
113
113
  },
114
114
  {
115
115
  "name": "data/samples/document-scanner-javascript",
@@ -452,7 +452,7 @@
452
452
  "dwt": {
453
453
  "name": "Dynamic Web TWAIN",
454
454
  "description": "Document scanning SDK for web applications. Supports TWAIN, WIA, ICA, SANE scanners.",
455
- "version": "19.4.4",
455
+ "version": "19.5.0",
456
456
  "default_platform": "web",
457
457
  "snippet_path": "dynamic-web-twain",
458
458
  "api_docs": "web-twain-api-docs.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-dynamsoft-mcp",
3
- "version": "7.4.41",
3
+ "version": "7.5.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": {
@@ -2,7 +2,9 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
2
  import { ensureDataScopesHydrated } from "../data/bootstrap.js";
3
3
  import { createScopeHydrator } from "./helpers/server-helpers.js";
4
4
  import { registerResourceHandlers } from "./resources/register-resources.js";
5
+ import { createIndexedResourceReader } from "./resources/read-indexed-resource.js";
5
6
  import { registerIndexTools } from "./tools/register-index-tools.js";
7
+ import { registerDocumentTools } from "./tools/register-document-tools.js";
6
8
  import { registerSampleTools } from "./tools/register-sample-tools.js";
7
9
  import { registerVersionTools } from "./tools/register-version-tools.js";
8
10
  import { registerQuickstartTools } from "./tools/register-quickstart-tools.js";
@@ -71,6 +73,8 @@ export function createMcpServerInstance({ pkgVersion, resourceIndexApi, ragApi }
71
73
  "",
72
74
  "How to use it:",
73
75
  "- Call get_index first to see valid product/edition/platform combinations before asking the user anything.",
76
+ "- To answer from documentation: call search, then call read_document with the exact doc:// uri it returned, and base the answer on that text, citing the public documentation URL it contains. Do not answer from search summaries alone or hand doc:// links to the user.",
77
+ "- For sample:// results, call get_sample_files with resource_uri to get the complete sample project.",
74
78
  "- Only ask the user to disambiguate dimensions that actually vary for the chosen product. Do NOT ask about dimensions that don't apply.",
75
79
  "",
76
80
  "Platform/edition rules (important):",
@@ -84,6 +88,12 @@ export function createMcpServerInstance({ pkgVersion, resourceIndexApi, ragApi }
84
88
  ].join("\n")
85
89
  });
86
90
 
91
+ const readIndexedResource = createIndexedResourceReader({
92
+ parseResourceUri,
93
+ ensureLatestMajor,
94
+ readResourceContent
95
+ });
96
+
87
97
  registerIndexTools({
88
98
  server,
89
99
  ensureScopeHydrated,
@@ -100,6 +110,11 @@ export function createMcpServerInstance({ pkgVersion, resourceIndexApi, ragApi }
100
110
  getSampleSuggestions
101
111
  });
102
112
 
113
+ registerDocumentTools({
114
+ server,
115
+ readIndexedResource
116
+ });
117
+
103
118
  registerSampleTools({
104
119
  server,
105
120
  registry,
@@ -183,9 +198,8 @@ export function createMcpServerInstance({ pkgVersion, resourceIndexApi, ragApi }
183
198
  registerResourceHandlers({
184
199
  server,
185
200
  getPinnedResources,
186
- parseResourceUri,
187
- ensureLatestMajor,
188
- readResourceContent
201
+ readResourceContent,
202
+ readIndexedResource
189
203
  });
190
204
 
191
205
  return server;
@@ -0,0 +1,33 @@
1
+ // Products whose versioned doc/sample URIs are subject to the latest-major policy.
2
+ const VERSION_POLICY_PRODUCTS = ["dbr", "dwt", "ddv", "mrz", "mds"];
3
+
4
+ // Shared read path for doc:// and sample:// URIs. Used by the MCP resource
5
+ // templates (resources/read) and the read_document tool so both enforce the
6
+ // same version policy and stale-URI guidance.
7
+ export function createIndexedResourceReader({ parseResourceUri, ensureLatestMajor, readResourceContent }) {
8
+ return async function readIndexedResource(uri) {
9
+ const uriStr = String(uri);
10
+ const parsed = parseResourceUri(uriStr);
11
+ if (parsed && VERSION_POLICY_PRODUCTS.includes(parsed.product)) {
12
+ const policy = ensureLatestMajor({
13
+ product: parsed.product,
14
+ version: parsed.version,
15
+ edition: parsed.edition,
16
+ platform: parsed.platform
17
+ });
18
+ if (!policy.ok) {
19
+ throw new Error(policy.message);
20
+ }
21
+ }
22
+
23
+ const resource = await readResourceContent(uriStr);
24
+ if (!resource) {
25
+ // Doc URIs embed a positional index + exact version, so they can go stale
26
+ // across data refreshes. Point the agent back to search to re-discover the
27
+ // current URI instead of dead-ending (issue #153).
28
+ throw new Error(`Resource not found: ${uriStr}. The URI may be stale (doc URIs change across data refreshes). Call search with keywords from the title to get the current URI.`);
29
+ }
30
+
31
+ return resource;
32
+ };
33
+ }
@@ -3,9 +3,8 @@ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
3
  export function registerResourceHandlers({
4
4
  server,
5
5
  getPinnedResources,
6
- parseResourceUri,
7
- ensureLatestMajor,
8
- readResourceContent
6
+ readResourceContent,
7
+ readIndexedResource
9
8
  }) {
10
9
  for (const entry of getPinnedResources()) {
11
10
  server.registerResource(
@@ -26,29 +25,7 @@ export function registerResourceHandlers({
26
25
  }
27
26
 
28
27
  async function templateReadHandler(uri) {
29
- const uriStr = uri.toString();
30
- const parsed = parseResourceUri(uriStr);
31
- if (parsed && ["dbr", "dwt", "ddv", "mrz", "mds"].includes(parsed.product)) {
32
- const policy = ensureLatestMajor({
33
- product: parsed.product,
34
- version: parsed.version,
35
- edition: parsed.edition,
36
- platform: parsed.platform
37
- });
38
- if (!policy.ok) {
39
- throw new Error(policy.message);
40
- }
41
- }
42
-
43
- const resource = await readResourceContent(uriStr);
44
- if (!resource) {
45
- // Doc URIs embed a positional index + exact version, so they can go stale
46
- // across data refreshes. Point the agent back to search to re-discover the
47
- // current URI instead of dead-ending (issue #153).
48
- throw new Error(`Resource not found: ${uriStr}. The URI may be stale (doc URIs change across data refreshes). Call search with keywords from the title to get the current URI.`);
49
- }
50
-
51
- return { contents: [resource] };
28
+ return { contents: [await readIndexedResource(uri.toString())] };
52
29
  }
53
30
 
54
31
  server.registerResource(
@@ -0,0 +1,69 @@
1
+ import { z } from "zod";
2
+ import { logEvent } from "../../observability/logging.js";
3
+
4
+ function errorResult(text) {
5
+ return { isError: true, content: [{ type: "text", text }] };
6
+ }
7
+
8
+ export function registerDocumentTools({ server, readIndexedResource }) {
9
+ server.registerTool(
10
+ "read_document",
11
+ {
12
+ title: "Read Document",
13
+ description: [
14
+ "Read the full text of a Dynamsoft documentation page by its doc:// URI.",
15
+ "",
16
+ "WHEN TO USE:",
17
+ "- After search returns doc:// results: call read_document with the exact uri from a resource_link or the plain URI list before answering.",
18
+ "- When you need the actual article body (API details, parameters, code snippets) instead of the search summary.",
19
+ "",
20
+ "WHEN NOT TO USE:",
21
+ "- For sample:// URIs, use get_sample_files with resource_uri instead (complete sample projects).",
22
+ "- Do not construct or edit doc:// URIs yourself; they are generated by search and may change across data refreshes.",
23
+ "",
24
+ "PARAMETERS:",
25
+ "- uri (required): The exact doc:// URI returned by search (e.g. a resource_link uri).",
26
+ "",
27
+ "RETURNS: The document as markdown text: title, public documentation URL (cite it in answers), and the full article body.",
28
+ "On failure (stale or missing URI, unsupported version, unsupported scheme) returns isError with guidance; call search again to get a current URI.",
29
+ "",
30
+ "RELATED TOOLS: search (find doc:// URIs), get_sample_files (read sample:// projects), get_quickstart (starter code)."
31
+ ].join("\n"),
32
+ inputSchema: {
33
+ uri: z.string().trim().min(1, "uri is required.").describe("Exact doc:// URI returned by search.")
34
+ },
35
+ annotations: {
36
+ readOnlyHint: true,
37
+ destructiveHint: false,
38
+ idempotentHint: true,
39
+ openWorldHint: false
40
+ }
41
+ },
42
+ async ({ uri }) => {
43
+ const scheme = /^([a-z][a-z0-9+.-]*):\/\//i.exec(uri)?.[1]?.toLowerCase();
44
+ if (scheme === "sample") {
45
+ return errorResult(`read_document only reads doc:// URIs. For sample:// URIs, call get_sample_files with resource_uri="${uri}".`);
46
+ }
47
+ if (scheme !== "doc") {
48
+ return errorResult(`Unsupported URI: "${uri}". read_document accepts only doc:// URIs returned by search.`);
49
+ }
50
+
51
+ let resource;
52
+ try {
53
+ resource = await readIndexedResource(uri);
54
+ } catch (error) {
55
+ const message = error instanceof Error ? error.message : String(error);
56
+ logEvent("read_document", "read_failed", { uri, error: message });
57
+ return errorResult(`Failed to read document: ${message}`);
58
+ }
59
+
60
+ const text = typeof resource.text === "string" ? resource.text : "";
61
+ if (!text.trim()) {
62
+ logEvent("read_document", "empty_document", { uri });
63
+ return errorResult(`Document ${uri} has no readable text content. Call search again to find an alternative document.`);
64
+ }
65
+
66
+ return { content: [{ type: "text", text }] };
67
+ }
68
+ );
69
+ }
@@ -118,9 +118,9 @@ export function registerIndexTools({
118
118
  "- type: 'doc', 'sample', 'index', 'policy', or 'any' (default). Use 'sample' to restrict to sample results.",
119
119
  "- limit: 1-10 (default 5). Max number of results.",
120
120
  "",
121
- "RETURNS: An MCP response whose content array includes a leading text summary item followed by zero or more resource_link items with URIs. Use resources/read to fetch full content of doc:// URIs. Use get_sample_files to fetch full project files for sample:// URIs.",
121
+ "RETURNS: An MCP response whose content array includes a leading text summary item followed by zero or more resource_link items with URIs. For doc:// URIs, call read_document with the exact returned uri to get the full document text before answering. For sample:// URIs, call get_sample_files with resource_uri to get the full project files.",
122
122
  "",
123
- "RELATED TOOLS: get_index (discover products first), list_samples (browse all samples), get_sample_files (retrieve full sample project files), resources/read (read a doc resource)."
123
+ "RELATED TOOLS: get_index (discover products first), list_samples (browse all samples), read_document (read a doc:// result), get_sample_files (retrieve full sample project files)."
124
124
  ].join("\n"),
125
125
  inputSchema: {
126
126
  query: z.string().trim().min(1, "Query is required.").describe("Keywords to search across docs and samples."),
@@ -199,7 +199,7 @@ export function registerIndexTools({
199
199
  const content = [
200
200
  {
201
201
  type: "text",
202
- text: `Found ${selected.length} exact match(es) for "${query}". Read the links you need with resources/read.`
202
+ text: `Found ${selected.length} exact match(es) for "${query}". Call get_sample_files with the exact resource_uri to get the full sample project.`
203
203
  }
204
204
  ];
205
205
 
@@ -325,9 +325,10 @@ export function registerIndexTools({
325
325
  // Capture Vision / DCV is the umbrella framework, not a selectable public
326
326
  // product — steer the agent to the right offering (issue #150).
327
327
  const dcvUmbrella = /\b(capture[\s-]?vision|dcv)\b/i.test(query) && !normalizedProduct;
328
+ const readGuidance = "For doc:// results, call read_document with the exact uri to get the document text; for sample:// results, call get_sample_files with resource_uri.";
328
329
  const leadText = dcvUmbrella
329
- ? `"Capture Vision" (DCV) is Dynamsoft's umbrella framework — pick the public product for your task: DBR (barcodes), MRZ (passports/IDs), MDS (document capture), DWT (scanner hardware), DDV (document viewer). Results below span the closest matches.\nFound ${topResults.length} result(s) for "${query}".`
330
- : `Found ${topResults.length} result(s) for "${query}". Read the links you need with resources/read.`;
330
+ ? `"Capture Vision" (DCV) is Dynamsoft's umbrella framework — pick the public product for your task: DBR (barcodes), MRZ (passports/IDs), MDS (document capture), DWT (scanner hardware), DDV (document viewer). Results below span the closest matches.\nFound ${topResults.length} result(s) for "${query}". ${readGuidance}`
331
+ : `Found ${topResults.length} result(s) for "${query}". ${readGuidance}`;
331
332
  const content = [
332
333
  {
333
334
  type: "text",
@@ -356,7 +357,7 @@ export function registerIndexTools({
356
357
 
357
358
  const plainLines = topResults.map((entry, index) => {
358
359
  const sampleId = entry.type === "sample" ? getSampleIdFromUri(entry.uri) : "";
359
- const action = entry.type === "sample" ? "get_sample_files resource_uri" : "resources/read uri";
360
+ const action = entry.type === "sample" ? "get_sample_files resource_uri" : "read_document uri";
360
361
  const sampleNote = sampleId ? ` sample_id=${sampleId}` : "";
361
362
  const scoreNote = formatScoreNote(entry);
362
363
  return `- ${index + 1}. ${entry.uri}${sampleNote}${scoreNote} (${action})`;
@@ -83,7 +83,7 @@ export function registerProjectTools({
83
83
  "",
84
84
  "WHEN NOT TO USE:",
85
85
  "- If you do not have a sample_id or resource_uri yet, call list_samples or search first to discover one.",
86
- "- For doc:// URIs, use resources/read instead (this tool only handles sample:// URIs).",
86
+ "- For doc:// URIs, use read_document instead (this tool only handles sample:// URIs).",
87
87
  "- If the user just wants a quick code snippet, use get_quickstart instead.",
88
88
  "",
89
89
  "PARAMETERS:",
@@ -137,7 +137,7 @@ export function registerProjectTools({
137
137
  isError: true,
138
138
  content: [{
139
139
  type: "text",
140
- text: "resource_uri must use the sample:// scheme. For doc:// URIs, use resources/read instead."
140
+ text: "resource_uri must use the sample:// scheme. For doc:// URIs, call read_document with the exact uri instead."
141
141
  }]
142
142
  };
143
143
  }