simple-dynamsoft-mcp 7.4.42 → 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 +4 -1
- package/package.json +1 -1
- package/src/server/create-server.js +17 -3
- package/src/server/resources/read-indexed-resource.js +33 -0
- package/src/server/resources/register-resources.js +3 -26
- package/src/server/tools/register-document-tools.js +69 -0
- package/src/server/tools/register-index-tools.js +7 -6
- package/src/server/tools/register-project-tools.js +2 -2
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:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-dynamsoft-mcp",
|
|
3
|
-
"version": "7.
|
|
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
|
-
|
|
187
|
-
|
|
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
|
-
|
|
7
|
-
|
|
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
|
-
|
|
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.
|
|
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),
|
|
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}".
|
|
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}".
|
|
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" : "
|
|
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
|
|
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,
|
|
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
|
}
|