Skip to content

MCP tools silently unavailable in headless --prompt mode after progressive-MCP rollout #4163

Description

@tanzhenxin

What happened?

Running qwen --prompt "..." against an MCP server configured in ~/.qwen/settings.json no longer gives the model access to that server's tools. The CLI starts, the server connects, but the model answers the prompt as if no MCP tools were available — it never calls them.

The bundled integration test makes this easy to reproduce. With an addition-server MCP exposing one add(a, b) tool, the test asks the model add 5 and 10, use tool if you can. On main today the model answers 15 directly without invoking mcp__addition-server__add. The test has been failing all three retries on the scheduled Release workflow since 2026-05-14:

  • 2026-05-13 scheduled release at 14512080egreen
  • 2026-05-14 scheduled release at d343e2c15 — red, only the integration-test jobs failing
  • 2026-05-15 scheduled release at cc800d013 — red, same failure

Both the No-Sandbox and Docker integration-test jobs fail identically.

The failure is not a flake — it reproduces deterministically locally on my machine in 167s (all retries exhausted) with the same Expected to find an add tool call assertion error.

What did you expect to happen?

Headless --prompt runs should see the same MCP tool surface as interactive sessions. If I configure an MCP server in settings, the model should be able to call its tools on the first prompt, exactly as it could before the progressive-MCP availability change.

Steps to reproduce

  1. Check out main.
  2. npm install && npm run build && npm run bundle
  3. cd integration-tests && QWEN_SANDBOX=false npx vitest run cli/simple-mcp-server.test.ts
  4. Observe: the test fails with Expected to find an add tool call: expected false to be truthy on all three retries.
  5. Re-run with the documented rollback flag: QWEN_CODE_LEGACY_MCP_BLOCKING=1 QWEN_SANDBOX=false npx vitest run cli/simple-mcp-server.test.ts
  6. Observe: the test passes on the first try in ~27 seconds.

Where this regressed

#3994 — feat(perf): progressive MCP availability — MCP no longer blocks first input (commit d343e2c15) — the only commit in the 2026-05-13 → 2026-05-14 bisect window that touches MCP discovery or tool registration.

The PR's intent is sound: the --prompt path in packages/cli/src/gemini.tsx does explicitly await config.waitForMcpReady() before sending the first request, exactly as the PR description requires. And startMcpDiscoveryInBackground does call await this.geminiClient?.setTools() after discovery completes, with a comment noting this is the only path that updates chat.tools for non-interactive runs.

But the wiring isn't actually flowing through to the model — even after waitForMcpReady() resolves, the next sendMessage is dispatched without the MCP tool in scope. So either setTools() is racing the chat's first turn, or the tool list it pushes isn't being picked up by GeminiChat, or something is stripping MCP tools from the request after setTools() runs.

The legacy synchronous path (gated by QWEN_CODE_LEGACY_MCP_BLOCKING=1) has none of this and the model picks up the tool reliably — which is the evidence that this is a wiring issue in the new path, not a model-behavior shift.

Why this matters

Anyone running qwen non-interactively with MCP servers (CI pipelines, scripts, batch jobs, ACP integrations using the single-prompt path) is silently running with built-in tools only. There is no error message; the model just produces an answer without invoking the configured server. For workflows that exist specifically to call out to an MCP server, the result is silently wrong rather than failing loud.

The QWEN_CODE_LEGACY_MCP_BLOCKING=1 flag is a viable short-term escape hatch, but it's an internal rollback meant for diagnosis, not a long-term answer — and it costs the startup-latency win the PR set out to deliver.

Suggested next step

Investigate why the post-discovery setTools() in Config.startMcpDiscoveryInBackground (packages/core/src/config/config.ts:1406-1431) is not making the MCP tools visible to the first non-interactive model request, even though await config.waitForMcpReady() resolves before that request is dispatched. The interactive path uses a different mechanism (the mcp-client-update subscriber in AppContainer) and is presumably unaffected.

Workaround

Set QWEN_CODE_LEGACY_MCP_BLOCKING=1 in the environment for non-interactive runs. The release workflow should set this on the integration-test jobs to unblock CI while the root cause is fixed.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions