simple-dynamsoft-mcp 7.2.3 → 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.
- package/data/metadata/data-manifest.json +8 -8
- package/data/metadata/dynamsoft_sdks.json +1 -1
- package/package.json +2 -2
- package/src/index.js +146 -33
- package/src/observability/startup-timing.js +41 -0
- package/src/server/resource-index/paths.js +91 -22
- package/src/server/resource-index.js +54 -34
- package/src/server/transports/http.js +57 -13
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"branch": "main",
|
|
29
29
|
"owner": "dynamsoft-docs",
|
|
30
30
|
"repo": "barcode-reader-docs-server",
|
|
31
|
-
"commit": "
|
|
32
|
-
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/barcode-reader-docs-server/zip/
|
|
31
|
+
"commit": "5479d530342edc40d1602addebbb8cd60ed781ce",
|
|
32
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/barcode-reader-docs-server/zip/5479d530342edc40d1602addebbb8cd60ed781ce"
|
|
33
33
|
},
|
|
34
34
|
{
|
|
35
35
|
"name": "data/documentation/capture-vision-docs",
|
|
@@ -78,8 +78,8 @@
|
|
|
78
78
|
"branch": "main",
|
|
79
79
|
"owner": "dynamsoft-docs",
|
|
80
80
|
"repo": "document-viewer-docs",
|
|
81
|
-
"commit": "
|
|
82
|
-
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/document-viewer-docs/zip/
|
|
81
|
+
"commit": "4a49e918d174f4efac927981b81c81024d9ba3dc",
|
|
82
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/document-viewer-docs/zip/4a49e918d174f4efac927981b81c81024d9ba3dc"
|
|
83
83
|
},
|
|
84
84
|
{
|
|
85
85
|
"name": "data/documentation/web-twain-docs",
|
|
@@ -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": "a38d73c583b8379c0d80337e92f291f6c1ab10a8",
|
|
92
|
+
"archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/a38d73c583b8379c0d80337e92f291f6c1ab10a8"
|
|
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",
|
|
@@ -492,7 +492,7 @@
|
|
|
492
492
|
"ddv": {
|
|
493
493
|
"name": "Dynamsoft Document Viewer",
|
|
494
494
|
"description": "Browser-based JavaScript SDK for viewing, editing, and annotating documents (PDFs and images).",
|
|
495
|
-
"version": "3.2",
|
|
495
|
+
"version": "3.2.1",
|
|
496
496
|
"default_platform": "web",
|
|
497
497
|
"snippet_path": "dynamsoft-document-viewer",
|
|
498
498
|
"api_docs": "ddv-api-docs.json",
|
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/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
|
+
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { existsSync, readFileSync
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
4
|
import { DOC_DIRS, SAMPLE_DIRS } from "./config.js";
|
|
5
5
|
import { getResolvedDataRoot } from "../../data/root.js";
|
|
@@ -13,6 +13,9 @@ const samplesRoot = join(dataRoot, "samples");
|
|
|
13
13
|
const docsRoot = join(dataRoot, "documentation");
|
|
14
14
|
|
|
15
15
|
const registryPath = join(metadataRoot, "dynamsoft_sdks.json");
|
|
16
|
+
const dataManifestPath = join(metadataRoot, "data-manifest.json");
|
|
17
|
+
|
|
18
|
+
const manifestCommitLookupCache = new Map();
|
|
16
19
|
|
|
17
20
|
const SAMPLE_ROOTS = {
|
|
18
21
|
dbrWeb: join(samplesRoot, SAMPLE_DIRS.dbrWeb),
|
|
@@ -60,28 +63,92 @@ function getExistingPath(...candidates) {
|
|
|
60
63
|
return null;
|
|
61
64
|
}
|
|
62
65
|
|
|
63
|
-
function
|
|
66
|
+
function normalizeManifestRepoPath(pathValue) {
|
|
67
|
+
return String(pathValue || "")
|
|
68
|
+
.replace(/\\/g, "/")
|
|
69
|
+
.replace(/^\.\//, "")
|
|
70
|
+
.replace(/^\/+/, "")
|
|
71
|
+
.replace(/\/+$/, "");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function getManifestRepoPath(repoPath, dataRootPath = dataRoot) {
|
|
75
|
+
const resolvedDataRoot = resolve(dataRootPath);
|
|
76
|
+
const resolvedRepoPath = resolve(repoPath);
|
|
77
|
+
const manifestRepoPath = normalizeManifestRepoPath(
|
|
78
|
+
relative(resolvedDataRoot, resolvedRepoPath)
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
if (!manifestRepoPath || manifestRepoPath === ".") {
|
|
82
|
+
throw new Error(`Unable to resolve manifest repo path for ${repoPath}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (manifestRepoPath.startsWith("..")) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`Repo path ${repoPath} is outside data root ${resolvedDataRoot}`
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return manifestRepoPath;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function buildManifestCommitLookup(manifestPath = dataManifestPath) {
|
|
95
|
+
const resolvedManifestPath = resolve(manifestPath);
|
|
96
|
+
const cachedLookup = manifestCommitLookupCache.get(resolvedManifestPath);
|
|
97
|
+
if (cachedLookup) {
|
|
98
|
+
return cachedLookup;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!existsSync(resolvedManifestPath)) {
|
|
102
|
+
throw new Error(`Missing data manifest at ${resolvedManifestPath}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let manifest = null;
|
|
64
106
|
try {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
107
|
+
manifest = JSON.parse(readFileSync(resolvedManifestPath, "utf8"));
|
|
108
|
+
} catch (error) {
|
|
109
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
110
|
+
throw new Error(
|
|
111
|
+
`Failed to parse data manifest at ${resolvedManifestPath}: ${reason}`
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (!manifest || !Array.isArray(manifest.repos)) {
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Invalid data manifest at ${resolvedManifestPath}: missing repos array`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const lookup = new Map();
|
|
122
|
+
for (const repo of manifest.repos) {
|
|
123
|
+
const repoPath = normalizeManifestRepoPath(repo?.path);
|
|
124
|
+
const commit = String(repo?.commit || "").trim();
|
|
125
|
+
if (!repoPath || !commit) {
|
|
126
|
+
throw new Error(
|
|
127
|
+
`Invalid data manifest entry at ${resolvedManifestPath}: path and commit are required`
|
|
128
|
+
);
|
|
74
129
|
}
|
|
75
|
-
|
|
76
|
-
if (!existsSync(headPath)) return "";
|
|
77
|
-
const headText = readFileSync(headPath, "utf8").trim();
|
|
78
|
-
if (!headText.startsWith("ref:")) return headText;
|
|
79
|
-
const refPath = join(gitDir, headText.slice("ref:".length).trim());
|
|
80
|
-
if (!existsSync(refPath)) return "";
|
|
81
|
-
return readFileSync(refPath, "utf8").trim();
|
|
82
|
-
} catch {
|
|
83
|
-
return "";
|
|
130
|
+
lookup.set(repoPath, commit);
|
|
84
131
|
}
|
|
132
|
+
|
|
133
|
+
manifestCommitLookupCache.set(resolvedManifestPath, lookup);
|
|
134
|
+
return lookup;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function readManifestRepoCommit(repoPath, options = {}) {
|
|
138
|
+
const dataRootPath = options.dataRootPath || dataRoot;
|
|
139
|
+
const manifestPath = options.manifestPath || dataManifestPath;
|
|
140
|
+
const commitLookup =
|
|
141
|
+
options.commitLookup || buildManifestCommitLookup(manifestPath);
|
|
142
|
+
const manifestRepoPath = getManifestRepoPath(repoPath, dataRootPath);
|
|
143
|
+
const commit = commitLookup.get(manifestRepoPath);
|
|
144
|
+
|
|
145
|
+
if (!commit) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
`Missing commit for ${manifestRepoPath} in data manifest ${resolve(manifestPath)}`
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return commit;
|
|
85
152
|
}
|
|
86
153
|
|
|
87
154
|
export {
|
|
@@ -91,8 +158,10 @@ export {
|
|
|
91
158
|
samplesRoot,
|
|
92
159
|
docsRoot,
|
|
93
160
|
registryPath,
|
|
161
|
+
dataManifestPath,
|
|
94
162
|
SAMPLE_ROOTS,
|
|
95
163
|
DOC_ROOTS,
|
|
96
164
|
getExistingPath,
|
|
97
|
-
|
|
165
|
+
buildManifestCommitLookup,
|
|
166
|
+
readManifestRepoCommit
|
|
98
167
|
};
|
|
@@ -17,7 +17,12 @@ import {
|
|
|
17
17
|
} from "./normalizers.js";
|
|
18
18
|
import { DOCS_CONFIG } from "./resource-index/config.js";
|
|
19
19
|
import { loadMarkdownDocs } from "./resource-index/docs-loader.js";
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
registryPath,
|
|
22
|
+
DOC_ROOTS,
|
|
23
|
+
SAMPLE_ROOTS,
|
|
24
|
+
readManifestRepoCommit
|
|
25
|
+
} from "./resource-index/paths.js";
|
|
21
26
|
import {
|
|
22
27
|
getCodeFileExtensions,
|
|
23
28
|
isCodeFile,
|
|
@@ -275,6 +280,7 @@ function buildVersionPolicyText() {
|
|
|
275
280
|
|
|
276
281
|
const resourceIndex = [];
|
|
277
282
|
const resourceIndexByUri = new Map();
|
|
283
|
+
let resourceIndexReady = false;
|
|
278
284
|
|
|
279
285
|
function addResourceToIndex(entry) {
|
|
280
286
|
resourceIndex.push(entry);
|
|
@@ -363,10 +369,16 @@ function refreshResourceIndex() {
|
|
|
363
369
|
for (const entry of resourceIndex) {
|
|
364
370
|
resourceIndexByUri.set(entry.uri, entry);
|
|
365
371
|
}
|
|
372
|
+
resourceIndexReady = true;
|
|
366
373
|
return { resourceCount: resourceIndex.length };
|
|
367
374
|
}
|
|
368
375
|
|
|
369
|
-
|
|
376
|
+
function ensureResourceIndexReady() {
|
|
377
|
+
if (!resourceIndexReady) {
|
|
378
|
+
return refreshResourceIndex();
|
|
379
|
+
}
|
|
380
|
+
return { resourceCount: resourceIndex.length };
|
|
381
|
+
}
|
|
370
382
|
|
|
371
383
|
function editionMatches(normalizedEdition, entryEdition) {
|
|
372
384
|
if (!normalizedEdition) return true;
|
|
@@ -392,6 +404,8 @@ function platformMatches(normalizedPlatform, entry) {
|
|
|
392
404
|
}
|
|
393
405
|
|
|
394
406
|
function getSampleEntries({ product, edition, platform }) {
|
|
407
|
+
ensureResourceIndexReady();
|
|
408
|
+
|
|
395
409
|
const normalizedProduct = normalizeProduct(product);
|
|
396
410
|
const normalizedPlatform = normalizePlatform(platform);
|
|
397
411
|
const normalizedEdition = normalizeEdition(edition, normalizedPlatform, normalizedProduct);
|
|
@@ -420,6 +434,7 @@ function formatScopeLabel(entry) {
|
|
|
420
434
|
}
|
|
421
435
|
|
|
422
436
|
function getPinnedResources() {
|
|
437
|
+
ensureResourceIndexReady();
|
|
423
438
|
return resourceIndex.filter((entry) => entry.pinned);
|
|
424
439
|
}
|
|
425
440
|
|
|
@@ -459,6 +474,8 @@ function buildResourceLookupCandidates(uri) {
|
|
|
459
474
|
}
|
|
460
475
|
|
|
461
476
|
async function readResourceContent(uri) {
|
|
477
|
+
ensureResourceIndexReady();
|
|
478
|
+
|
|
462
479
|
let resource = null;
|
|
463
480
|
for (const candidate of buildResourceLookupCandidates(uri)) {
|
|
464
481
|
resource = resourceIndexByUri.get(candidate);
|
|
@@ -476,6 +493,8 @@ async function readResourceContent(uri) {
|
|
|
476
493
|
}
|
|
477
494
|
|
|
478
495
|
function getRagSignatureData() {
|
|
496
|
+
ensureResourceIndexReady();
|
|
497
|
+
|
|
479
498
|
return {
|
|
480
499
|
resourceCount: resourceIndex.length,
|
|
481
500
|
dcvCoreDocCount: dcvCoreDocs.length,
|
|
@@ -489,38 +508,38 @@ function getRagSignatureData() {
|
|
|
489
508
|
ddvDocCount: ddvDocs.articles.length,
|
|
490
509
|
versions: LATEST_VERSIONS,
|
|
491
510
|
dataSources: {
|
|
492
|
-
dbrWebSamplesHead:
|
|
493
|
-
dbrMobileSamplesHead:
|
|
494
|
-
dbrPythonSamplesHead:
|
|
495
|
-
dbrDotnetSamplesHead:
|
|
496
|
-
dbrJavaSamplesHead:
|
|
497
|
-
dbrCppSamplesHead:
|
|
498
|
-
dbrMauiSamplesHead:
|
|
499
|
-
dbrReactNativeSamplesHead:
|
|
500
|
-
dbrFlutterSamplesHead:
|
|
501
|
-
dbrNodejsSamplesHead:
|
|
502
|
-
dcvWebSamplesHead:
|
|
503
|
-
dcvMobileSamplesHead:
|
|
504
|
-
dcvPythonSamplesHead:
|
|
505
|
-
dcvDotnetSamplesHead:
|
|
506
|
-
dcvJavaSamplesHead:
|
|
507
|
-
dcvCppSamplesHead:
|
|
508
|
-
dcvMauiSamplesHead:
|
|
509
|
-
dcvReactNativeSamplesHead:
|
|
510
|
-
dcvFlutterSamplesHead:
|
|
511
|
-
dcvNodejsSamplesHead:
|
|
512
|
-
dcvSpmSamplesHead:
|
|
513
|
-
dwtSamplesHead:
|
|
514
|
-
ddvSamplesHead:
|
|
515
|
-
dbrWebDocsHead:
|
|
516
|
-
dbrMobileDocsHead:
|
|
517
|
-
dbrServerDocsHead:
|
|
518
|
-
dcvCoreDocsHead:
|
|
519
|
-
dcvWebDocsHead:
|
|
520
|
-
dcvMobileDocsHead:
|
|
521
|
-
dcvServerDocsHead:
|
|
522
|
-
dwtDocsHead:
|
|
523
|
-
ddvDocsHead:
|
|
511
|
+
dbrWebSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrWeb),
|
|
512
|
+
dbrMobileSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrMobile),
|
|
513
|
+
dbrPythonSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrPython),
|
|
514
|
+
dbrDotnetSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrDotnet),
|
|
515
|
+
dbrJavaSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrJava),
|
|
516
|
+
dbrCppSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrCpp),
|
|
517
|
+
dbrMauiSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrMaui),
|
|
518
|
+
dbrReactNativeSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrReactNative),
|
|
519
|
+
dbrFlutterSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrFlutter),
|
|
520
|
+
dbrNodejsSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dbrNodejs),
|
|
521
|
+
dcvWebSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvWeb),
|
|
522
|
+
dcvMobileSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvMobile),
|
|
523
|
+
dcvPythonSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvPython),
|
|
524
|
+
dcvDotnetSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvDotnet),
|
|
525
|
+
dcvJavaSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvJava),
|
|
526
|
+
dcvCppSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvCpp),
|
|
527
|
+
dcvMauiSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvMaui),
|
|
528
|
+
dcvReactNativeSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvReactNative),
|
|
529
|
+
dcvFlutterSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvFlutter),
|
|
530
|
+
dcvNodejsSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvNodejs),
|
|
531
|
+
dcvSpmSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dcvSpm),
|
|
532
|
+
dwtSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.dwt),
|
|
533
|
+
ddvSamplesHead: readManifestRepoCommit(SAMPLE_ROOTS.ddv),
|
|
534
|
+
dbrWebDocsHead: readManifestRepoCommit(DOC_ROOTS.dbrWeb),
|
|
535
|
+
dbrMobileDocsHead: readManifestRepoCommit(DOC_ROOTS.dbrMobile),
|
|
536
|
+
dbrServerDocsHead: readManifestRepoCommit(DOC_ROOTS.dbrServer),
|
|
537
|
+
dcvCoreDocsHead: readManifestRepoCommit(DOC_ROOTS.dcvCore),
|
|
538
|
+
dcvWebDocsHead: readManifestRepoCommit(DOC_ROOTS.dcvWeb),
|
|
539
|
+
dcvMobileDocsHead: readManifestRepoCommit(DOC_ROOTS.dcvMobile),
|
|
540
|
+
dcvServerDocsHead: readManifestRepoCommit(DOC_ROOTS.dcvServer),
|
|
541
|
+
dwtDocsHead: readManifestRepoCommit(DOC_ROOTS.dwt),
|
|
542
|
+
ddvDocsHead: readManifestRepoCommit(DOC_ROOTS.ddv),
|
|
524
543
|
registrySha256
|
|
525
544
|
}
|
|
526
545
|
};
|
|
@@ -580,6 +599,7 @@ export {
|
|
|
580
599
|
buildIndexData,
|
|
581
600
|
buildResourceIndex,
|
|
582
601
|
refreshResourceIndex,
|
|
602
|
+
ensureResourceIndexReady,
|
|
583
603
|
editionMatches,
|
|
584
604
|
platformMatches,
|
|
585
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
|
}
|