You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fetch: Readability.js extraction has no timeout and blocks the event loop #5306
extract_content_from_html in src/fetch/src/mcp_server_fetch/server.py runs readabilipy synchronously, inside the fetch tool's async handler. When Node.js is on PATH, readabilipy runs Readability.js in a blocking subprocess call with no timeout. readabilipy 0.3 may also run npm install first. So:
a Node.js (or npm) that hangs stalls the tool call until the client gives up, with no error the model can read; and
while it runs, it blocks the server's event loop, so every other request on the connection waits too.
#4199 adds --no-readability-js as an operator opt-out. The default path still has no timeout and still blocks the event loop. This issue is the hardening that #4199's report also suggested (its suggestion c, a timeout on the subprocess). It was left out of that fix to keep it small: a timeout around readabilipy's own call can only abandon the worker thread, and the Node.js process it started keeps running.
Reproduce
Put a node that never exits first on the server's PATH (#!/bin/sh + exec sleep 3600), start mcp-server-fetch --ignore-robots-txt, and call fetch on any HTML page. The call never returns.
Expected
Extraction runs off the event loop and has a bounded time. On timeout, the Node.js process is killed, and the call either falls back to the pure-Python extractor or returns a tool error that names the cause and points at --no-readability-js. This probably means running readabilipy's ExtractArticle.js under our own asyncio subprocess, with a kill on timeout, instead of through readabilipy's wrapper.
I can reproduce this: with a node shim that runs sleep 3600 first on PATH, fetch never returns, and other requests on the connection stall while it runs.
I’d suggest running ExtractArticle.js via asyncio.create_subprocess_exec and wrapping it in asyncio.wait_for, killing the process on timeout so no orphaned Node.js process is left behind. On timeout I’d lean toward falling back to the pure-Python extractor, with a logged warning pointing at --no-readability-js. Is the timeout meant to be configurable, and should the npm install step be covered too?
A fake-node shim like the one in the repro would work as a regression test.
Activity
krishnayak986-lang commentedon Oct 27, 2026
I can reproduce this: with a node shim that runs sleep 3600 first on PATH, fetch never returns, and other requests on the connection stall while it runs.
I’d suggest running ExtractArticle.js via asyncio.create_subprocess_exec and wrapping it in asyncio.wait_for, killing the process on timeout so no orphaned Node.js process is left behind. On timeout I’d lean toward falling back to the pure-Python extractor, with a logged warning pointing at --no-readability-js. Is the timeout meant to be configurable, and should the npm install step be covered too?
A fake-node shim like the one in the repro would work as a regression test.