Repository navigation
fix(filesystem): reject non-init requests before MCP handshake completes - #4203
nakshatra-nahar wants to merge 3 commits into
Conversation
Currently, the secure-filesystem-server accepts and successfully handles tools/list (and other requests) before the MCP lifecycle handshake completes. Per the MCP spec, the only methods a server should accept pre-handshake are initialize and ping. The MCP TypeScript SDK does not currently expose a middleware or pre-dispatch hook for request gating, so we wrap the protocol's internal request handler map. After all tool registrations have installed SDK-managed tools/list and tools/call handlers, installHandshakeGate() replaces each entry except initialize and ping with a wrapper that throws InvalidRequest until oninitialized has fired. Add an integration test that exercises both directions: - tools/list before initialize -> rejected with code -32600 - full initialize -> notifications/initialized -> tools/list -> accepted Closes modelcontextprotocol#4195
LuuOW
left a comment
There was a problem hiding this comment.
Technical audit: Verified MCP server implementation for consistency with current SDK patterns.
Can we go with these changes @LuuOW |
|
Thank you for this pull request, and apologies for the long wait for a response. Since it was opened, the reference servers have been rebuilt for the 2026-07-28 MCP specification (#4857) and released as v2 (2.0.0 on npm for the TypeScript servers, 2026.10.19 on PyPI for the Python servers). The code this PR changes has been replaced, and the behavior it addresses has either been fixed in v2 or no longer exists in the same form. This is not a judgment on the change or on you; it's a consequence of the code it targets having been replaced. If the problem still happens on v2, please open a fresh issue with the version you're running and steps to reproduce. Note that this repository now accepts contributions as issues rather than pull requests from anyone but its maintainers; see This is part of the backlog triage in #4875. |
Description
Currently
secure-filesystem-serveraccepts and successfully handlestools/list(and other requests) before the MCP lifecycle handshake completes. Per the MCP spec, the only methods a server should accept pre-handshake areinitializeandping. This PR adds a pre-dispatch gate that rejects all other methods with JSON-RPCInvalidRequest(-32600) until the handshake finishes.Server Details
@modelcontextprotocol/server-filesystem)src/filesystem/index.ts+ new integration test fileMotivation and Context
Closes #4195.
Returning a normal JSON-RPC
resultfortools/list(or anything else) before handshake:initialize.Implementation note (please flag if a different shape is preferred): the MCP TypeScript SDK does not currently expose a middleware or pre-dispatch hook for request gating, so
installHandshakeGate()wraps the protocol's internal_requestHandlersmap. It is invoked once, after allserver.registerTool(...)calls have run, and replaces each entry exceptinitializeandpingwith a thin wrapper that throwsInvalidRequestuntiloninitializedhas fired. A longer-term fix likely belongs at the SDK layer (modelcontextprotocol/typescript-sdk) so every reference server benefits; happy to file a follow-up issue there if you'd like.How Has This Been Tested?
src/filesystem/__tests__/handshake-gating.test.tsadds two integration tests, both spawningdist/index.jsand exchanging line-delimited JSON-RPC over stdio:tools/listsent withoutinitialize→ asserts response haserror.code === -32600and message matches/handshake|initialize/i, with noresultkey.initialize→notifications/initialized→tools/list) → asserts a populatedresult.toolsarray.index.tschange, rebuilt, re-ran the gating tests — Test 1 fails as expected, confirming the regression test catches the bug.npm test --workspace=src/filesystempasses: 8 test files, 148/148 tests (146 pre-existing + 2 new).npm run build --workspace=src/filesystemis clean (tsc exit 0, no warnings).Breaking Changes
No breaking changes for spec-compliant clients (all official MCP clients perform the
initializehandshake before sending other requests). Clients that incorrectly skip the handshake and previously got a successful response fromtools/listwill now receive a JSON-RPCInvalidRequesterror — which is the correct, spec-mandated behaviour.Types of changes
Checklist
Clientexercises this path via the existingstructured-content.test.tssuite)Additional context
The fix lives entirely inside
src/filesystem/index.ts(singleinstallHandshakeGate()function + one flag + one flag-set in the existingoninitializedcallback). The other reference servers in this repo (everything,fetch,git,memory,sequentialthinking,time) have the same underlying SDK behaviour and would benefit from the same gating; I intentionally kept this PR scoped tofilesystemto match the issue and to keep the diff reviewable. Happy to send follow-up PRs per server, or to wait for an SDK-level fix — whichever maintainers prefer.