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.
@@ -28,8 +28,8 @@
28
28
  "branch": "main",
29
29
  "owner": "dynamsoft-docs",
30
30
  "repo": "barcode-reader-docs-server",
31
- "commit": "80a3d89c3b8ee53e4640a87fe36cf6d96714929e",
32
- "archiveUrl": "https://codeload.github.com/dynamsoft-docs/barcode-reader-docs-server/zip/80a3d89c3b8ee53e4640a87fe36cf6d96714929e"
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": "26ea218ada436b1bbae04a224ebe2d42a08fef5b",
82
- "archiveUrl": "https://codeload.github.com/dynamsoft-docs/document-viewer-docs/zip/26ea218ada436b1bbae04a224ebe2d42a08fef5b"
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": "275eefb2a97512e91cfca139ac58f8a8b6317f8a",
92
- "archiveUrl": "https://codeload.github.com/dynamsoft-docs/web-twain-docs/zip/275eefb2a97512e91cfca139ac58f8a8b6317f8a"
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": "2f0a723e88e3271170672e61c214a3d1d7a31328",
112
- "archiveUrl": "https://codeload.github.com/Dynamsoft/barcode-reader-javascript-samples/zip/2f0a723e88e3271170672e61c214a3d1d7a31328"
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",
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
- await maybeSyncSubmodulesOnStart();
15
- const dataStatus = await ensureDataReady();
16
- logEvent("data", "startup_mode", {
17
- mode: dataStatus.mode,
18
- path: dataStatus.dataRoot,
19
- cache_source: dataStatus.mode.includes("downloaded") ? (dataStatus.downloaded ? "fresh-download" : "cache") : "n/a"
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
- const resourceIndexApi = await import("./server/resource-index.js");
23
- const ragApi = await import("./rag/index.js");
29
+ let resourceIndexApi = null;
30
+ let ragApi = null;
24
31
 
25
- logEvent("profile", "resolved", {
26
- profile: ragApi.ragConfig.profile,
27
- provider: ragApi.ragConfig.provider,
28
- provider_source: ragApi.ragConfig.providerSource,
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
- const createServer = () => createMcpServerInstance({
34
- pkgVersion: pkg.version,
35
- resourceIndexApi,
36
- ragApi
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.ragConfig.prewarm) return;
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
- } else {
44
- void ragApi.prewarmRagIndex();
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
- let runtimeConfig;
49
- try {
50
- runtimeConfig = resolveRuntimeConfig();
51
- } catch (error) {
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
- logEvent("transport", "config_error", { message }, { level: "error" });
54
- process.exit(1);
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
- await startStdioServer({ createServer });
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, statSync } from "node:fs";
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 readSubmoduleHead(repoPath) {
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
- const gitMarker = join(repoPath, ".git");
66
- if (!existsSync(gitMarker)) return "";
67
- let gitDir = gitMarker;
68
- const markerStats = statSync(gitMarker);
69
- if (!markerStats.isDirectory()) {
70
- const markerText = readFileSync(gitMarker, "utf8").trim();
71
- if (!markerText.toLowerCase().startsWith("gitdir:")) return "";
72
- const relGitDir = markerText.slice("gitdir:".length).trim();
73
- gitDir = resolve(repoPath, relGitDir);
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
- const headPath = join(gitDir, "HEAD");
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
- readSubmoduleHead
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 { registryPath, DOC_ROOTS, SAMPLE_ROOTS, readSubmoduleHead } from "./resource-index/paths.js";
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
- refreshResourceIndex();
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: readSubmoduleHead(SAMPLE_ROOTS.dbrWeb),
493
- dbrMobileSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrMobile),
494
- dbrPythonSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrPython),
495
- dbrDotnetSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrDotnet),
496
- dbrJavaSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrJava),
497
- dbrCppSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrCpp),
498
- dbrMauiSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrMaui),
499
- dbrReactNativeSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrReactNative),
500
- dbrFlutterSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrFlutter),
501
- dbrNodejsSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dbrNodejs),
502
- dcvWebSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvWeb),
503
- dcvMobileSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvMobile),
504
- dcvPythonSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvPython),
505
- dcvDotnetSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvDotnet),
506
- dcvJavaSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvJava),
507
- dcvCppSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvCpp),
508
- dcvMauiSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvMaui),
509
- dcvReactNativeSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvReactNative),
510
- dcvFlutterSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvFlutter),
511
- dcvNodejsSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvNodejs),
512
- dcvSpmSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dcvSpm),
513
- dwtSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.dwt),
514
- ddvSamplesHead: readSubmoduleHead(SAMPLE_ROOTS.ddv),
515
- dbrWebDocsHead: readSubmoduleHead(DOC_ROOTS.dbrWeb),
516
- dbrMobileDocsHead: readSubmoduleHead(DOC_ROOTS.dbrMobile),
517
- dbrServerDocsHead: readSubmoduleHead(DOC_ROOTS.dbrServer),
518
- dcvCoreDocsHead: readSubmoduleHead(DOC_ROOTS.dcvCore),
519
- dcvWebDocsHead: readSubmoduleHead(DOC_ROOTS.dcvWeb),
520
- dcvMobileDocsHead: readSubmoduleHead(DOC_ROOTS.dcvMobile),
521
- dcvServerDocsHead: readSubmoduleHead(DOC_ROOTS.dcvServer),
522
- dwtDocsHead: readSubmoduleHead(DOC_ROOTS.dwt),
523
- ddvDocsHead: readSubmoduleHead(DOC_ROOTS.ddv),
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({ host, port, mcpPath, createServer }) {
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
- const server = createServer();
17
- const transport = new StreamableHTTPServerTransport({
18
- sessionIdGenerator: undefined
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
- await transport.close();
60
+ if (transport) {
61
+ await transport.close();
62
+ }
27
63
  } catch {}
28
64
  try {
29
- await server.close();
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
- process.on("SIGINT", () => {
75
- void shutdown("SIGINT");
76
- });
77
- process.on("SIGTERM", () => {
78
- void shutdown("SIGTERM");
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
  }