Repository navigation
McpToolset hangs silently for ~5 minutes per retry (3 retries) when stdio command is an npm shim ("npx") on Windows #1686
Description
Description
The examples/mcpfilesystem sample — and any McpToolset built with
command("npx") — appears to hang indefinitely on Windows. No error is
printed and no node.exe process is ever spawned; the caller just blocks
inside McpToolset.getTools().
After digging into it, the "hang" is actually a very slow failure loop:
each initialization attempt waits out the full default
initializationTimeout (5 minutes), and the sync retry logic re-runs the
same doomed initialization, so the program stays silent for a measured
900 seconds (~15 minutes: 3 attempts x 5-minute timeout) before a
McpToolLoadingException (wrapped
Client failed to initialize by explicit API call) finally surfaces —
and even that is invisible unless an SLF4J provider is on the classpath,
which the samples don't include by default.
Environment
- OS: Windows 11 Home (10.0.26200)
- JDK: Oracle 21.0.5
- google-adk: 1.13.0
- Maven: 3.9.8
- Node.js: 24.15.0 (npx 11.12.1, standard npm-for-Windows install with
npx.cmdshim)
Root cause
On Windows, npm installs command shims as batch scripts (npx.cmd), not
executables. Java's ProcessBuilder cannot execute a bare npx command
(it is not a PE executable and is not resolved through PATHEXT), so the
stdio subprocess never starts.
A plain new ProcessBuilder("npx", "-v").start() on the same machine
fails immediately with CreateProcess error=2, which confirms the
JVM/OS-level cause. Inside ADK, however, the failure is masked by:
McpSessionManager.initializeSession(...)defaults the
initializationTimeoutto 5 minutes
(core/src/main/java/com/google/adk/tools/mcp/McpSessionManager.java,
theDuration.ofMinutes(5)fallback), soclient.initialize()waits
for a response from a process that never started.- The sync
McpToolset.getTools()retry logic (MAX_RETRIES = 3,
100 ms delay) then re-runs the same doomed initialization. - Without an SLF4J provider (the default for the samples), even the
retry ERROR logs are swallowed, leaving zero output on the console.
Observed log once an SLF4J provider is added (full run, timestamps from
the measured repro):
ERROR com.google.adk.tools.mcp.McpToolset - Unexpected error during tool loading, retry attempt 1
java.lang.RuntimeException: Client failed to initialize by explicit API call
at io.modelcontextprotocol.client.LifecycleInitializer...
ERROR ... retry attempt 2
ERROR ... retry attempt 3
ERROR com.google.adk.tools.mcp.McpToolset - Failed to load tools after multiple retries due to unexpected error.
com.google.adk.tools.mcp.McpToolsetException$McpToolLoadingException
(total elapsed: 900,857 ms)
Minimal reproduction (public API only)
StdioServerParameters params = StdioServerParameters.builder()
.command("npx") // hangs; "npx.cmd" works
.args(ImmutableList.of(
"-y", "@modelcontextprotocol/server-filesystem", "<some-dir>"))
.build();
McpToolset toolset = new McpToolset(params.toServerParameters());
List<BaseTool> tools = toolset.getTools(null).toList().blockingGet();
// blocks: ~5 min per attempt, up to 3 retries, then throwsObserved behavior
| command | result |
|---|---|
npx |
silent block, measured 900 s (~15 min) total, then McpToolLoadingException; no node.exe ever spawned |
npx.cmd |
OK in ~3.5 s, 14 tools loaded |
Workaround
Use the .cmd extension explicitly on Windows:
.command("npx.cmd")Suggestions
- Add a Windows note to
examples/mcpfilesystem/README.md(lowest
effort, immediate relief for new users). - Optionally, fail fast with a clear error when the configured command
cannot be executed at all (e.g. probe withProcessBuilder.start()
before the 5-minute timeout), or auto-map well-known npm shims
(npx/npm/node) to their.cmdvariants on Windows.
Happy to send a PR for either suggestion. Thanks!
Activity
pinqionghongbai commentedon Oct 22, 2026
Follow-up with two clarifications and one piece of cross-ecosystem
evidence after further digging:
1. Precise retry semantics. The title says "3 retries", but to be
exact:
MAX_RETRIES = 3inMcpToolsetcorresponds to 3 totalattempts (1 initial + 2 retries), because the sync retry logic zips
errors with
Flowable.range(1, MAX_RETRIES). The measured 900,857 msmatches exactly 3 × 300 s.
2. Repro completeness. Full runnable version is attached as
ReproToolset.java(compiles against `com.g<some-dir>with any existing absolute path). This isWindows-only — on Linux/macOS npm installs
npxas a real shebangscript, so
command("npx")works there unchanged.3. Alignment with adk-python (per CONTRIBUTING). adk-python doesn't
hit this because the Python MCP SDK handle
The Java MCP SDK (
io.modelcontextprotocol.sdk2.0.0,StdioClientTransport) has no equivalent, so adk-java inherits the gap.This suggests suggestion 2 (auto-mapping shims) could live in
DefaultMcpTransportBuilderfor adk-java, and/or upstream in the JavaMCP SDK for ecosystem-wide parity with the Python SDK. Happy to pursue
either path (CLA-ready).
hemasekhar-p commentedon Oct 23, 2026
Hi @pinqionghongbai, thank you for reporting this issue. I have been able to reproduce the behavior with your inputs, We truly appreciate and welcomes your contribution. Would you be able to provide a pull request to address this issue in a robust way?
fix: resolve Windows command shims for stdio MCP servers
fix: resolve Windows command shims for stdio MCP servers
fix: resolve Windows command shims for stdio MCP servers
pinqionghongbai commentedon Oct 23, 2026
@hemasekhar-p Yes — opened #1691: #1691
Before
ServerParametersreachesStdioClientTransport, the command isresolved against
PATH/PATHEXTthe wayshutil.whichdoes — the same lookup the PythonMCP SDK's
get_windows_executable_commandalready performs. A barecommand("npx")now reachesProcessBuilderasnpx.cmd.**Measured on the same machine, same repro,
McpToolLoadingException,node.exenever startedCovered by 14 resolver tests and 4 builder tests; full
coresuite is 2609 tests, 0 failures.One open question. This resolution arguably belongs in the upstream Java MCP SDK's
StdioClientTransportrather than in ADK — ython SDK does it. Iplaced it in
DefaultMcpTransportBuilderso ADK users get the fix without waiting on an upstreamrelease, but the class can be lifted upstream as-is if you'd prefer that. Happy to rework either
way.
Thanks again for the quick turnaround on triage.
hemasekhar-p commentedon Oct 23, 2026
@pinqionghongbai, Thanks for quick fix, Our team is currently reviewing both the issue and your pull request and we will reach out if we need any further details. Thank you.