simple-dynamsoft-mcp 7.2.4 → 7.2.6
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.
|
@@ -88,8 +88,8 @@
|
|
|
88
88
|
"branch": "master",
|
|
89
89
|
"owner": "dynamsoft-docs",
|
|
90
90
|
"repo": "web-twain-docs",
|
|
91
|
-
"commit": "
|
|
92
|
-
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/
|
|
91
|
+
"commit": "7b8e127b298a2ef9abb7cd870bb742b44a132503",
|
|
92
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/7b8e127b298a2ef9abb7cd870bb742b44a132503"
|
|
93
93
|
},
|
|
94
94
|
{
|
|
95
95
|
"name": "data/samples/dynamic-web-twain",
|
|
@@ -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.6",
|
|
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
|
+
};
|
|
@@ -2,12 +2,24 @@ import { existsSync, readdirSync, statSync } from "node:fs";
|
|
|
2
2
|
import { extname, join } from "node:path";
|
|
3
3
|
import { DDV_PREFERRED_ENTRY_FILES } from "./config.js";
|
|
4
4
|
|
|
5
|
-
function
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
function countSamples(sampleData) {
|
|
6
|
+
if (Array.isArray(sampleData)) {
|
|
7
|
+
return sampleData.length;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (sampleData && typeof sampleData === "object") {
|
|
11
|
+
return Object.values(sampleData).reduce((total, value) => {
|
|
12
|
+
return total + countSamples(value);
|
|
13
|
+
}, 0);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function countDiscoveredSamplesByPlatforms(platforms, discoverSamplesForPlatform) {
|
|
20
|
+
return platforms.reduce((total, platform) => {
|
|
21
|
+
return total + countSamples(discoverSamplesForPlatform(platform));
|
|
22
|
+
}, 0);
|
|
11
23
|
}
|
|
12
24
|
|
|
13
25
|
function getDcvScenarioTags(sampleName) {
|
|
@@ -35,20 +47,20 @@ function buildProductSelectionGuidanceText() {
|
|
|
35
47
|
return [
|
|
36
48
|
"# Product Selection Guidance",
|
|
37
49
|
"",
|
|
38
|
-
"## DBR vs DCV",
|
|
50
|
+
"## Dynamsoft Barcode Reader (DBR) vs Dynamsoft Capture Vision (DCV)",
|
|
39
51
|
"",
|
|
40
|
-
"Dynamsoft Capture Vision (DCV) is a superset architecture that aggregates DBR, DLR, DDN, DCP, and DCE.",
|
|
52
|
+
"Dynamsoft Capture Vision (DCV) is a superset architecture that aggregates Dynamsoft Barcode Reader (DBR), Dynamsoft Label Recognizer (DLR), Dynamsoft Document Normalizer (DDN), Dynamsoft Code Parser (DCP), and Dynamsoft Camera Enhancer (DCE).",
|
|
41
53
|
"",
|
|
42
|
-
"Use DBR when you only need barcode reading and do not need DCV workflows.",
|
|
54
|
+
"Use Dynamsoft Barcode Reader (DBR) when you only need barcode reading and do not need Dynamsoft Capture Vision (DCV) workflows.",
|
|
43
55
|
"",
|
|
44
|
-
"Use DCV when your scenario includes:",
|
|
56
|
+
"Use Dynamsoft Capture Vision (DCV) when your scenario includes:",
|
|
45
57
|
"- VIN scanning",
|
|
46
58
|
"- MRZ/passport/ID scanning",
|
|
47
59
|
"- Driver license parsing",
|
|
48
60
|
"- Document detection/normalization/auto-capture/cropping",
|
|
49
61
|
"- Multi-task image processing and parsing workflows",
|
|
50
62
|
"",
|
|
51
|
-
"If a query includes MRZ, VIN, driver license, or document-normalization intents, prefer DCV samples
|
|
63
|
+
"If a query includes MRZ, VIN, driver license, or document-normalization intents, prefer Dynamsoft Capture Vision (DCV) samples and docs."
|
|
52
64
|
].join("\n");
|
|
53
65
|
}
|
|
54
66
|
|
|
@@ -146,16 +158,17 @@ function buildIndexData({
|
|
|
146
158
|
const dcvWebFrameworks = getDcvWebFrameworkPlatforms();
|
|
147
159
|
const dcvMobilePlatforms = getDcvMobilePlatforms();
|
|
148
160
|
const dcvServerPlatforms = getDcvServerPlatforms();
|
|
149
|
-
const
|
|
161
|
+
const dbrWebSampleCount = countSamples(discoverWebSamples());
|
|
150
162
|
const dbrWebFrameworks = getDbrWebFrameworkPlatforms();
|
|
151
163
|
const dbrMobilePlatforms = getDbrMobilePlatforms();
|
|
152
164
|
const dbrServerPlatforms = getDbrServerPlatforms();
|
|
165
|
+
const dwtSampleCount = countSamples(discoverDwtSamples());
|
|
153
166
|
const ddvSamples = discoverDdvSamples();
|
|
154
167
|
const ddvWebFrameworks = getDdvWebFrameworkPlatforms();
|
|
155
168
|
|
|
156
169
|
return {
|
|
157
170
|
productSelection: {
|
|
158
|
-
dcvSupersetSummary: "DCV aggregates DBR, DLR, DDN, DCP, and DCE into one pipeline.",
|
|
171
|
+
dcvSupersetSummary: "Dynamsoft Capture Vision (DCV) aggregates Dynamsoft Barcode Reader (DBR), Dynamsoft Label Recognizer (DLR), Dynamsoft Document Normalizer (DDN), Dynamsoft Code Parser (DCP), and Dynamsoft Camera Enhancer (DCE) into one pipeline.",
|
|
159
172
|
useDbrWhen: [
|
|
160
173
|
"Barcode-only workflows where DCV-specific workflows are not required."
|
|
161
174
|
],
|
|
@@ -175,32 +188,25 @@ function buildIndexData({
|
|
|
175
188
|
version: dcvCoreVersion,
|
|
176
189
|
platforms: ["core"],
|
|
177
190
|
docCount: dcvCoreDocs.length,
|
|
178
|
-
|
|
191
|
+
sampleCount: 0
|
|
179
192
|
},
|
|
180
193
|
web: {
|
|
181
194
|
version: dcvWebVersion,
|
|
182
195
|
platforms: ["js", ...dcvWebFrameworks],
|
|
183
|
-
samples: dcvWebSamples,
|
|
184
196
|
docCount: dcvWebDocs.length,
|
|
185
|
-
|
|
197
|
+
sampleCount: countSamples(dcvWebSamples)
|
|
186
198
|
},
|
|
187
199
|
mobile: {
|
|
188
200
|
version: dcvMobileVersion,
|
|
189
201
|
platforms: dcvMobilePlatforms,
|
|
190
|
-
samples: Object.fromEntries(
|
|
191
|
-
dcvMobilePlatforms.map((platform) => [platform, discoverDcvMobileSamples(platform)])
|
|
192
|
-
),
|
|
193
202
|
docCount: dcvMobileDocs.length,
|
|
194
|
-
|
|
203
|
+
sampleCount: countDiscoveredSamplesByPlatforms(dcvMobilePlatforms, discoverDcvMobileSamples)
|
|
195
204
|
},
|
|
196
205
|
server: {
|
|
197
206
|
version: dcvServerVersion,
|
|
198
207
|
platforms: dcvServerPlatforms,
|
|
199
|
-
samples: Object.fromEntries(
|
|
200
|
-
dcvServerPlatforms.map((platform) => [platform, discoverDcvServerSamples(platform)])
|
|
201
|
-
),
|
|
202
208
|
docCount: dcvServerDocs.length,
|
|
203
|
-
|
|
209
|
+
sampleCount: countDiscoveredSamplesByPlatforms(dcvServerPlatforms, discoverDcvServerSamples)
|
|
204
210
|
}
|
|
205
211
|
}
|
|
206
212
|
},
|
|
@@ -210,27 +216,20 @@ function buildIndexData({
|
|
|
210
216
|
mobile: {
|
|
211
217
|
version: dbrMobileVersion,
|
|
212
218
|
platforms: dbrMobilePlatforms,
|
|
213
|
-
samples: Object.fromEntries(
|
|
214
|
-
dbrMobilePlatforms.map((platform) => [platform, discoverMobileSamples(platform)])
|
|
215
|
-
),
|
|
216
219
|
docCount: dbrMobileDocs.length,
|
|
217
|
-
|
|
220
|
+
sampleCount: countDiscoveredSamplesByPlatforms(dbrMobilePlatforms, discoverMobileSamples)
|
|
218
221
|
},
|
|
219
222
|
web: {
|
|
220
223
|
version: dbrWebVersion,
|
|
221
224
|
platforms: ["js", ...dbrWebFrameworks],
|
|
222
|
-
samples: dbrWebSamples,
|
|
223
225
|
docCount: dbrWebDocs.length,
|
|
224
|
-
|
|
226
|
+
sampleCount: dbrWebSampleCount
|
|
225
227
|
},
|
|
226
228
|
server: {
|
|
227
229
|
version: dbrServerVersion,
|
|
228
230
|
platforms: dbrServerPlatforms,
|
|
229
|
-
samples: Object.fromEntries(
|
|
230
|
-
dbrServerPlatforms.map((platform) => [platform, discoverDbrServerSamples(platform)])
|
|
231
|
-
),
|
|
232
231
|
docCount: dbrServerDocs.length,
|
|
233
|
-
|
|
232
|
+
sampleCount: countDiscoveredSamplesByPlatforms(dbrServerPlatforms, discoverDbrServerSamples)
|
|
234
233
|
}
|
|
235
234
|
}
|
|
236
235
|
},
|
|
@@ -240,12 +239,8 @@ function buildIndexData({
|
|
|
240
239
|
web: {
|
|
241
240
|
version: dwtVersion,
|
|
242
241
|
platforms: ["js"],
|
|
243
|
-
sampleCategories: discoverDwtSamples(),
|
|
244
242
|
docCount: dwtDocs.articles.length,
|
|
245
|
-
|
|
246
|
-
title: article.title,
|
|
247
|
-
category: article.breadcrumb || ""
|
|
248
|
-
}))
|
|
243
|
+
sampleCount: dwtSampleCount
|
|
249
244
|
}
|
|
250
245
|
}
|
|
251
246
|
},
|
|
@@ -255,12 +250,8 @@ function buildIndexData({
|
|
|
255
250
|
web: {
|
|
256
251
|
version: ddvVersion,
|
|
257
252
|
platforms: ["js", ...ddvWebFrameworks],
|
|
258
|
-
samples: ddvSamples,
|
|
259
253
|
docCount: ddvDocs.articles.length,
|
|
260
|
-
|
|
261
|
-
title: article.title,
|
|
262
|
-
category: article.breadcrumb || ""
|
|
263
|
-
}))
|
|
254
|
+
sampleCount: countSamples(ddvSamples)
|
|
264
255
|
}
|
|
265
256
|
}
|
|
266
257
|
}
|
|
@@ -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
|
}
|