LearnNewsExamplesServices
Frontmatter
id16231
titlefix(ai): file-system MCP server tools missing x-pass-as-object -> object-handler arg-shape mismatch
stateClosed
labels
bugtesting
assigneesnovice-22
createdAtJul 31, 2026, 7:01 PM
updatedAtJul 31, 2026, 8:45 PM
githubUrlhttps://github.com/neomjs/neo/issues/16231
authornovice-22
commentsCount3
parentIssuenull
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[]
blocking[]
closedAtJul 31, 2026, 8:45 PM

fix(ai): file-system MCP server tools missing x-pass-as-object -> object-handler arg-shape mismatch

novice-22
novice-22 commented on Jul 31, 2026, 7:01 PM

Problem

Every argument-taking tool of the file-system MCP server fails at call time. Only healthcheck (no arguments) succeeds.

Verified against the published npm packages neo.mjs@13.0.0 and neo.mjs@13.1.0, by starting ai/mcp/server/file-system/mcp-server.mjs and connecting a real MCP client over stdio (@modelcontextprotocol/sdk):

tools/list -> healthcheck, read_file, write_file, list_directory, check_syntax, run_playwright_test

healthcheck          isError=false  {"status":"healthy"}
read_file            isError=true   Error executing read_file: The "paths[0]" argument must be of type string. Received undefined
list_directory       isError=true   (same)
write_file           isError=true   (same)
check_syntax         isError=true   (same)
run_playwright_test  isError=true   (same)

Stack trace from the server process:

BaseServer.mjs:341            dispatch
  ToolService.mjs:74          callTool           -> tool.handler(...handlerArgs)
    FileSystemService.mjs:54  checkSyntax
      FileSystemService.mjs:16 ensureSandboxed   -> TypeError (ERR_INVALID_ARG_TYPE)

Root cause

Same defect class as #8332, #9545 and #14542.

ToolService dispatch branches on x-pass-as-object (ai/mcp/ToolService.mjs):

if (tool.passAsObject) {
    return tool.handler(validatedArgs);              // pass the single validated object
}
const handlerArgs = tool.argNames.map(name => validatedArgs[name]);
return tool.handler(...handlerArgs);                 // else: spread positionally

All FileSystemService handlers destructure a single object:

static async readFile({absolutePath})            { ... }
static async writeFile({absolutePath, content})  { ... }
static async listDirectory({absolutePath})       { ... }
static async checkSyntax({absolutePath})         { ... }
static async runPlaywrightTest({absolutePath})   { ... }

…but ai/mcp/server/file-system/openapi.yaml declares x-pass-as-object on none of its operations, so the positional branch is taken and the handlers receive a bare string. Destructuring it yields absolutePath === undefined, and path.resolve(undefined) throws.

Declaration counts per server (13.0.0):

server operations x-pass-as-object
github-workflow 23 16
gitlab-workflow 13 12
knowledge-base 9 8
memory-core 29 28
neural-link 37 32
file-system 6 0

Suggested fix

Add x-pass-as-object: true to the five argument-taking operations in ai/mcp/server/file-system/openapi.yaml:

  /read_file:
    post:
      operationId: read_file
      x-pass-as-object: true

(same for write_file, list_directory, check_syntax, run_playwright_test)

Locally patching just check_syntax this way makes the tool return Syntax OK through the same MCP client, so the one-line annotation is sufficient.

Why it went unnoticed

No test exercises these tools through the real ToolService dispatch. FileSystemPolicy.spec.mjs substitutes callTool with a mock, and the FileSystemService unit specs call the service methods directly with an object. A dispatch-level test for one file-system tool would have caught this, and would guard the other servers too.

Related: PR #10873 review already flagged that x-pass-as-object lacks a discoverable definition in the docs surface, which is likely why the annotation keeps getting missed.

Environment

  • neo.mjs 13.0.0 and 13.1.0 (npm)
  • Node.js 22
  • @modelcontextprotocol/sdk 1.27.x
tobiu closed this issue on Jul 31, 2026, 8:45 PM