fix(mcp): run OSV malware check off event loop#29190
Open
qdaszx wants to merge 2 commits into
Open
Conversation
Move the blocking OSV malware lookup for stdio MCP servers into asyncio.to_thread so MCP discovery startup is not frozen by intermittent OSV SSL handshakes. Add a regression test that verifies the event loop remains responsive while the malware check is slow. Closes NousResearch#29184
Fail open if the threaded OSV malware check itself does not return within the startup guard, covering lower-level SSL/connect stalls without mutating global socket defaults. Add a regression test for the bounded timeout path.
Author
|
Follow-up after reading #29192: thanks @ygd58 for calling out the second layer here. The original version of this PR only moved the blocking OSV lookup off the MCP event loop with I incorporated that concern here in commit
Validation after the follow-up:
Appreciate the reference PR — it made this fix more complete. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
_run_stdio()is an async MCP startup path, but it was calling the OSV malware preflight synchronously. That preflight eventually reachesurllib.request.urlopen(), so an intermittent OSV SSL/connect hang can block the entire MCP event loop during server discovery. In TUI startup this can exceed the 15s gateway readiness window even though MCP discovery itself is waiting on a 120s future.This PR now covers both layers:
asyncio.to_thread()so MCP discovery keeps the event loop responsiveThis preserves the existing pre-spawn malware check and error handling while avoiding a global
socket.setdefaulttimeout()mutation.The bug
tools/mcp_tool.py::_run_stdio()runs inside the MCP asyncio loop:check_package_for_malware()calls intotools/osv_check.py, which uses synchronousurllibI/O. When the OSV request stalls in SSL handshake or network setup, the MCP event loop cannot schedule other work. That makes MCP startup appear hung and can cause the TUI gateway startup timeout described in #29184.Even after moving the work into a thread, a lower-level OSV stall can still leave startup waiting on the thread result. Because the OSV malware check is already fail-open for network errors, the bounded async wrapper should also fail open on timeout.
The fix
asyncio.to_thread(check_package_for_malware, command, args)so blocking urllib work happens off the event-loop thread.asyncio.wait_for(..., timeout=_OSV_MALWARE_CHECK_TIMEOUT).malware_error = None, matching the existing fail-open policy for OSV/network failures._run_stdio()still raisesValueError("MCP server '<name>': ...")before spawning.Regression coverage
Added coverage in
tests/tools/test_mcp_tool_issue_948.py:test_run_stdio_malware_check_does_not_block_event_loopawait asyncio.sleep(0.05)resumes promptly instead of being delayed by the malware checkelapsed ~= 0.20s; after the fix it passestest_run_stdio_malware_check_timeout_fails_open_OSV_MALWARE_CHECK_TIMEOUTto 50msValidation
python -m pytest tests/tools/test_mcp_tool_issue_948.py::test_run_stdio_malware_check_does_not_block_event_loop tests/tools/test_mcp_tool_issue_948.py::test_run_stdio_malware_check_timeout_fails_open -q -o 'addopts='python -m pytest tests/tools/test_mcp_tool_issue_948.py tests/tools/test_osv_check.py tests/tools/test_mcp_tool.py -q -o 'addopts='git diff --checkCloses #29184.