There is a race condition during the startup of MCP servers (e.g., memory-core) that rely on singleton services with asynchronous initialization.
Problem:
- The main server entry point (
mcp-stdio.mjs) imports services at startup.
- Services like
ChromaManager extend core.Base and perform async setup (like database connections) in initAsync().
core.Base calls initAsync() in a non-blocking way from the construct() method.
- Other services (e.g.,
HealthService) are called immediately in the startup sequence, before initAsync() is guaranteed to have completed, leading to failed checks and incorrect "unhealthy" status reports.
Solution:
Enhance Neo.core.Base to provide a standardized and reliable way to await the completion of an instance's asynchronous initialization.
Introduce an awaitable ready() method to Neo.core.Base:
- Add private
#readyPromise and #readyResolver fields to core.Base.
- In the
construct() method, initialize the promise.
- Add a public
ready() method that returns this promise.
Centralize Ready Logic in afterSetIsReady:
- Create an
afterSetIsReady(value, oldValue) hook in core.Base.
- When
isReady becomes true, this hook will:
- Resolve the
#readyPromise.
- For observable classes (
static observable = true), fire a ready event, passing the instance as the payload.
- Update the JSDoc for the
isReady_ config to reflect this new behavior.
Refactor consumers:
- Update
ai/mcp/server/memory-core/mcp-stdio.mjs to use await ChromaManager.ready() before performing the initial health check.
This change will provide a clean, reusable, and framework-aligned solution for managing async initialization in Neo.mjs classes.
There is a race condition during the startup of MCP servers (e.g.,
memory-core) that rely on singleton services with asynchronous initialization.Problem:
mcp-stdio.mjs) imports services at startup.ChromaManagerextendcore.Baseand perform async setup (like database connections) ininitAsync().core.BasecallsinitAsync()in a non-blocking way from theconstruct()method.HealthService) are called immediately in the startup sequence, beforeinitAsync()is guaranteed to have completed, leading to failed checks and incorrect "unhealthy" status reports.Solution: Enhance
Neo.core.Baseto provide a standardized and reliable way to await the completion of an instance's asynchronous initialization.Introduce an awaitable
ready()method toNeo.core.Base:#readyPromiseand#readyResolverfields tocore.Base.construct()method, initialize the promise.ready()method that returns this promise.Centralize Ready Logic in
afterSetIsReady:afterSetIsReady(value, oldValue)hook incore.Base.isReadybecomestrue, this hook will:#readyPromise.static observable = true), fire areadyevent, passing the instance as the payload.isReady_config to reflect this new behavior.Refactor consumers:
ai/mcp/server/memory-core/mcp-stdio.mjsto useawait ChromaManager.ready()before performing the initial health check.This change will provide a clean, reusable, and framework-aligned solution for managing async initialization in Neo.mjs classes.