anthropics / anthropics/claude-code
[BUG] Windows: Claude Code CLI corrupts stdio MCP password arguments when launching npx through cmd.exe
- Dominant language
- Python
- Stars
- 145k
- Forks
- 23.1k
- PR merge metrics
- PR metrics pending
Description
# [BUG] Windows: Claude Code CLI corrupts stdio MCP password arguments when launching `npx` through `cmd.exe`
### Preflight Checklist
- [x] I searched existing issues; this report is about argument corruption after launch, not the existing `npx` `ENOENT`/`EINVAL` startup failures.
- [x] This is a single bug report.
- [x] I am using the latest stable version of Claude Code CLI available when this report was filed.
### What's Wrong?
I use Claude Code CLI on native Windows with a local stdio MCP server to connect to a MySQL database. The MCP server is [Anarkh-Lee/universal-db-mcp](https://github.com/Anarkh-Lee/universal-db-mcp).
When the MCP configuration uses `"command": "npx"`, Claude Code launches `npx` through a Windows `cmd.exe` wrapper. If a value in the MCP `args` array contains `cmd.exe` metacharacters, especially `^` or `&`, the value can be reparsed and changed before it reaches the MCP server.
I configured this MCP server through `cc switch`. In this setup, Claude Code CLI automatically adds the `cmd.exe /d /s /c` layer when it launches `npx`. Codex CLI does not add this extra `cmd.exe` wrapper for the same MCP use case, and the same database password works there without additional caret escaping.
This affects database passwords in particular. A password that works when passed directly to the database or through Codex CLI does not work unchanged when the same MCP server is launched by Claude Code CLI through `npx`.
The problem is not limited to passwords. The same launch path can affect API tokens, connection strings, and any other literal argument containing characters such as `^`, `&`, `|`, `<`, `>`, or `%`.
### Environment
- Claude Code CLI: latest stable release available when this report was filed
- Operating system: Windows 11, native Windows installation, not WSL
- Node.js: 20.x and 22.x
- MCP server: `universal-db-mcp`
- Database: MySQL
- Transport: stdio
- Terminal: PowerShell / Windows Terminal
### Configuration
I configured the MCP server through `cc switch` with a configuration like this:
```json
{
"mcpServers": {
"my-database": {
"command": "npx",
"args": [
"universal-db-mcp",
"--type", "mysql",
"--host", "localhost",
"--port", "3306",
"--user", "root",
"--password", "P@ss^&w0rd",
"--database", "test"
]
}
}
}
```
### Steps to Reproduce
1. Configure `universal-db-mcp` through `cc switch` as a stdio MCP server using `command: "npx"`, and pass a password containing `^` and `&` in the `args` array.
2. Start Claude Code CLI on native Windows and load the MCP configuration.
3. Try to use the MCP server to connect to MySQL.
4. Observe that authentication fails even though the same database, username, and password work when tested independently.
5. Add an additional caret as a temporary escape, for example changing the configured value from `P@ss^&w0rd` to `P@ss^^&w0rd`, then restart Claude Code.
6. In the affected setup, the additional caret can make the connection work, but the required number of carets depends on the characters in the value and on how many parsing layers are involved. More complex passwords can require multiple levels of escaping and remain inconsistent.
The child process command line observed from a separate PowerShell session has the following form:
```text
cmd.exe /d /s /c "npx ^"universal-db-mcp^" ^"--type^" ^"mysql^" ... ^"--password^" ^"P@ss^^&w0rd^" ..."
```
The presence of the `cmd.exe /d /s /c` wrapper explains why the same literal `args` value is sensitive to `cmd.exe` escaping rules.
### Observed Comparison
The same MCP server and password behave differently depending on the client launch path:
| Launch path | Wrapper | Result |
| --- | --- | --- |
| Claude Code CLI, MCP configured through `cc switch` with `command: "npx"` | `cmd.exe /d /s /c` around `npx` | Password must be manually caret-escaped; complex values can still be inconsistent |
| Codex CLI with the same MCP server and password | No additional `cmd.exe` wrapper | Works without adding caret escapes |
| Claude Code CLI with `command: "node"` and an absolute path to `universal-db-mcp/dist/index.js` | No `npx.cmd`/`cmd.exe` shim path | Works without adding caret escapes because the wrapper is bypassed |
This comparison points to the Claude Code Windows MCP process-launch path rather than the database server or the `universal-db-mcp` database adapter.
### Expected Behavior
Each element of the MCP `args` array should reach the MCP subprocess as the same literal value configured by the user.
For example:
```text
Configured password: P@ss^&w0rd
Received password: P@ss^&w0rd
```
The user should not need to know how many `cmd.exe` or transport parsing layers are involved, and a database password should not need to be changed to compensate for the client launch implementation.
### Actual Behavior
Claude Code CLI launches the `npx` command through a `cmd.exe /d /s /c` wrapper by default in this setup. `cmd.exe` then interprets or reparses shell metacharacters in the serialized argument string. Codex CLI does not add this extra wrapper for the same MCP use case, so the same password reaches the MCP server without extra caret escaping.
As a result, the password reaching the MCP server is not reliably identical to the configured password. Depending on the exact combination of characters, a character can be consumed as an escape, remain as an extra caret, or cause the value to be split at a shell operator such as `&`.
Adding extra `^` characters can compensate for one parsing layer, but this is a fragile workaround. It becomes difficult to predict when Claude Code's own transport serialization and `cmd.exe` parsing both apply escaping.
### Suspected Cause
The observed process command line is consistent with the Windows stdio MCP launch path converting the `args` array into shell text for `cmd.exe`, then allowing `cmd.exe` to parse that text again.
The exact internal Claude Code call site is not known, but the externally visible behavior is clear: a literal MCP argument is altered only when it goes through the Claude Code `npx` launch path on Windows.
This is also consistent with Node.js documentation for `child_process.spawn` and `shell: true`: when an `args` array is passed with a shell enabled, the arguments are concatenated rather than being handled as ordinary argv elements. Node identifies this pattern in the `DEP0190` deprecation documentation.
### Suggested Fix
Please audit the Windows stdio MCP process-launch path for `npx` and other `.cmd`/`.bat` shims.
The implementation should preserve the boundaries and literal values of all MCP arguments. It should avoid passing raw serialized arguments through `cmd.exe`; if a shell is unavoidable for a Windows batch shim, it needs a tested Windows-specific argument serializer that covers at least:
- `^`, `&`, `|`, `<`, `>`, and `%`
- spaces and empty arguments
- embedded double quotes
- trailing backslashes
- combinations of multiple metacharacters
- repeated parsing layers
Please add a regression test that launches a stdio probe with values such as:
```text
simple
with space
a^b&c%d|e<>f
quote"inside
trailing\\
```
The test should inspect the actual arguments received by the child process, not only whether the MCP handshake succeeds.
Simply enabling `shell: true` without correctly escaping the arguments would not solve this issue; it would preserve the same class of corruption and may introduce the security risk described by Node's `DEP0190` warning.
### Workaround
A temporary workaround is to add extra caret escaping to the password value, but this depends on the number of parsing layers and is not reliable for arbitrary passwords.
A more reliable transport workaround is to bypass `npx` and launch the JavaScript entry point with the real `node.exe` executable:
```json
{
"mcpServers": {
"my-database": {
"command": "node",
"args": [
"C:\\path\\to\\node_modules\\universal-db-mcp\\dist\\index.js",
"--type", "mysql",
"--host", "localhost",
"--port", "3306",
"--user", "root",
"--password", "P@ss^&w0rd",
"--database", "test"
]
}
}
}
```
This bypasses the `npx.cmd`/`cmd.exe` layer and preserves the argument in the direct `node.exe` launch. The path is machine-specific, and putting a password in `args` can still expose it in the child process command line; this workaround addresses argument corruption, not credential storage or process-list visibility.
### Related Issues and References
- [#58510](https://github.com/anthropics/claude-code/issues/58510) covers Windows stdio MCP servers using bare `npx` that fail to start with `spawn ENOENT`. This report covers a different failure mode: the MCP process starts, but its argument value is corrupted.
- [Node.js DEP0190 documentation](https://nodejs.org/api/deprecations.html#dep0190-passing-args-to-nodechild_process-execfile-spawn-with-shell-option) documents the risk of passing an `args` array together with `shell: true` without argument escaping.
- [VS Code PR #314642](https://github.com/microsoft/vscode/pull/314642) provides relevant reference material for escaping arguments when launching MCP subprocesses through `cmd.exe`; it is not a Claude Code fix.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start by tracing the Windows stdio MCP process-launch path for npx and other .cmd/.bat shims, focusing on the child_process.spawn and shell parsing behavior described in the report. Add a regression test using a stdio probe with spaces, metacharacters, quotes, empty values, and trailing backslashes, then verify the child receives each configured argument literally.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- node.js, python, shell
- Domain
- cli, operating-systems, security, testing-qa
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 55/100