How to fix LangChain MultiServerMCPClient NotImplementedError on Windows

MultiServerMCPClient fails on Windows because the default Selector event loop has no subprocess transport. Set the Proactor policy to fix it. Telling four host contexts apart up front.

Two flow paths showing how the asyncio loop policy controls whether MultiServerMCPClient can launch MCP servers on Windows

TL;DR: If a LangChain MultiServerMCPClient example raises NotImplementedError on Windows the moment it tries to spawn an MCP server over stdio, set the asyncio Proactor policy before anything async runs:

import sys, asyncio
if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())

The default loop on the path that creates the subprocess is the Windows SelectorEventLoop, and it does not implement _make_subprocess_transport. The Proactor loop does. The sys.platform guard keeps the change portable so the same script still runs on macOS and Linux. Issue #25 tracks the bug.

That is the canonical fix. Most posts stop there, which is a problem because four different host setups produce the same traceback for slightly different reasons, and only one of them is actually fixed by adding those three lines at the top of the file.

Why NotImplementedError happens on Windows

Python ships two asyncio loops on Windows: SelectorEventLoop and ProactorEventLoop. Only the Proactor loop implements subprocess transports - the call asyncio.create_subprocess_exec(), which is what MultiServerMCPClient uses to launch an MCP server over stdio. When the running loop is the Selector one, you get the bare NotImplementedError from asyncio/base_events.py deep inside _make_subprocess_transport. The Python policy docs spell this out, but most stdio MCP example code does not check.

Since Python 3.8, a fresh interpreter on Windows defaults to the Proactor loop, so a plain .py script should work without setting any policy. When a plain script raises this anyway, something else upstream installed the Selector loop - which is exactly the case for Jupyter, the LangGraph dev server, and a few framework hosts.

Two horizontal flow paths comparing how MultiServerMCPClient runs on Windows. Top path: SelectorEventLoop calls asyncio.create_subprocess_exec which calls _make_subprocess_transport, marked NOT IMPLEMENTED in red, raising NotImplementedError before the MCP server starts. Bottom path: WindowsProactorEventLoop calls the same chain, marked IMPLEMENTED in green, the MCP server process spawns and stdio is wired up. Same Python code, same MCP client, only the loop policy differs.
Same MCP client. Same call. The Selector loop has no subprocess transport on Windows; the Proactor loop does. Source: Python asyncio policy docs and issue #25.

The four contexts that produce the same traceback

Hook issues in the langchain-mcp-adapters repo all read the same in headlines, but the workaround differs by host. Identify yours before pasting code.

HostWhy Selector loop is in playFix
Plain .py script with Python 3.11 / 3.12Should use Proactor by default; if it does not, you are on a stale build or a venv that ran a third-party shimAdd the policy line shown above; if it still fails, run python -c "import asyncio; print(asyncio.get_event_loop_policy())" and look for non-default classes
Jupyter notebook (classic or Lab)Jupyter installs _WindowsSelectorEventLoop in the kernel for tornado compatibility (jupyter/notebook #5916)Move the code to a .py file, or set the policy in the very first cell before any await runs and restart the kernel
LangGraph dev serverThe reload worker re-installs Selector when watching filesRun langgraph dev --no-reload (add --allow-blocking if needed). The policy line alone is not enough
Older mcp SDK (pre-PR #1044)The SDK skipped the platform check and assumed Proactor was already in placeUpgrade mcp to a recent release; modelcontextprotocol/python-sdk PR #1044 ships the upstream guard
Python 3.13 + recent mcp SDKResolved natively; subprocess support is available without policy hacks for typical MCP casesConfirm versions before adding workaround code; you may not need any

If your symptom is "the policy line had no effect," the most likely match is the LangGraph dev row. The reload worker is a separate process that does not inherit your top-level policy change. The --no-reload flag is what fixes it, not anything in user code.

How to verify the fix worked

Add a one-liner that prints the running loop class right before you instantiate the client:

import asyncio
print(type(asyncio.get_event_loop()).__name__)
# expected on Windows after the fix: ProactorEventLoop

If that prints SelectorEventLoop, the policy change has not taken effect in this process - check that the policy line runs before any other import that calls get_event_loop, and that you are not in a Jupyter kernel that already started one. If it prints ProactorEventLoop and you still get NotImplementedError, the issue is in a worker subprocess (LangGraph dev, multiprocessing pool); fix it there as well.

If the fix did not work

Three less obvious causes catch people:

  • You are calling asyncio.run() twice. The second call rebuilds the loop and on some Windows configs lands back on Selector. Wrap the entire MCP session in a single asyncio.run(main()).
  • Your MCP server command path has spaces. The traceback can mask the real error when the spawn step fails. Quote the path and re-run.
  • You are inside Docker Desktop on Windows. The container runs Linux, so the Windows policy line is a no-op there. The container itself may have a different problem - check our notes on the Windows + Docker localhost trap for analogous gotchas.

If you also use Claude Code's MCP servers on Windows, the same Proactor policy applies whenever a host outside of Claude itself launches MCP processes. Pair this fix with the right LangChain agent iteration limit error handling and the agent runs cleanly on Windows.

FAQ

I added the policy line and still get NotImplementedError. What is wrong?

You are most likely in a Jupyter kernel or a LangGraph dev reload worker. Both install the Selector loop in a different process or before your top-level code runs. Move to a .py file (Jupyter case) or add --no-reload (LangGraph case).

Why does the same code work on macOS but not Windows?

macOS and Linux use Unix-style subprocess transports that asyncio implements on every loop. The split between Selector and Proactor is Windows-only. The sys.platform == "win32" guard in the fix is what keeps the policy line a no-op on POSIX.

Does Python 3.13 fix this on its own?

Reports in issue #25 say that 3.13 paired with a recent mcp SDK no longer needs the workaround for the typical stdio path. If you are pinning older versions for other reasons, the policy line is still the right fix; do not rely on the runtime upgrade alone.

Will the policy change break my code on Linux or macOS?

Not with the sys.platform == "win32" guard. WindowsProactorEventLoopPolicy only exists in the Windows build of the standard library; importing or referencing it on POSIX raises AttributeError. The guard prevents the line from running there.

Can I avoid stdio transport entirely?

Yes. MultiServerMCPClient supports HTTP transport for MCP servers that expose an HTTP endpoint, which sidesteps the subprocess path completely. Cost: you have to run the MCP server yourself rather than letting the client spawn it. For local development, the policy fix is usually less hassle than standing up a separate process.