Skip to content

server-filesystem ≤2025.8.21 emits empty inputSchema when zod v4 is resolved #4661

Closed

Description

@pauxier-coder

Describe the bug
On @modelcontextprotocol/server-filesystem versions up to and including 2025.8.21, a fresh install resolves zod v4, and the server then advertises every tool with an empty inputSchema — just {"$schema": "http://json-schema.org/draft-07/schema#"}, with no type, no properties, and no required.

Schema conversion appears to fail silently rather than throwing. The server logs Server started and connected successfully and returns a well-formed tools/list response, so nothing looks wrong from the outside — but every tool definition in it is unusable. Strict MCP clients reject the server outright at tools/list.

Forcing zod back to 3.25.76 fixes it completely. Same server version, same Node, same config — the zod version is the only variable.

To Reproduce
Steps to reproduce the behavior:

Install the server on its own:
mkdir /tmp/fsrepro && cd /tmp/fsrepro
npm init -y
npm install @modelcontextprotocol/[email protected]
Confirm zod resolved to v4:
node -e "console.log(require('./node_modules/zod/package.json').version)"
Launch the server over stdio:
node node_modules/@modelcontextprotocol/server-filesystem/dist/index.js /tmp
Send initialize, then notifications/initialized, then tools/list.

Inspect any tool's inputSchema in the response.

To confirm the cause, add an overrides block forcing zod to 3.25.76, reinstall, and repeat steps 3–5. The schemas come back correct.

Expected behavior

tools/list should return complete input schemas, as it does when zod v3 is installed:

{
"name": "write_file",
"inputSchema": {
"type": "object",
"properties": {
"path": { "type": "string" },
"content": { "type": "string" }
},
"required": ["path", "content"],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
}
}
Failing that, the schema conversion should throw rather than silently returning an empty object, so the incompatibility surfaces immediately instead of appearing as a client-side problem.

Logs
With zod v4 resolved, all 14 tools come back like this:

{
"name": "write_file",
"inputSchema": { "$schema": "http://json-schema.org/draft-07/schema#" }
}
The client rejects the server:

Invalid result for tools/list: [ {
"code": "invalid_value",
"values": ["object"],
"path": ["tools", 0, "inputSchema", "type"],
"message": "Invalid input: expected "object""
}, ... ]
With zod pinned to 3.25.76, probing the same build: 14 tools, all with inputSchema.type == "object", full properties and required. Server loads and every tool works.

Additional context
Verified workaround — npm overrides forcing zod to v3:

{
"dependencies": { "@modelcontextprotocol/server-filesystem": "2025.8.21" },
"overrides": { "zod": "3.25.76" }
}
Likely cause — flagging this as a hypothesis, since I haven't read the conversion code: these releases look like they call a zod v3 API surface while the dependency range permits v4 to resolve, and the mismatch makes conversion return an empty object instead of raising. What I verified directly is only the input/output relationship: zod v4 → empty schemas, zod v3.25.76 → correct schemas, everything else held constant.

Suggested fix — constrain the zod dependency to the major version the code targets, or make schema conversion throw on failure. Either would turn this into an immediate, obvious error.

Why it's worth fixing rather than leaving to version pinning: the failure is silent and misattributed. The resulting client-side error reads like a filesystem permissions or sandbox problem, so users go looking at their own folder configuration. I spent several weeks there before finding this.

Environment

@modelcontextprotocol/server-filesystem 2025.8.21; also reproduced on 2025.7.29 and 2025.8.18
macOS, Darwin 25.6.0
Reproduced both via npx -y and via local npm install
Related, but not this repo's bug: versions from 2025.11.25 onward emit correct input schemas, but declare outputSchema in JSON Schema draft-07, which some clients reject as an unsupported dialect. That's a client-side strictness question and I've reported it to that vendor separately. Worth mentioning only because the combination means there's currently no release of this package that works with such a client — newer releases fail on outputSchema, older ones fail on the empty inputSchema described above.

Activity

  1. ramiiZPD commented on Aug 21, 2026

    @ramiiZPD

    Thanks for the detailed writeup, the repro steps made this easy to check. I pulled current main (599dafc1), installed src/filesystem fresh with no overrides, and it resolved the same zod major you flagged ([email protected] alongside @modelcontextprotocol/[email protected]). Built it, ran it over stdio, and walked through initialize → notifications/initialized → tools/list by hand.

    Couldn't reproduce it, though. All 14 tools came back with full schemas. write_file looked like this:

    {
      "$schema": "http://json-schema.org/draft-07/schema#",
      "type": "object",
      "properties": {
        "path": { "type": "string" },
        "content": { "type": "string" }
      },
      "required": ["path", "content"]
    }

    Dug a bit further to see why, since your hypothesis about the conversion made sense to me. Turns out @modelcontextprotocol/[email protected] already has a fix for this: server/zod-json-schema-compat.js checks for zod v4 (schema._zod) and hands those schemas to zod/v4-mini's own toJSONSchema() instead of the old zod-to-json-schema package, which really is still v3-only under the hood. That compat file evidently didn't exist yet in the SDK version bundled with the server-filesystem release you tested (2025.7.29 through 2025.8.21), so this was a real bug, it's just been fixed one layer up since then.

    Since servers itself never does the schema conversion (it just hands .shape to registerTool), there's nothing to patch here. It's inherited automatically once the SDK dependency is current. Probably safe to close, unless there's a still-published server-filesystem build out there pinned to an older SDK that would still hit this.

  2. pauxier-coder commented on Aug 21, 2026

    @pauxier-coder
    Author

    Thanks for running it. That's more than I expected.

    I think we tested two different things. You built main. My report is about the published tarballs on npm, which is what npx -y @modelcontextprotocol/[email protected] actually runs, and that's different code.

    I re-ran it today against the published 2025.8.21. Fresh install, no overrides:

    @modelcontextprotocol/[email protected]
    +-- @modelcontextprotocol/[email protected]
    | +-- [email protected] deduped
    | -- [email protected] -- [email protected]
    Same SDK 1.30.0 and the same zod 4.4.3 you had. Still broken. Walking initialize → notifications/initialized → tools/list gives 14 tools, and 13 of them come back as just this:

    { "$schema": "http://json-schema.org/draft-07/schema#" }
    The one that survives is list_allowed_directories, and only because its schema is written out as a literal object in the source instead of being converted.

    Here's why the SDK compat file doesn't help. Published 2025.8.21 never calls registerTool. It registers a raw handler and builds every schema itself:

    dist/index.js:9 import { zodToJsonSchema } from "zod-to-json-schema";
    dist/index.js:134 server.setRequestHandler(ListToolsRequestSchema, async () => {
    dist/index.js:173 inputSchema: zodToJsonSchema(WriteFileArgsSchema),
    zod-to-json-schema is a direct dependency of the package at that version. The conversion happens before the SDK sees anything, so server/zod-json-schema-compat.js never runs. And the package declares @modelcontextprotocol/sdk: ^1.17.0, so a fresh install already pulls 1.30.0. A current SDK doesn't rescue it.

    What did fix it is the move to registerTool. Published 2025.11.25 and 2026.7.10 both use it, and neither imports zod-to-json-schema at all, which matches those versions emitting correct input schemas in my original table. main picks up the SDK fix for the same reason.

    So I'm fine with closing this. The bug is real but bounded: published versions ≤2025.8.21 only, no SDK upgrade clears it, and from a user's side the only fixes are moving to 2025.11.25 or later, or pinning zod to 3.25.76.

  3. banks0frome commented on Aug 27, 2026

    @banks0frome

    I built and independently verified a bounded proof for this issue.

    The independently accepted artifact contains this standalone checker for maintainer review:

    import fs from 'node:fs';
    const path = process.argv[2];
    if (!path) {
      console.error('schema-lock: usage: node check-mcp-schema.mjs <tools-list.json>');
      process.exit(2);
    }
    let payload;
    try { payload = JSON.parse(fs.readFileSync(path, 'utf8')); }
    catch (error) {
      console.error(`schema-lock: cannot read payload: ${error.message}`);
      process.exit(2);
    }
    if (payload?.jsonrpc !== '2.0' || !payload?.result || !Array.isArray(payload.result.tools)) {
      console.error('schema-lock: invalid tools/list JSON-RPC response; expected result.tools array');
      process.exit(2);
    }
    const tools = payload.result.tools;
    if (tools.length === 0) {
      console.error('schema-lock: no tools captured; verify the adapter and server fixture');
      process.exit(2);
    }
    const invalid = tools.filter(tool => {
      const s = tool?.inputSchema;
      return !s || typeof s !== 'object' || Array.isArray(s) ||
        s.type !== 'object' || !s.properties ||
        Object.keys(s).every(k => k === '$schema');
    });
    if (invalid.length) {
      console.error(`schema-lock: ${invalid.length}/${tools.length} invalid tool schemas`);
      process.exit(1);
    }
    console.log(`schema-lock: ${tools.length} valid tool schemas`);
    

    This inline artifact is the product test; it is not evidence of maintainer adoption.

    If this is useful, I’ll wait for maintainer direction on this issue before opening a PR.

  4. olaservo commented on Aug 27, 2026

    @olaservo
    Member

    Confirmed, and fixed from 2025.11.25 onward.

    Reproduced your report exactly against the published tarball. A fresh npm install @modelcontextprotocol/[email protected] resolves zod 4.4.3 and SDK 1.30.0, and tools/list returns 13 of 14 tools as bare {"$schema": "http://json-schema.org/draft-07/schema#"}. list_allowed_directories survives because its schema is a literal object in the source rather than a converted one. The control on 2026.7.10, same zod 4.4.3, returns full schemas for all 14.

    Version zodToJsonSchema call sites zod-to-json-schema dep Result under zod 4.4.3
    2025.8.21 14 ^3.23.5 direct 13/14 tools empty
    2025.11.25 0 declared, unused correct
    2026.7.10 0 removed correct

    @pauxier-coder is right that a current SDK does not rescue 2025.8.21. That version calls zodToJsonSchema itself at dist/index.js:173 and declares zod-to-json-schema as its own direct dependency, so the conversion completes before the SDK sees the schema and server/zod-json-schema-compat.js never runs. @ramiiZPD's run was against main, which uses registerTool and was never affected.

    Nothing left to patch: main has no zod-to-json-schema usage in src, and none of the currently published TypeScript servers declare it. Fixes are to move to 2025.11.25 or later, or pin zod to 3.25.76 if you must stay on the older release.

    Agreed the silent failure was the worst part of this. Thanks for the repro steps!

  5. dodogeny commented on Sep 1, 2026

    @dodogeny

    This is a brutal one to debug — “surfaces client-side as what looks like a permissions or sandbox problem” is exactly why it took someone weeks to trace back to the server. Nothing about the symptom points at the real cause.

    We build Apitella, which diffs a server’s tools/resources/prompts across releases — this is close to the canonical case for it: 13 of 14 schemas silently collapsing to {} would show up as a breaking-change alert the moment that release went out, not weeks later downstream. Mentioning it mainly because the mechanism here is such a clean illustration, not trying to sell you on anything — you clearly know this problem better than most.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions