An absolute path to your MCP server does not make relative paths inside that server resolve beside its source file. In our local fixture, initialization and tool discovery succeeded, but the tool read a different file with the same name. Changing the child working directory fixed that fixture; resolving a packaged resource relative to the module also worked.
This guide is for developers who control a Node.js MCP client or server. It tests StdioClientTransport in SDK 1.27.1 on macOS, using Node 25.8.0. It does not establish what Claude Code, Claude Desktop, or a third-party host does with a configuration field named cwd.
A reported problem, with two separate causes
SDK issue #251 describes a server launch that looked for a package in the wrong directory. The maintainer’s October 5, 2026 response separates a command string containing an argument from the working directory used by the child. The issue is closed. It is evidence of a concrete developer problem, not proof of an unfixed defect in every current host.
Our test deliberately avoids npm and any real project data. It asks a narrower question: after a server starts, which file does a tool actually read? That is the check missing from a successful connection indicator.
Results: connection success can hide the wrong data
We placed fixture.txt beside the server and a different file with the same name in a second directory. Both contain public dummy strings. We launched the client from the second directory.
| Test | MCP initialized? | Relative data read | Module-relative data read |
|---|---|---|---|
Absolute server path; child cwd omitted | Yes | Wrong dummy file | Expected dummy file |
| Absolute server path; explicit fixture directory | Yes | Expected dummy file | Expected dummy file |
| Relative server path; explicit fixture directory | Yes | Expected dummy file | Expected dummy file |
Relative server path; child cwd omitted | No | Not reached | Not reached |
Absolute server path; nonexistent cwd | No | Not reached | Not reached |
Executable and argument combined in command | No | Not reached | Not reached |
All six expected outcomes passed. Successful cases also completed tools/list and tools/call. The failing cases establish that initialization failed; their error messages should not be treated as portable exact strings across operating systems or SDK versions.
The practical result is more specific than “always use absolute paths”: you must decide separately where the executable is, where the entry-point script is, and which directory your application data belongs to.
Reproduce with disposable files
The fixture server and test runner use ordinary SDK package imports. Save both in a new disposable directory. The runner creates fixture.txt and other/fixture.txt there, overwriting files with those names, so do not put it in an existing project directory. It writes ../results.json beside that directory; choose a disposable parent too.
Use Node and SDK 1.27.1. If you choose to install dependencies in that disposable directory, pin the version:
npm install --save-exact @modelcontextprotocol/[email protected]
node test.mjs
We used an already-installed SDK through a local dependency symlink; we did not perform a clean installation. No application credentials, remote MCP server, paid service, or production files were involved. A fresh install and other operating systems remain untested. The expected console summary is {"passed":6}; the result file records the separate outcomes.
The key client setting is:
const transport = new StdioClientTransport({
command: process.execPath,
args: [absoluteServerPath],
cwd: explicitDataDirectory,
stderr: 'pipe'
});
Here absoluteServerPath is the full path to the entry-point file and explicitDataDirectory is the existing directory your application intentionally uses. These are placeholders, not host-independent .mcp.json fields. command is the executable; the script path belongs in args.
For a resource deliberately distributed beside an ESM module, the server can instead use:
const resource = new URL('./fixture.txt', import.meta.url);
const text = await readFile(resource, 'utf8');
That is appropriate for this packaged fixture. It is not a general rule for user-selected documents or writable application storage, which may belong elsewhere. In those cases, define and validate the intended data root explicitly.
Diagnose in this order
- No child launch: verify that
commandnames an executable and that arguments are separate. A nonexistent working directory can also prevent launch. A spawn error alone does not identify which path is wrong. - Child starts but initialization fails: inspect the child exit and stderr. A relative entry-point path may resolve against an unexpected directory. Do not “fix” this by broadening file access.
- Initialization succeeds: call one harmless tool that reads an unmistakable dummy fixture. Tool discovery alone cannot prove correct data selection.
- Wrong fixture returned: compare the intended data root with the child working directory. An absolute entry-point path solves only entry-point lookup.
- Expected fixture returned: repeat from a different parent directory. Otherwise, your test may pass only because your terminal happens to be in the right place.
Use dummy paths when sharing logs. Working directories and error messages can expose local names; do not dump credentials or real document contents to diagnose this problem. Keep diagnostic logging off protocol stdout. Our separate stdout-noise guide covers that failure layer, and the environment inheritance test covers missing settings after a successful connection.
What this test does not prove
A working directory is not an access-control boundary. A successful read does not prove appropriate authorization, and changing cwd does not grant filesystem permission. MCP roots, host permissions, symlink handling and user-selected path validation were not tested here.
The version-pinned SDK implementation exposes the subprocess working-directory option. The Node subprocess documentation explains command, arguments and working-directory behavior. This article adds the executable wrong-file control and recovery cases, rather than replacing those references.
There is no reason to buy a secrets manager or another tool merely to repair this path problem. Start with the local fixture. The useful outcome is a server that reads the intended data from more than one launch location—not a new subscription or a green connection badge alone.