modelcontextprotocol / modelcontextprotocol/csharp-sdk
StdioClientTransportOptions is failing with space in Command
@jozkee is already working on this.
Since Jul 19, 2026.
- Dominant language
- C#
- Stars
- 4.5k
- Forks
- 814
- Avg merge
- 9d 19h
- Merged PRs (30d)
- 4
Description
Describe the bug
On Windows, StdioClientTransport wraps every non-cmd.exe command as cmd.exe /c <Command> <Arguments...>. When Command is an absolute path that contains a space (e.g. anything under C:\Program Files\...) and Arguments is non-empty, the resulting command line confuses cmd.exe's /c quote-handling rules: cmd strips the outer quotes around the executable path, splits on the first space, and tries to launch a non-existent program. The MCP server is never started.
The relevant code is in [src/ModelContextProtocol.Core/Client/StdioClientTransport.cs](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.Core/Client/StdioClientTransport.cs):
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) &&
!string.Equals(Path.GetFileName(command), "cmd.exe", StringComparison.OrdinalIgnoreCase))
{
arguments = arguments is null or [] ? ["/c", command] : ["/c", command, ..arguments];
command = "cmd.exe";
}
Per cmd /?, when /c is followed by more than two quote characters, cmd strips the first and last quote and re-parses the middle. Because ProcessStartInfo.ArgumentList correctly quotes both the space-containing path and any further argument, you always end up with 4+ quote characters and trigger this stripping behavior. The /s switch plus an extra outer pair of quotes around the whole command-and-args region would prevent this, but the SDK does not emit either.
To Reproduce
Steps to reproduce the behavior:
- Place any working stdio MCP server executable at a path containing a space, e.g.
C:\Program Files\MssqlMcp\server.exe. - Create a client with at least one argument:
var options = new StdioClientTransportOptions { Name = "MssqlMcp", Command = @"C:\Program Files\MssqlMcp\server.exe", Arguments = [connectionString] }; var transport = new StdioClientTransport(options); var client = await McpClient.CreateAsync(transport); - Observe that
CreateAsyncfails — the child process either exits immediately or never starts. - Move/copy the same executable to a space-free path (e.g.
C:\Tools\MssqlMcp\server.exe), updateCommand, and the exact same code works.
Expected behavior
StdioClientTransport should launch the configured executable regardless of whether its path contains spaces, just as it does for paths without spaces. The Windows cmd /c wrapping should be transparent to the caller.
Logs
Trace-level log from StdioClientTransport shows the wrapped invocation; the spawned cmd.exe produces output like:
'C:\Program' is not recognized as an internal or external command,
operable program or batch file.
(Exact message varies depending on whether the path's first space-separated token happens to match an existing file.)
Additional context
- Platform: Windows (any version). Not reproducible on Linux/macOS — the wrapping branch is Windows-only.
- Reproduces whenever
Path.GetFileName(Command) != "cmd.exe"and the path contains a space andArgumentsis non-empty. With zero arguments, cmd's "exactly two quotes around an executable" special case happens to make it work, which is why this bug is intermittent and easy to miss in samples (npx,dotnet,uvicornall live on space-free PATH entries).
Workarounds users currently have to apply:
- Install/copy the server to a space-free directory.
A proper fix would be for the SDK to either (a) build the joined-and-quoted command string itself when wrapping with cmd (e.g. ["/d", "/s", "/c", $"\"\"{command}\" {joinedArgs}\""]) rather than relying on ArgumentList's per-arg quoting, or (b) skip the cmd wrapping entirely when Command resolves to a real executable file — the original justification in the source comment ("usually npx or uvicorn") only applies to PATH-resolved batch/script commands, not absolute .exe paths.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.