Repository navigation
fix(fetch): treat a header-less response as HTML only when its body is - #5300
Conversation
A response with no Content-Type was always treated as HTML, so JSON, plain text and other payloads went through readability and came back altered or as a simplification error. A header-less body is now simplified only when it opens with an HTML doctype or <html> tag (after any BOM and whitespace). application/xhtml+xml is recognized as HTML, and the media type is matched case-insensitively. Closes #5134 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
There was a problem hiding this comment.
🟢 Approval recommended
The focused classification change addresses the reported defect with relevant regression coverage and no identified blocking issues.
No open issues 🎉
What changed in this PR
This PR fixes #5134 in the Fetch server by returning header-less non-HTML payloads unchanged instead of sending them through HTML simplification.
Changes:
- Adds an opening-body HTML check for responses without a
Content-Type. - Recognizes XHTML and case-insensitive HTML media types while preserving the existing
<htmlsniff. - Adds 13 protocol-level regression cases covering raw payloads, empty responses, HTML detection, and declared content types.
| File | Description |
|---|---|
src/fetch/tests/test_missing_content_type.py |
Adds regression coverage for content classification and returned content. |
src/fetch/src/mcp_server_fetch/server.py |
Replaces the missing-header HTML assumption with body detection and expands HTML media-type recognition. |
Review effort: Balanced
|
Copilot round 1: clean (no findings, no suppressed comments). The review loop ends here on the first clean round; nothing was pushed after it. |
…tion directly readabilipy 0.2.0 writes the page to a temp file in the locale encoding, so a page with a byte-order mark cannot be extracted on Windows (#5303). That crash predates #5134 and is not what it changes, so the protocol case is skipped on Windows and the BOM handling of _body_looks_like_html is asserted directly on every platform. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: cliffhall <cliff@futurescale.com>
|
Pushed 593dab9 for the "Test fetch (Windows)" failure. The In
No change to |
|
Copilot round 2 (on 593dab9, the Windows test fix): clean, with no findings and no suppressed comments. The loop stops here on a clean round. |
Closes #5134
Description
fetch_urltreated any response with noContent-Typeheader as HTML (or not content_type), so JSON, plain text, CSV and other header-less payloads were pushed through readability and markdownify: they came back altered (underscores and asterisks escaped, newlines collapsed without Node) or, for an empty body, as a simplification error.A header-less body is now simplified only when the body itself opens like an HTML document: after any byte-order mark and whitespace, an
<!doctype htmlor an<htmltag, case-insensitively (new helper_body_looks_like_html). Anything else comes back raw, like any other non-HTML content type. Two small additions in the same expression:application/xhtml+xmlcounts as HTML, and the media type is matched case-insensitively (Text/HTMLwas previously returned raw). The existing"<html" in page_raw[:100]sniff is unchanged, and the doctype sniff applies only when the header is missing, so a declared non-HTML type is still honored.Server Details
mcp-server-fetch)fetchtool's content handling (is_page_htmlinfetch_url); no tool, argument or prompt changesMotivation and Context
See #5134. The reporter's prototype (and the branch linked there) sniffed the body the same way; this keeps the change to the HTML decision only.
How Has This Been Tested?
New protocol-level tests in
src/fetch/tests/test_missing_content_type.py(13 cases) driveserve()through aClientSessionwith HTTP mocked: header-less JSON object/array, plain text and CSV come back raw and unaltered; a header-less empty body no longer reports "Page failed to be simplified"; header-less HTML (upper/lowercase doctype, BOM plus whitespace, a<HTML>tag beyond the 100-character window) is still simplified;application/xhtml+xmlandText/HTML; charset=UTF-8are simplified; a doctype does not override a declaredtext/plain. Eight of them fail againstv2/main. Fetch coverage:server.py100% lines, 98.4% branches.npm run local:gateexits 0.Client evidence: a local HTTP server that sends no
Content-Typeheader serving/data.json({"snake_case_key": "*value*", "items": [1, 2, 3]}),/notes.txt(plain_text with *asterisks*\nsecond line) and/page(a<!DOCTYPE html>article with no<html>tag). The server ran asmcp-server-fetch --ignore-robots-txt --allow-private-ips; "before" isorigin/v2/main'smcp_server_fetchsource.Inspector CLI 2.9.0,
--method tools/call --tool-name fetch --tool-arg url=…, with--protocol-era legacy(2025-11-25) and--protocol-era modern(2026-07-28; result_metacarriesio.modelcontextprotocol/serverInfo). Both eras returned identical text; exit 0 for all twelve calls./data.json{"snake\_case\_key": "\*value\*", "items": [1, 2, 3]}(markdown-escaped)Content type cannot be simplified to markdown, but here is the raw content:then{"snake_case_key": "*value*", "items": [1, 2, 3]}/notes.txtplain\_text with \*asterisks\*plain_text with *asterisks*/second line/pageThis is a [link](https://example.com/x) …,## Sub headingLLM client: Claude Code 2.1.311 (
claude -p --strict-mcp-config, fetch server as above; this client does not let the era be chosen, so it is not labeled with one). Prompt: "Use the fetch tool to fetch http://127.0.0.1:51347/data.json. Reply with exactly two lines: first, the JSON value of the key snake_case_key exactly as the tool returned it …; second, yes or no: did the tool say the content type cannot be simplified and return raw content?""\*value\*"/no"*value*"/yesBreaking Changes
None. No configuration change. A header-less non-HTML response is now returned raw instead of as (mangled) Markdown, which is the fix.
Types of changes
Checklist
npm run changeset) if this changes what a TypeScript server publishes (not applicable: fetch is a Python server)Additional context
Part of the Fetch v2.2.0 tracker #5265. The raw-content prefix reads
Content type cannot be simplified…(an empty media type) when the header is missing; that line is unchanged here, since the issue is about the HTML decision.🤖 Generated with Claude Code