Skip to content

pywin32 installation fails on Windows — hard dep only needed for client stdio, but pulled in for all users via eager import in __init__.py #2233

Description

@sergeykad

Initial Checks

Description

pywin32>=311 is a required Windows dependency used exclusively in mcp.client.stdio (Windows Job Objects for child process cleanup). However, mcp/__init__.py eagerly imports from .client.stdio import StdioServerParameters, stdio_client, which causes mcp/os/win32/utilities.py to unconditionally execute:

if sys.platform == "win32":
    import pywintypes
    import win32api
    import win32con
    import win32job

This means pywin32 is a hard runtime requirement on Windows for all users — including those running a pure MCP server that never uses mcp.client.stdio.

The pywin32 wheel contains a .data directory with Windows DLLs. On Windows, AV software (e.g. Windows Defender) locks newly-extracted DLLs before uv can clean up its temp directory, causing installation to fail every time with:

error: Failed to install: pywin32-311-cp313-cp313-win_amd64.whl (pywin32==311)
  Caused by: failed to remove directory `...\uv\cache\builds-v0\.tmp\Lib\site-packages\pywin32-311.data`
  os error 32 (file in use by another process)

This blocks server-only deployments like ha-mcp entirely on Windows (tracked in homeassistant-ai/ha-mcp#672).

The null-checks already in _create_job_object() and _maybe_assign_process_to_job() handle win32job = None gracefully — so the only missing piece is allowing the import to fail softly:

if sys.platform == "win32":
    try:
        import pywintypes, win32api, win32con, win32job
    except ImportError:
        win32api = win32con = win32job = pywintypes = None

Or alternatively, lazily import mcp.client.stdio from mcp/__init__.py so server-only users don't trigger this code path at all.

Example Code

# This alone triggers the pywin32 import on Windows:
from mcp.server.stdio import stdio_server

Python & MCP Python SDK

Python 3.13, MCP Python SDK 1.26.0 (latest)

Activity

  1. herakles-dev commented on Mar 7, 2026

    @herakles-dev

    I'd like to take this. The fix is straightforward — the pywin32 imports in src/mcp/os/win32/utilities.py (lines 19-23) should be wrapped in a try/except ImportError so they degrade gracefully when pywin32 isn't installed:

    if sys.platform == "win32":
        try:
            import pywintypes
            import win32api
            import win32con
            import win32job
        except ImportError:
            pywintypes = None
            win32api = None
            win32con = None
            win32job = None

    The downstream code (_create_job_object, _maybe_assign_process_to_job, terminate_windows_process_tree) already null-checks these modules before use, so the graceful degradation path is fully covered.

    Will have a PR up shortly.

  2. added 2 commits that reference this issue on Mar 13, 2026
    3b6af99
    8a9c0d8
  3. added
    enhancementRequest for a new feature that's not currently supported
    P2Moderate issues affecting some users, edge cases, potentially valuable feature
    needs decisionIssue is actionable, needs maintainer decision on whether to implement
    on Aug 14, 2026
  4. J-Pster commented on Sep 9, 2026

    @J-Pster

    Independent confirmation of the same root cause, from a different angle: this also completely blocks running under free-threaded CPython (3.14t) on Windows, not just installation flakiness under AV lock contention.

    pywin32 has no cp314t wheel in any published release (checked against the full version history on PyPI, up to 312 as of this writing). Since mcp/__init__.py eagerly imports mcp.client.stdio → mcp.os.win32.utilities, which does import pywintypes etc. unconditionally at module level on sys.platform == "win32", import mcp itself fails on a free-threaded interpreter on Windows before any application code runs — there's no way to work around it short of stubbing the four modules locally (which is obviously not viable for a real deployment, since the actual Job Object functionality would then silently no-op).

    I hit this while experimenting with a free-threaded daemon architecture for an MCP server (to share memory across sessions without the GIL becoming the new serialization point). The fix @herakles-dev described above (soft-failing the import) would resolve this for us too, same as the AV case — the root cause is identical: pywin32 support should degrade gracefully rather than being a hard requirement for code paths (mcp.client.stdio's subprocess Job Object management) that a server-only, or otherwise stdio-client-free, deployment never touches.

    Given there's apparently no timeline for pywin32 to ever ship a free-threaded wheel (it hasn't kept pace with recent CPython ABI tags in general), this is likely to stay a hard blocker for the free-threaded + Windows combination indefinitely unless this import is made optional. Happy to test against a PR if one lands.

  5. dgrunwald-qt commented on Sep 17, 2026

    @dgrunwald-qt

    pywin32 is also the absolute kitchen sink of Windows stuff -- it ships Scintilla (text editor), OpenGL examples, a debugger.
    It's the embodiment of dependency hell: it requires DLLs (e.g. MFC) that do not ship with Windows, with Python, or with pywin32 -- the user just has to have to them installed separately somehow!
    It also requires DLLs that are not available on Windows Server Core.
    Please drop the dependency on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P2Moderate issues affecting some users, edge cases, potentially valuable featureenhancementRequest for a new feature that's not currently supportedneeds decisionIssue is actionable, needs maintainer decision on whether to implement

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions