simple-dynamsoft-mcp 7.2.4 → 7.2.5
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.
|
@@ -108,8 +108,8 @@
|
|
|
108
108
|
"branch": "main",
|
|
109
109
|
"owner": "Dynamsoft",
|
|
110
110
|
"repo": "barcode-reader-javascript-samples",
|
|
111
|
-
"commit": "
|
|
112
|
-
"archiveUrl": "https://codeload.github.com/Dynamsoft/barcode-reader-javascript-samples/zip/
|
|
111
|
+
"commit": "deb6320704e324073cd0cb5d6edb8064ce130726",
|
|
112
|
+
"archiveUrl": "https://codeload.github.com/Dynamsoft/barcode-reader-javascript-samples/zip/deb6320704e324073cd0cb5d6edb8064ce130726"
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
"name": "data/samples/dynamsoft-barcode-reader-c-cpp",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "simple-dynamsoft-mcp",
|
|
3
|
-
"version": "7.2.
|
|
3
|
+
"version": "7.2.5",
|
|
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": {
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"start": "node src/index.js",
|
|
26
26
|
"test": "npm run test:lite",
|
|
27
|
-
"test:unit": "node --test test/unit/gemini-retry.test.js test/unit/profile-config.test.js test/unit/lexical-provider.test.js test/unit/hydration-mode.test.js test/unit/hydration-policy.test.js test/unit/repo-map.test.js test/unit/download-utils.test.js test/unit/logging.test.js test/unit/rag-signature-manifest.test.js test/unit/create-server.test.js test/unit/server-helpers.test.js",
|
|
27
|
+
"test:unit": "node --test test/unit/gemini-retry.test.js test/unit/profile-config.test.js test/unit/lexical-provider.test.js test/unit/hydration-mode.test.js test/unit/hydration-policy.test.js test/unit/repo-map.test.js test/unit/download-utils.test.js test/unit/logging.test.js test/unit/rag-signature-manifest.test.js test/unit/startup-timing.test.js test/unit/http-startup-readiness.test.js test/unit/resource-index-startup.test.js test/unit/create-server.test.js test/unit/server-helpers.test.js",
|
|
28
28
|
"test:lite": "npm run test:stdio && npm run test:http && npm run test:package",
|
|
29
29
|
"test:lexical": "node --test test/integration/stdio.test.js test/integration/http.test.js",
|
|
30
30
|
"test:gemini": "node scripts/run-gemini-tests.mjs",
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { readFileSync } from "node:fs";
|
|
3
3
|
import { ensureDataReady } from "./data/bootstrap.js";
|
|
4
4
|
import { maybeSyncSubmodulesOnStart } from "./data/submodule-sync.js";
|
|
5
|
+
import { createStartupTimingTracker } from "./observability/startup-timing.js";
|
|
5
6
|
import { createMcpServerInstance } from "./server/create-server.js";
|
|
6
7
|
import { MCP_HTTP_PATH, resolveRuntimeConfig } from "./server/runtime-config.js";
|
|
7
8
|
import { startStdioServer } from "./server/transports/stdio.js";
|
|
@@ -10,48 +11,132 @@ import { logEvent } from "./observability/logging.js";
|
|
|
10
11
|
|
|
11
12
|
const pkgUrl = new URL("../package.json", import.meta.url);
|
|
12
13
|
const pkg = JSON.parse(readFileSync(pkgUrl, "utf8"));
|
|
14
|
+
const startupTiming = createStartupTimingTracker();
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
let runtimeConfig;
|
|
17
|
+
try {
|
|
18
|
+
runtimeConfig = resolveRuntimeConfig();
|
|
19
|
+
} catch (error) {
|
|
20
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
21
|
+
logEvent("transport", "config_error", { message }, { level: "error" });
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
startupTiming.mark("runtime_config_resolved", {
|
|
26
|
+
transport: runtimeConfig.transport
|
|
20
27
|
});
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
let resourceIndexApi = null;
|
|
30
|
+
let ragApi = null;
|
|
24
31
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
fallback: ragApi.ragConfig.fallback,
|
|
30
|
-
fallback_source: ragApi.ragConfig.fallbackSource
|
|
31
|
-
});
|
|
32
|
+
const startupState = {
|
|
33
|
+
ready: false,
|
|
34
|
+
stage: "initializing"
|
|
35
|
+
};
|
|
32
36
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
37
|
+
function startupCacheSource(dataStatus) {
|
|
38
|
+
if (!dataStatus.mode.includes("downloaded")) return "n/a";
|
|
39
|
+
return dataStatus.downloaded ? "fresh-download" : "cache";
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function createServer() {
|
|
43
|
+
if (!resourceIndexApi || !ragApi) {
|
|
44
|
+
throw new Error("MCP server is still initializing");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return createMcpServerInstance({
|
|
48
|
+
pkgVersion: pkg.version,
|
|
49
|
+
resourceIndexApi,
|
|
50
|
+
ragApi
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function logResolvedProfile() {
|
|
55
|
+
logEvent("profile", "resolved", {
|
|
56
|
+
profile: ragApi.ragConfig.profile,
|
|
57
|
+
provider: ragApi.ragConfig.provider,
|
|
58
|
+
provider_source: ragApi.ragConfig.providerSource,
|
|
59
|
+
fallback: ragApi.ragConfig.fallback,
|
|
60
|
+
fallback_source: ragApi.ragConfig.fallbackSource
|
|
61
|
+
});
|
|
62
|
+
}
|
|
38
63
|
|
|
39
64
|
async function maybePrewarm() {
|
|
40
|
-
if (!ragApi
|
|
65
|
+
if (!ragApi?.ragConfig?.prewarm) return;
|
|
66
|
+
|
|
67
|
+
startupTiming.mark("prewarm_triggered", {
|
|
68
|
+
provider: ragApi.ragConfig.provider,
|
|
69
|
+
block: ragApi.ragConfig.prewarmBlock ? "true" : "false"
|
|
70
|
+
});
|
|
71
|
+
|
|
41
72
|
if (ragApi.ragConfig.prewarmBlock) {
|
|
42
73
|
await ragApi.prewarmRagIndex();
|
|
43
|
-
|
|
44
|
-
|
|
74
|
+
startupTiming.mark("prewarm_done", {
|
|
75
|
+
provider: ragApi.ragConfig.provider
|
|
76
|
+
});
|
|
77
|
+
return;
|
|
45
78
|
}
|
|
79
|
+
|
|
80
|
+
void ragApi.prewarmRagIndex().then(
|
|
81
|
+
() => {
|
|
82
|
+
startupTiming.mark("prewarm_done", {
|
|
83
|
+
provider: ragApi.ragConfig.provider
|
|
84
|
+
});
|
|
85
|
+
},
|
|
86
|
+
() => {
|
|
87
|
+
// prewarmRagIndex already logs failure details.
|
|
88
|
+
}
|
|
89
|
+
);
|
|
46
90
|
}
|
|
47
91
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
92
|
+
async function initializeRuntime() {
|
|
93
|
+
startupState.stage = "submodule_sync";
|
|
94
|
+
await maybeSyncSubmodulesOnStart();
|
|
95
|
+
startupTiming.mark("submodule_sync_done");
|
|
96
|
+
|
|
97
|
+
startupState.stage = "data_resolve";
|
|
98
|
+
const dataStatus = await ensureDataReady();
|
|
99
|
+
logEvent("data", "startup_mode", {
|
|
100
|
+
mode: dataStatus.mode,
|
|
101
|
+
path: dataStatus.dataRoot,
|
|
102
|
+
cache_source: startupCacheSource(dataStatus)
|
|
103
|
+
});
|
|
104
|
+
startupTiming.mark("data_ready", {
|
|
105
|
+
mode: dataStatus.mode,
|
|
106
|
+
cache_source: startupCacheSource(dataStatus)
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
startupState.stage = "resource_index_import";
|
|
110
|
+
resourceIndexApi = await import("./server/resource-index.js");
|
|
111
|
+
startupTiming.mark("resource_index_module_loaded");
|
|
112
|
+
|
|
113
|
+
startupState.stage = "resource_index_build";
|
|
114
|
+
const indexReady = resourceIndexApi.ensureResourceIndexReady();
|
|
115
|
+
startupTiming.mark("resource_index_ready", {
|
|
116
|
+
resource_count: indexReady.resourceCount
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
startupState.stage = "rag_import";
|
|
120
|
+
ragApi = await import("./rag/index.js");
|
|
121
|
+
startupTiming.mark("rag_module_loaded", {
|
|
122
|
+
provider: ragApi.ragConfig.provider,
|
|
123
|
+
fallback: ragApi.ragConfig.fallback
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
logResolvedProfile();
|
|
127
|
+
|
|
128
|
+
startupState.ready = true;
|
|
129
|
+
startupState.stage = "mcp_ready";
|
|
130
|
+
startupTiming.mark("mcp_ready");
|
|
131
|
+
|
|
132
|
+
return dataStatus;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function logStartupFailure(error) {
|
|
52
136
|
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
-
|
|
54
|
-
|
|
137
|
+
startupState.stage = "startup_failed";
|
|
138
|
+
startupState.ready = false;
|
|
139
|
+
logEvent("startup", "failed", { message }, { level: "error" });
|
|
55
140
|
}
|
|
56
141
|
|
|
57
142
|
if (runtimeConfig.transport === "http") {
|
|
@@ -59,10 +144,38 @@ if (runtimeConfig.transport === "http") {
|
|
|
59
144
|
host: runtimeConfig.host,
|
|
60
145
|
port: runtimeConfig.port,
|
|
61
146
|
mcpPath: MCP_HTTP_PATH,
|
|
62
|
-
createServer
|
|
147
|
+
createServer,
|
|
148
|
+
isReady: () => startupState.ready,
|
|
149
|
+
getReadinessState: () => ({ stage: startupState.stage })
|
|
63
150
|
});
|
|
151
|
+
startupTiming.mark("http_listener_ready", {
|
|
152
|
+
host: runtimeConfig.host,
|
|
153
|
+
port: runtimeConfig.port
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
try {
|
|
157
|
+
const dataStatus = await initializeRuntime();
|
|
158
|
+
await maybePrewarm();
|
|
159
|
+
startupTiming.complete({
|
|
160
|
+
transport: "http",
|
|
161
|
+
data_mode: dataStatus.mode
|
|
162
|
+
});
|
|
163
|
+
} catch (error) {
|
|
164
|
+
logStartupFailure(error);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
64
167
|
} else {
|
|
65
|
-
|
|
168
|
+
try {
|
|
169
|
+
const dataStatus = await initializeRuntime();
|
|
170
|
+
await startStdioServer({ createServer });
|
|
171
|
+
startupTiming.mark("stdio_listener_ready");
|
|
172
|
+
await maybePrewarm();
|
|
173
|
+
startupTiming.complete({
|
|
174
|
+
transport: "stdio",
|
|
175
|
+
data_mode: dataStatus.mode
|
|
176
|
+
});
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logStartupFailure(error);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
66
181
|
}
|
|
67
|
-
|
|
68
|
-
await maybePrewarm();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { logEvent } from "./logging.js";
|
|
2
|
+
|
|
3
|
+
function createStartupTimingTracker({ now = Date.now, emit } = {}) {
|
|
4
|
+
const emitEvent =
|
|
5
|
+
typeof emit === "function"
|
|
6
|
+
? emit
|
|
7
|
+
: (component, event, payload) => {
|
|
8
|
+
logEvent(component, event, payload);
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const startedAt = now();
|
|
12
|
+
let previousAt = startedAt;
|
|
13
|
+
|
|
14
|
+
function mark(stage, fields = {}) {
|
|
15
|
+
const currentAt = now();
|
|
16
|
+
emitEvent("startup", "stage", {
|
|
17
|
+
stage,
|
|
18
|
+
elapsed_ms: currentAt - previousAt,
|
|
19
|
+
since_boot_ms: currentAt - startedAt,
|
|
20
|
+
...fields
|
|
21
|
+
});
|
|
22
|
+
previousAt = currentAt;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function complete(fields = {}) {
|
|
26
|
+
const currentAt = now();
|
|
27
|
+
emitEvent("startup", "complete", {
|
|
28
|
+
total_ms: currentAt - startedAt,
|
|
29
|
+
...fields
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return {
|
|
34
|
+
mark,
|
|
35
|
+
complete
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export {
|
|
40
|
+
createStartupTimingTracker
|
|
41
|
+
};
|
|
@@ -280,6 +280,7 @@ function buildVersionPolicyText() {
|
|
|
280
280
|
|
|
281
281
|
const resourceIndex = [];
|
|
282
282
|
const resourceIndexByUri = new Map();
|
|
283
|
+
let resourceIndexReady = false;
|
|
283
284
|
|
|
284
285
|
function addResourceToIndex(entry) {
|
|
285
286
|
resourceIndex.push(entry);
|
|
@@ -368,10 +369,16 @@ function refreshResourceIndex() {
|
|
|
368
369
|
for (const entry of resourceIndex) {
|
|
369
370
|
resourceIndexByUri.set(entry.uri, entry);
|
|
370
371
|
}
|
|
372
|
+
resourceIndexReady = true;
|
|
371
373
|
return { resourceCount: resourceIndex.length };
|
|
372
374
|
}
|
|
373
375
|
|
|
374
|
-
|
|
376
|
+
function ensureResourceIndexReady() {
|
|
377
|
+
if (!resourceIndexReady) {
|
|
378
|
+
return refreshResourceIndex();
|
|
379
|
+
}
|
|
380
|
+
return { resourceCount: resourceIndex.length };
|
|
381
|
+
}
|
|
375
382
|
|
|
376
383
|
function editionMatches(normalizedEdition, entryEdition) {
|
|
377
384
|
if (!normalizedEdition) return true;
|
|
@@ -397,6 +404,8 @@ function platformMatches(normalizedPlatform, entry) {
|
|
|
397
404
|
}
|
|
398
405
|
|
|
399
406
|
function getSampleEntries({ product, edition, platform }) {
|
|
407
|
+
ensureResourceIndexReady();
|
|
408
|
+
|
|
400
409
|
const normalizedProduct = normalizeProduct(product);
|
|
401
410
|
const normalizedPlatform = normalizePlatform(platform);
|
|
402
411
|
const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
|
|
@@ -425,6 +434,7 @@ function formatScopeLabel(entry) {
|
|
|
425
434
|
}
|
|
426
435
|
|
|
427
436
|
function getPinnedResources() {
|
|
437
|
+
ensureResourceIndexReady();
|
|
428
438
|
return resourceIndex.filter((entry) => entry.pinned);
|
|
429
439
|
}
|
|
430
440
|
|
|
@@ -464,6 +474,8 @@ function buildResourceLookupCandidates(uri) {
|
|
|
464
474
|
}
|
|
465
475
|
|
|
466
476
|
async function readResourceContent(uri) {
|
|
477
|
+
ensureResourceIndexReady();
|
|
478
|
+
|
|
467
479
|
let resource = null;
|
|
468
480
|
for (const candidate of buildResourceLookupCandidates(uri)) {
|
|
469
481
|
resource = resourceIndexByUri.get(candidate);
|
|
@@ -481,6 +493,8 @@ async function readResourceContent(uri) {
|
|
|
481
493
|
}
|
|
482
494
|
|
|
483
495
|
function getRagSignatureData() {
|
|
496
|
+
ensureResourceIndexReady();
|
|
497
|
+
|
|
484
498
|
return {
|
|
485
499
|
resourceCount: resourceIndex.length,
|
|
486
500
|
dcvCoreDocCount: dcvCoreDocs.length,
|
|
@@ -585,6 +599,7 @@ export {
|
|
|
585
599
|
buildIndexData,
|
|
586
600
|
buildResourceIndex,
|
|
587
601
|
refreshResourceIndex,
|
|
602
|
+
ensureResourceIndexReady,
|
|
588
603
|
editionMatches,
|
|
589
604
|
platformMatches,
|
|
590
605
|
getDisplayEdition,
|
|
@@ -2,7 +2,15 @@ import { createServer as createHttpServer } from "node:http";
|
|
|
2
2
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
3
3
|
import { logEvent } from "../../observability/logging.js";
|
|
4
4
|
|
|
5
|
-
async function startHttpServer({
|
|
5
|
+
async function startHttpServer({
|
|
6
|
+
host,
|
|
7
|
+
port,
|
|
8
|
+
mcpPath,
|
|
9
|
+
createServer,
|
|
10
|
+
isReady = () => true,
|
|
11
|
+
getReadinessState = () => ({}),
|
|
12
|
+
registerSignalHandlers = true
|
|
13
|
+
}) {
|
|
6
14
|
logEvent("transport", "server_start", { mode: "http", host, port, path: mcpPath });
|
|
7
15
|
|
|
8
16
|
const httpServer = createHttpServer(async (req, res) => {
|
|
@@ -13,20 +21,50 @@ async function startHttpServer({ host, port, mcpPath, createServer }) {
|
|
|
13
21
|
return;
|
|
14
22
|
}
|
|
15
23
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
if (!isReady()) {
|
|
25
|
+
const readiness = getReadinessState();
|
|
26
|
+
logEvent("transport", "request_rejected_not_ready", {
|
|
27
|
+
mode: "http",
|
|
28
|
+
path: requestUrl.pathname,
|
|
29
|
+
stage: readiness?.stage || "initializing"
|
|
30
|
+
}, { level: "debug" });
|
|
31
|
+
|
|
32
|
+
res.writeHead(503, {
|
|
33
|
+
"content-type": "application/json; charset=utf-8",
|
|
34
|
+
"retry-after": "2"
|
|
35
|
+
});
|
|
36
|
+
res.end(
|
|
37
|
+
JSON.stringify({
|
|
38
|
+
jsonrpc: "2.0",
|
|
39
|
+
error: {
|
|
40
|
+
code: -32000,
|
|
41
|
+
message: "Server is warming up. Please retry shortly.",
|
|
42
|
+
data: {
|
|
43
|
+
stage: readiness?.stage || "initializing"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
id: null
|
|
47
|
+
})
|
|
48
|
+
);
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let server = null;
|
|
53
|
+
let transport = null;
|
|
20
54
|
|
|
21
55
|
let closed = false;
|
|
22
56
|
const closeResources = async () => {
|
|
23
57
|
if (closed) return;
|
|
24
58
|
closed = true;
|
|
25
59
|
try {
|
|
26
|
-
|
|
60
|
+
if (transport) {
|
|
61
|
+
await transport.close();
|
|
62
|
+
}
|
|
27
63
|
} catch {}
|
|
28
64
|
try {
|
|
29
|
-
|
|
65
|
+
if (server) {
|
|
66
|
+
await server.close();
|
|
67
|
+
}
|
|
30
68
|
} catch {}
|
|
31
69
|
};
|
|
32
70
|
|
|
@@ -35,6 +73,10 @@ async function startHttpServer({ host, port, mcpPath, createServer }) {
|
|
|
35
73
|
});
|
|
36
74
|
|
|
37
75
|
try {
|
|
76
|
+
server = createServer();
|
|
77
|
+
transport = new StreamableHTTPServerTransport({
|
|
78
|
+
sessionIdGenerator: undefined
|
|
79
|
+
});
|
|
38
80
|
await server.connect(transport);
|
|
39
81
|
await transport.handleRequest(req, res);
|
|
40
82
|
} catch (error) {
|
|
@@ -71,12 +113,14 @@ async function startHttpServer({ host, port, mcpPath, createServer }) {
|
|
|
71
113
|
process.exit(0);
|
|
72
114
|
};
|
|
73
115
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
116
|
+
if (registerSignalHandlers) {
|
|
117
|
+
process.on("SIGINT", () => {
|
|
118
|
+
void shutdown("SIGINT");
|
|
119
|
+
});
|
|
120
|
+
process.on("SIGTERM", () => {
|
|
121
|
+
void shutdown("SIGTERM");
|
|
122
|
+
});
|
|
123
|
+
}
|
|
80
124
|
|
|
81
125
|
return { httpServer };
|
|
82
126
|
}
|