Skip to content

fetch: Readability.js extraction has no timeout and blocks the event loop #5306

Open

Description

@cliffhall

Problem

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.

Related: #4199, #4830.

Activity

  1. added this to the v2.3.0 milestone on Oct 27, 2026
  2. added
    bugSomething isn't working
    server-fetchReference implementation for the Fetch MCP server - src/fetch
    on Oct 27, 2026
  3. krishnayak986-lang commented on Oct 27, 2026

    @krishnayak986-lang

    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.

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

    bugSomething isn't workingserver-fetchReference implementation for the Fetch MCP server - src/fetchv2

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions