Repository navigation
Bugs, Coverage, and SDK Wave 1 Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate #4854
Description
Activity
- changed the title
[-]2026-07-28 Spec Refactor Part 4: Modern-era Python servers[/-][+]2026-07-28 Spec Refactor Part 5: Extensive new-spec unit tests for TypeScript servers[/+]on Sep 26, 2026 - added a parent issue
on Sep 26, 2026 - changed the title
[-]2026-07-28 Spec Refactor Part 5: Extensive new-spec unit tests for TypeScript servers[/-][+]2026-07-28 Spec Refactor Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate[/+]on Sep 26, 2026 - added a commit that references this issue
on Sep 28, 2026 Here is a coverage baseline for the four TypeScript servers to size this work, from
mainat f46d957 on 2026-09-28. 17 of 47 files meet the 90% gate. Helpers and pure functions are well tested. The code that registers and serves the tools is not, and that is the layer Part 3 changes. A few small refactors have to land before in-process tests can start. Issues that turned out not to be gaps are listed at the end.Baseline
Server Tests passing Files at 90% Lowest files (lines %) everything 109 16 of 37 index.tsand all three transports 0,server/roots.ts11.8,tools/trigger-elicitation-request-async.ts19.6,prompts/completions.ts25.0filesystem 168 1 of 5 index.ts0,path-validation.ts83.3,lib.ts85.4memory 87 0 of 2 index.ts83.5sequentialthinking 26 0 of 3 index.ts0Per-file tables (Node 24.19, vitest 4.1.8,
vitest run --coverageper package, same method as #4474)Bold is below the gate.
everything
File Lines Stmts Funcs Branches index.ts0 0 0 0 transports/stdio.ts,sse.ts,streamableHttp.ts0 0 0 0 server/roots.ts11.8 11.8 0 0 server/index.ts60.0 54.5 25.0 0 prompts/completions.ts25.0 20.0 22.2 0 tools/trigger-elicitation-request-async.ts19.6 18.9 33.3 9.3 tools/trigger-sampling-request-async.ts29.7 28.9 33.3 16.7 tools/get-roots-list.ts53.8 53.8 33.3 23.1 resources/files.ts65.5 59.4 50.0 30.0 resources/templates.ts70.0 68.3 50.0 26.7 resources/subscriptions.ts79.5 77.5 75.0 50.0 tools/simulate-research-query.ts76.5 74.5 70.0 68.6 tools/gzip-file-as-resource.ts81.2 81.4 75.0 61.1 resources/session.ts85.7 85.7 100 75.0 tools/trigger-elicitation-request.ts90.9 84.2 100 63.3 server/logging.ts93.3 93.3 75.0 66.7 tools/trigger-sampling-request.ts100 100 100 75.0 tools/get-annotated-message.ts,get-resource-links.ts100 100 100 87.5 the other 16 files 100 100 100 100 filesystem
File Lines Stmts Funcs Branches index.ts0 0 0 0 lib.ts85.4 85.9 92.0 73.0 path-validation.ts83.3 83.3 100 90.0 roots-utils.ts91.7 91.7 100 71.4 path-utils.ts94.4 94.4 100 94.3 memory
File Lines Stmts Funcs Branches index.ts83.5 84.3 84.2 79.0 version.ts90.0 90.0 100 50.0 sequentialthinking
File Lines Stmts Funcs Branches index.ts0 0 0 0 lib.ts96.7 96.7 100 88.9 version.ts90.0 90.0 100 50.0 Prerequisites
- Every
index.tsneeds acreateServer(...)export and a guardedmain(). - filesystem's allow-list (
setAllowedDirectoriesinlib.ts) has to move from a module global to per-instance. - everything tests need to reset its module-level session maps or set
serverTransport.sessionIdbeforeconnect. - everything's
sse.tsandstreamableHttp.tsneed acreateApp()refactor before any HTTP test. registrations.test.tsfails on a cold start. Hoisting its dynamic imports removes the cause.
Why each one is needed
- Every
index.tsbuilds the server and connects stdio at module load. Importing one from vitest attaches the server to the runner's stdio. - filesystem also does a top-level
awaitoverprocess.argv. memory assigns its manager only insidemain(). - Two in-process filesystem servers cannot coexist while the allow-list is a module global.
- The everything maps are in
server/roots.ts,server/logging.ts,resources/subscriptions.ts,resources/session.tsand both toggle tools.InMemoryTransporthas nosessionId, so every in-memory session shares theundefinedkey. sse.tsandstreamableHttp.tscallapp.listenat import and export nothing.registrations.test.tsdoes its dynamic imports inside the 5s default timeout. A cold start took 7s here.
What the new suites should pin
Each item was checked against the source on
main.filesystem
- Seven of 14 tools are never called from a test.
- Annotations:
idempotentHintis missing on the 10 read-only tools (Add missing idempotentHint and openWorldHint to server-filesystem #3402). - Roots: server-filesystem: MCP roots protocol overwrites CLI-provided allowed directories #3602, Filesystem MCP: Server should wait inital roots to be loaded before handling tool calls #3204, and an unhandled rejection at
index.ts:768. - Writes: a failed rename (filesystem edit_file: EPERM error when renaming over locked files on Windows #3199) and inode replacement on every overwrite (filesystem:
write_file/edit_filedestroy file creation time (birthtime) and file identity due to atomic-rename write strategy #4512). edit_file: the whitespace-tolerant matcher is entirely uncovered (edit_file tool fails when exact text match not found due to whitespace differences #2034).- Path validation: NFD allowed directory (🐛 MCP Filesystem: macOS Screenshot Files Fail with Unicode Characters (ENOENT Error) #1970) and UNC root on win32 (UNC/network share paths (\\server\share\subdir) fail access check despite being under allowed directory #3527).
- Stdio: filesystem: malformed JSON-RPC message on stdio causes crash or zombie state #4206, filesystem: ~1MB JSON-RPC line on stdio causes crash or zombie state #4207 and filesystem: tools/list succeeds without initialize handshake (MCP lifecycle bypass) #4195 can all be pinned in-process.
version: "0.2.0"is hardcoded (Sync reported server versions with package.json and pyproject.toml #360).
filesystem details
- Untested tools:
read_file,read_text_file,read_multiple_files,write_file,edit_file,get_file_info,list_allowed_directories. - The draft-07
$schemain server-filesystem: tools/list schemas declare unsupported $schema draft-07 dialect, rejected by strict 2020-12 validators #4841 comes from the SDK'smapMiniTargetdefault and can only be characterized here. - server-filesystem: MCP roots protocol overwrites CLI-provided allowed directories #3602:
oninitializedreplaces CLI directories with roots, as the README says. - Filesystem MCP: Server should wait inital roots to be loaded before handling tool calls #3204: a
tools/callsent right afterconnectruns before the client answersroots/list. This is deterministic in-process. - The throw at
index.ts:768fires with no roots capability and no CLI args. It is unhandled because the SDK does not awaitoninitialized. - No test covers a
pathToFileURL(...).hrefroot.roots-utils.test.tsbuildsfile://${dir}by concatenation. lib.ts221-224 and 346-349 (rename failed, temp removed) are uncovered. A mocked EPERM rename covers them and characterizes filesystem edit_file: EPERM error when renaming over locked files on Windows #3199.- For filesystem:
write_file/edit_filedestroy file creation time (birthtime) and file identity due to atomic-rename write strategy #4512, assertino, notbirthtime. - The whitespace matcher is at
lib.ts:299-318. A test is named for it, but every existingedit_filetest passes an exact substring. - 🐛 MCP Filesystem: macOS Screenshot Files Fail with Unicode Characters (ENOENT Error) #1970: an allowed directory whose own name is NFD rejects NFC requests before the Unicode walk starts.
- UNC/network share paths (\\server\share\subdir) fail access check despite being under allowed directory #3527: a UNC share as the allowed root fails at
path-validation.ts:84because of a doubled separator. path-validation.ts32, 37, 56, 61 are unreachable and want a justifiedv8 ignore.lib.ts:473(search recursion) is uncovered because every search test mocks a flatreaddir.StdioServerTransporttakes streams, so the stdio cases run in-process. filesystem: malformed JSON-RPC message on stdio causes crash or zombie state #4206 (malformed line) and filesystem: ~1MB JSON-RPC line on stdio causes crash or zombie state #4207 (1 MiB line) both survive on main. filesystem: tools/list succeeds without initialize handshake (MCP lifecycle bypass) #4195 (pre-inittools/list) is answered on main.- The version is hardcoded at
index.ts:167, so no bug report can be dated fromserverInfo.
memory
- All nine tool handlers are uncovered. The tests call
KnowledgeGraphManagerdirectly. create_entitiesdrops observations for existing entities (memory: create_entities silently drops observations for existing entities #4887, PR fix(memory): report entities that create_entities skipped #4888).- The next write deletes unreadable lines (memory: unreadable lines are deleted by the next write (regression from #4717, unreleased) #4885, PR fix(memory): keep unreadable lines when saving the graph #4886).
- Temp-file rename loses permission bits on POSIX (server-memory: saveGraph rewrites memory.jsonl via temp-file + rename, dropping an existing file's permission bits (0600 -> 0644) and silently overwriting read-only files #4827, PR fix(memory): preserve memory file permissions across atomic save #4828).
- Two writers on one file (memory: two server processes sharing MEMORY_FILE_PATH silently discard each other's writes (the #4555 mutex is per-process) #4797) reproduce in-process with two manager instances.
- memory is the only TS server with no subprocess stdio smoke test.
memory details
- The handlers are
index.ts420-687. create_entitiesreports only the created entities.- The memory: unreadable lines are deleted by the next write (regression from #4717, unreleased) #4885 seed file also covers the remaining
loadGraphbranches (124-126, 140, 158). - For memory: two server processes sharing MEMORY_FILE_PATH silently discard each other's writes (the #4555 mutex is per-process) #4797, stall both instances between load and save.
notifyGraphUpdated(395-399) is the only uncovered part of the resource path.
sequentialthinking
index.ts11-134 is the whole gap.- The sequentialthinking: a branchId that collides with an Object.prototype key throws instead of creating the branch, and the failed call still extends thoughtHistory #4813 test (branch id colliding with an
Object.prototypekey, PR fix(sequentialthinking): track branches whose IDs match Object.prototype keys #4814) alone bringslib.tsto 100% lines. - Annotations are wrong for a stateful server (sequential-thinking: readOnlyHint and idempotentHint annotations are inaccurate (server is stateful, non-idempotent) #4721, four competing PRs).
- The tool description is 2781 characters (sequentialthinking tool description beyond OpenAI length limitation #799). A 1024 assertion fails today, so pin the current length.
- Porting
input-schema.test.tsto the in-memory client removes its skip whendist/is absent.
everything
- After
createApp(): cross-stream event replay (Issues with streamable http everying example: #4087) and a SIGINT handler that never closes a transport. - The unsubscribe handler is the cheapest pin for the fix(everything): clean up subscriptions on session disconnect #4712 cleanup change. It needs no HTTP.
- Both async task tools are never referenced by a test.
docs/instructions.mdnames three capability-gated tools unconditionally (everything server: instructions unconditionally reference sampling/elicitation tools that only exist when the client declares those capabilities #4792).- Two sessions with the same file name evict each other (everything: session resources evict another session's resource when two sessions use the same file name #4808). The issue's two-client repro is the test.
- everything sever response with an extra empty SSE event causing the java mcp client to fail deserialization #3267's priming event is SDK behavior required by SEP-1699.
everything details
- Issues with streamable http everying example: #4087:
InMemoryEventStorereplays events across streams (streamableHttp.ts:21-36). - The SIGINT handler at
streamableHttp.ts:228usesfor...inover aMap. - The unsubscribe handler (
resources/subscriptions.ts:69-96) is wholly uncovered. - A client that declares
sampling,elicitation,tasks.requests.*and passestaskStore: new InMemoryTaskStore()covers both async task tools andsimulate-research-query301-317. - everything server: instructions unconditionally reference sampling/elicitation tools that only exist when the client declares those capabilities #4792 covers
trigger-sampling-request,trigger-elicitation-requestandget-roots-list, which are registered only when the client declares the capability. - everything: session resources evict another session's resource when two sessions use the same file name #4808 comes from the module-level map at
resources/session.ts:58-62. server/roots.ts32-89 andget-roots-list.ts49-94 never execute.server/index.tsoninitializedandcleanupnever run.
Two small inconsistencies block the branch gate:
blobResourcereturnsmimeType: "text/plain"(templates.ts:108) against the template'sapplication/octet-stream. That leavesget-resource-links.ts:76dead.trigger-elicitation-request.ts198 and 207 are dead relative to the requested schema.
Not gaps after all
- Fixed on main and already tested, so port the test: Filesystem MCP: move_file tool returns array instead of string (Error -32602) #3093, [BUG] Filesystem server crashes completely when any configured path is invalid #2113, edit_file tool fails with PowerShell expressions containing dollar signs #2033, Filesystem MCP: create_directory fails with relative paths but works with absolute paths - Kiro AI IDE #2416, File system tools unable to traverse symlinked directories beyond top level #734, Filesystem server: roots are not working on Windows #3174.
- Not reproducible on main: server-filesystem: missing inputSchema.type breaks JSON Schema 2020-12 validators (all versions 0.6.2–2025.8.21) #4772 (SDK 1.30 emits
type: "object"), Case sensitivity preservation in list_allowed_directories #499, Filesystem MCP server crashes silently on paths containing ~ (tilde) character #3412, [filesystem] write_file silently returns success but never writes to disk on Windows #4138, [everything] Unintuitive / misleading resource indexing #475 (the numbered resource code is gone). - About code in unmerged PR fix(filesystem): timeouts + max-visited cap for hangs on lazy provider paths (#4162) #4212: fix(filesystem): harden atomicReplaceFile fallback from PR #3296 #3430, filesystem: recursive search can hang on macOS CloudStorage / lazy provider paths #4162, filesystem: FS_SEARCH_EXCLUDE_PREFIXES is lexical; should excludes be canonical/symlink-aware? #4208.
- Not server bugs: Whitespace issues in filesystem server #1590, Multiple edits should start from the bottom of a file in filesystem server #1591, filesystem MCP server doesn't fully support filenames that contain the "NARROW NO-BREAK SPACE" character (U+202F) #1597, [Bug]: Filesystem server glob pattern matching inconsistent across platforms #3517, MCP Filesystem UTF-8 Issues on Windows with German Umlaute #2098, Memory MCP ignores custom storage path setting #692,
sequentialthinking's tool definition costs ~921 tokens/session; about half duplicates the schema's own parameter descriptions #4507, 'everything' server notification/progress does not seem to work correctly #2621. - Already covered: the
get-envleak (tools.test.ts:159-174),trigger-long-running-operation.ts(100%), memory's subscribe handlers (resource.test.ts).
#499, #734 and #475 look closeable.
The scan is scripted and can be rerun against
v2/mainas slices land, with per-file deltas against this baseline.
Scan and verification done with help from Claude Code. The coverage numbers come from a real run; the per-issue verdicts were checked against the source but are a starting point, not a review.
- Every
- linked a pull request that will close this issueci(coverage): enforce the TypeScript per-file coverage gate in CI and local:gate #4969
on Oct 4, 2026 - added 5 commits that reference this issue
on Oct 4, 2026 6 remaining items
- added 7 commits that reference this issue
on Oct 4, 2026 - changed the title
[-]2026-07-28 Spec Refactor Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate[/-][+]2026-07-28 Spec Refactor Wave 1 Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate[/+]on Oct 5, 2026 - removed a parent issue
on Oct 10, 2026 - added a parent issue
on Oct 10, 2026 - changed the title
[-]2026-07-28 Spec Refactor Wave 1 Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate[/-][+]Bugs, Coverage, and SDK Wave 1 Part 1: TypeScript servers: extensive unit tests + per-file 90% coverage gate[/+]on Oct 10, 2026
Part of the 2026-07-28 Spec Refactor tracker #4857. This is the first step, and has no dependencies.
Goal
Before any SDK or spec changes, build an extensive vitest suite that pins down the current behavior of every TypeScript server (
everything,filesystem,memory,sequentialthinking) on@modelcontextprotocol/sdk@1.x. The suite must pass the inspector's coverage quality gate from #4474.This suite is the regression net for Part 3 (#4856). The SDK v2 migration is supposed to be transparent on the wire, and these tests are how we prove it. Existing tests may be deleted where the new suite covers them better.
Test design
Clientto each server over an in-memory transport, and assert on what goes over the wire: tool, resource and prompt lists, call results, errors and notifications.createServer()factory thatindex.tscalls. Today thefilesystemintegration tests spawn the compiled server as a child process, so V8 reportsindex.tsas 0% covered (see the baseline in Bring all servers to the 90% per-file coverage gate; reintroduce the rule to AGENTS.md #4474). The in-process approach fixes that.everything: every tool, resource (including templates), prompt and completion. Also:filesystem:memory: knowledge-graph CRUD, persistence (atomic save, file path), search, and resource behavior.sequentialthinking: input schema, and branching and revision logic.Quality gate (from #4474, modeled on the inspector's
v2/mainAGENTS.md)coverage: { provider: 'v8', thresholds: { perFile: true, lines: 90, statements: 90, functions: 90, branches: 90 } }./* v8 ignore next -- <reason> */.npm run coverageper server, and a rootnpm run coveragethat chains the workspaces). If TypeScript workspace gate: Prettier, ESLint, root validate, CI #4864 lands first, it has already split each workspace'stestfromcoverage, so this issue adds the thresholds to the existingcoveragescript. It is not part of the fasttest/validateloop.coveragejob next to the fast tests, as the Inspector's CI now does (inspector#2159). Maintainers decided this on PR docs: agentic software factory inception (docs/agent-guidance-inception.md) #4861 (the agentic software factory inception doc). The coverage command stays separate from the fasttest/validateloop locally. This issue wires it in (changed from the earlier plan, which left the wiring to Add local:gate, pre-push-gate skill #4871): add the parallelcoveragejob totypescript.ymland a TS coverage stage tolocal:gate, and record the stage indocs/quality-gate.mdand thepre-push-gateskill. Add local:gate, pre-push-gate skill #4871 buildslocal:gatefirst, without coverage, so that it does not have to wait for this suite.Acceptance criteria
npm run coverageat 90/90/90/90 per file, enforced by config.npm run local:gatefails on it too.main(SDK v1) in CI.Carried over from #4474, which this issue supersedes
npm run coveragescript that chains the per-workspace coverage runs.AGENTS.mdunder "Always test new or modified code", in this issue's PR (a rule inAGENTS.mdmust be true the day it is stated, so each language's half lands with its own gate; Bugs, Coverage, and SDK Wave 1 Part 2: Python servers: extensive unit tests + per-file 90% coverage gate #4855 adds the Python half). Adapt the wording from the inspector's reference, and cover the per-file ≥ 90 threshold on all four dimensions, the rule that every ignore needs a reason, and thecoveragevsvalidatesplit.filesystemfrom Bring all servers to the 90% per-file coverage gate; reintroduce the rule to AGENTS.md #4474, measured onmainat76d64c82(statements / branches / functions / lines):index.ts: 0 across the board, because it only runs in spawned processeslib.ts: about 77 / 66 / 89 / 77