--- title: "Why Your Windows Desktop MCP Calls Silently Fail: Taming the 10-Second Idle Gate" description: "Why actions in windows-mcp return CONTROL_PREEMPTED or fail silently, and the exact flag needed to disable the desktop control gate." pubDate: 2026-03-12 updatedDate: 2026-10-25 category: notes tags: ["windows", "mcp", "debugging", "ai-agents", "troubleshooting"] ogImage: /og/why-your-windows-mcp-calls-silently-fail.jpg banner: /banners/why-your-windows-mcp-calls-silently-fail.jpg draft: false --- You install `windows-mcp`, register it in your agent config, and ask your model to trigger a native Windows notification or click a button in an open desktop application. The tool call finishes in under 200 milliseconds. The agent happily reports: *"I have sent the notification."* **Nothing appeared on screen.** When you inspect the raw JSON response returned by the MCP protocol, you don't find a standard Python exception or Win32 error. Instead, you see this: ```json { "code": "CONTROL_PREEMPTED", "status": { "state": "user", "generation": 3, "wait_seconds": 9.8639, "executed_steps": 0 } } ``` Or, in slightly different timing conditions, your stdio process dumps a stack trace: ```text RuntimeError: AI control indicator is stopping AI control indicator failed; releasing physical input ``` Here is the root cause of this failure mode and the exact configuration setting that fixes it. --- ## The Culprit: The Desktop Control Gate `windows-mcp` is designed around a concept called the **Desktop Control Policy** (implemented in `src/windows_mcp/desktop/control_overlay.py` and `tools/control_policy.py`). The developers wanted to prevent an AI agent from fighting the human user for physical mouse and keyboard ownership. To achieve this, the server installs an idle detection watchdog: 1. Before executing any state-changing input tool (`Click`, `Type`, `Move`, `Shortcut`, `Notification`, etc.), it checks Windows user input timers (`GetLastInputInfo`). 2. If the user moved the physical mouse or pressed a keyboard key within the last **10 seconds** (`WINDOWS_MCP_CONTROL_IDLE_TIMEOUT`), the server considers the human to be actively using the machine. 3. Instead of waiting for you to stop typing, it **aborts immediately**, returning `CONTROL_PREEMPTED` and leaving the action unexecuted. If you are chatting with your agent in a desktop window or typing commands in a terminal, **you just touched the keyboard**. Your idle timer is zero. Therefore, almost every single immediate tool call the agent issues will be preempted by default. --- ## Why `--no-control` Fails If you inspect the repository's README or CLI documentation, you might see references to `--control / --no-control`. Naturally, you might try adding `--no-control` to your server startup command: ```bash uvx windows-mcp serve --no-control ``` The server instantly crashes on startup with: ```text Usage: windows-mcp serve [OPTIONS] Error: No such option '--no-control'. ``` The CLI parser for the `serve` subcommand only exposes transport, network, and tool exclusion flags (`--transport`, `--host`, `--port`, `--exclude-tools`). It does not bind the control flag to the CLI argument group for `serve`. --- ## The Real Fix: WINDOWS_MCP_CONTROL=false The configuration loader (`src/windows_mcp/config.py`) reads desktop control settings directly from environment variables. To completely bypass the 10-second idle lockout and allow the agent to execute actions immediately upon request, set: ```bash WINDOWS_MCP_CONTROL=false ``` When injected into your MCP client's environment block: ```json { "command": "uvx", "args": ["windows-mcp", "serve", "--exclude-tools", "PowerShell,Registry"], "env": { "WINDOWS_MCP_CONTROL": "false" } } ``` The control overlay, idle watchdog, and preemptive preemption logic are completely disabled. --- ## Verification To confirm the fix, send a toast notification through the raw MCP client while actively moving your mouse: ```python import asyncio from mcp.client.stdio import stdio_client, StdioServerParameters from mcp.client.session import ClientSession async def main(): params = StdioServerParameters( command="uvx", args=["windows-mcp", "serve", "--exclude-tools", "PowerShell,Registry"], env={"WINDOWS_MCP_CONTROL": "false", "ANONYMIZED_TELEMETRY": "false"} ) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() res = await session.call_tool("Notification", arguments={ "title": "Windows-MCP Ready", "message": "Immediate execution verified.", "app_id": "Hermes Agent" }) for c in res.content: print(c.text) asyncio.run(main()) ``` Output: ```text Notification sent: "Windows-MCP Ready" - Immediate execution verified. ``` No 10-second wait, no `CONTROL_PREEMPTED`, and no silent drops.