fix(filesystem): wait for roots initialization before handling tool calls#3806
Conversation
Add a Promise-based gate that blocks tool handlers until allowedDirectories are initialized. This prevents a race condition where tool calls arrive before oninitialized finishes fetching roots, causing false "Access denied" errors. If CLI args already provide directories the gate opens immediately. Otherwise it opens when oninitialized completes (via try/finally). Fixes modelcontextprotocol#3204 Signed-off-by: majiayu000 <1835304752@qq.com>
Extract the inline Promise gate into roots-gate.ts with a configurable timeout (default 10s) to prevent indefinite hangs when oninitialized never fires. Add unit tests for immediate resolution, delayed resolution, timeout rejection, and concurrent waiters. Signed-off-by: majiayu000 <1835304752@qq.com>
|
@claude review |
|
Claude finished @cliffhall's task in 2m 3s —— View job PR Review: fix(filesystem): wait for roots initialization before handling tool calls
SummaryThis PR correctly identifies and fixes a real race condition: when an MCP client provides roots instead of CLI args, The overall implementation is sound. A few issues worth addressing: Issues1. Timer leak in
|
Clear per-waiter timeout handles once roots initialization completes so ready gates do not leave pending timers behind. Add an explicit post-resolve test and narrow the gate API to the methods callers actually use. Signed-off-by: majiayu000 <1835304752@qq.com>
Fixes #3204
Description
Tool calls can arrive before
oninitializedfinishes fetching roots, soallowedDirectoriesis empty and every path check returns "Access denied". This adds a Promise-based gate that blocks tool handlers until roots are loaded.The SDK fires
oninitializedwithout awaiting it (() => this.oninitialized?.()in SDK index.js:53). The filesystem server's handler asynchronously callsserver.server.listRoots(), but tool handlers run immediately against an emptyallowedDirectories.Server Details
Motivation and Context
When CLI args aren't provided and roots come from the client, there's a window where tool calls hit empty
allowedDirectories. This is the race condition described in #3204.How Has This Been Tested?
__tests__/roots-gate.test.tscovering: immediate resolution, delayed resolution, timeout, rejection, and concurrent waiters.lib.test.tshas a pre-existingvi.mockfailure on main, unrelated to this change.)npx tsc --noEmitpasses clean.Breaking Changes
None. If CLI args already provide allowed directories, the gate resolves immediately and there's no behavior change. The 10s timeout only applies when waiting for client-provided roots.
Types of changes
Checklist
Additional context
What changed:
roots-gate.ts(~25 lines) -createRootsGate(timeoutMs)factory returning{ promise, resolve, waitForReady }. Races the internal promise against a configurable timeout.index.ts- imports the gate, resolves it immediately when CLI args provide directories, resolves it inoninitializedafter roots load, and addsawait gate.waitForReady()at the top of all 13 tool handlers.__tests__/roots-gate.test.ts- 5 test cases for the gate in isolation.