Title: Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages · Issue #2433 · modelcontextprotocol/python-sdk · GitHub
Open Graph Title: Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages · Issue #2433 · modelcontextprotocol/python-sdk
X Title: Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages · Issue #2433 · modelcontextprotocol/python-sdk
Description: Summary mcp/server/stdio.py creates TextIOWrapper(sys.stdout.buffer, encoding="utf-8") without specifying newline="". On Windows, the default newline=None causes \n → \r\n translation, so every JSON-RPC message written to stdout ends wit...
Open Graph Description: Summary mcp/server/stdio.py creates TextIOWrapper(sys.stdout.buffer, encoding="utf-8") without specifying newline="". On Windows, the default newline=None causes \n → \r\n translation, so every JSO...
X Description: Summary mcp/server/stdio.py creates TextIOWrapper(sys.stdout.buffer, encoding="utf-8") without specifying newline="". On Windows, the default newline=None causes \n → \r\n trans...
Opengraph URL: https://github.com/modelcontextprotocol/python-sdk/issues/2433
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"Windows: TextIOWrapper in stdio_server() emits CRLF instead of LF, corrupting newline-delimited JSON messages","articleBody":"## Summary\n\n`mcp/server/stdio.py` creates `TextIOWrapper(sys.stdout.buffer, encoding=\"utf-8\")` without specifying `newline=\"\"`. On Windows, the default `newline=None` causes `\\n` → `\\r\\n` translation, so every JSON-RPC message written to stdout ends with `\\r\\n` instead of `\\n`.\n\nThe MCP spec uses newline-delimited JSON with `\\n` as the delimiter. Emitting `\\r\\n` is a protocol-level impurity.\n\n## Affected file\n\n[`mcp/server/stdio.py` lines 46–49](https://github.com/modelcontextprotocol/python-sdk/blob/main/src/mcp/server/stdio.py)\n\n```python\n# Current (buggy on Windows)\nif not stdin:\n stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding=\"utf-8\"))\nif not stdout:\n stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding=\"utf-8\"))\n```\n\n## Reproduction\n\nOn Windows, spawn a Python subprocess that uses this code and read the raw bytes:\n\n```python\nimport subprocess, sys\n\nscript = r'''\nimport sys\nfrom io import TextIOWrapper\nstdout = TextIOWrapper(sys.stdout.buffer, encoding=\"utf-8\")\nstdout.write('{\"jsonrpc\":\"2.0\",\"result\":\"ok\"}\\n')\nstdout.flush()\n'''\n\nproc = subprocess.Popen([sys.executable, \"-c\", script], stdout=subprocess.PIPE)\nout, _ = proc.communicate(timeout=5)\nprint(repr(out))\n# Output on Windows: b'{\"jsonrpc\":\"2.0\",\"result\":\"ok\"}\\r\\n'\n# Output on Linux: b'{\"jsonrpc\":\"2.0\",\"result\":\"ok\"}\\n'\n```\n\nVerified on:\n- **OS:** Windows 11 Pro (10.0.26200)\n- **Python:** 3.11\n- **mcp:** 1.26.0\n\n## Why this matters\n\nWhile the current JS MCP SDK client (`StdioClientTransport`) strips trailing `\\r` via `.replace(/\\r$/, \"\")` before parsing, this is a server-side bug that:\n\n1. Violates the NDJSON wire format (which specifies LF-only line endings)\n2. Creates an asymmetry: the Python `stdio_client` sends bare `\\n`, but the Python `stdio_server` responds with `\\r\\n`\n3. Could break any MCP client that does a strict `split(\"\\n\")` and then fails to `JSON.parse` the line with a trailing `\\r`\n\nThe comment on line 43 even acknowledges: *\"Encoding of stdin/stdout as text streams on python is platform-dependent (Windows is particularly problematic)\"* — but the fix applied (`re-wrap to ensure UTF-8`) doesn't also fix the newline translation mode.\n\n## Fix\n\nAdd `newline=\"\"` to both `TextIOWrapper` calls. `newline=\"\"` disables translation while still operating in text mode:\n\n```python\nif not stdin:\n stdin = anyio.wrap_file(TextIOWrapper(sys.stdin.buffer, encoding=\"utf-8\", newline=\"\"))\nif not stdout:\n stdout = anyio.wrap_file(TextIOWrapper(sys.stdout.buffer, encoding=\"utf-8\", newline=\"\"))\n```\n\nWith this fix:\n```python\n# newline=\"\" result:\nbuf = io.BytesIO()\nwrapper = TextIOWrapper(buf, encoding=\"utf-8\", newline=\"\")\nwrapper.write('{\"jsonrpc\":\"2.0\",\"result\":\"ok\"}\\n')\nwrapper.flush()\nrepr(buf.getvalue())\n# b'{\"jsonrpc\":\"2.0\",\"result\":\"ok\"}\\n' ← correct on all platforms\n```\n\nThe same fix should be applied to the `stdin` wrapper so that incoming messages with bare `\\n` are not translated either (avoiding any future issues if a client sends strict LF).\n\n## Context\n\nThis was discovered while debugging Windows MCP tool timeouts with `mem0-mcp-selfhosted`. The eager-init approach fixed the actual timeout, but this CRLF emission was identified as a secondary protocol-level issue during investigation.","author":{"url":"https://github.com/DonovanDeHart","@type":"Person","name":"DonovanDeHart"},"datePublished":"2026-04-13T12:50:52.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":8},"url":"https://github.com/2433/python-sdk/issues/2433"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:1e5da2f9-babe-d83e-02a8-cf33d2847fe2 |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | AA1A:3BC6BD:ABE7F5:F61517:6A5E2351 |
| html-safe-nonce | 089fb2b331545857244f37a3b0ae3e21136ffc9148364fedabab5c7ae3cd7de1 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiJBQTFBOjNCQzZCRDpBQkU3RjU6RjYxNTE3OjZBNUUyMzUxIiwidmlzaXRvcl9pZCI6IjQ3ODU3MTA2Nzc2MzczNDIwMzMiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | ab7ae677a28cd0ddb8990dafca371ba9f89c4d0ce22bf27c61f6ccf82d2b23a3 |
| hovercard-subject-tag | issue:4254758384 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/modelcontextprotocol/python-sdk/2433/issue_layout |
| twitter:image | https://opengraph.githubassets.com/01e961f8490cf5f15ba4d69cc1562f64a6ec6758520bdae9696412bfa5453d77/modelcontextprotocol/python-sdk/issues/2433 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/01e961f8490cf5f15ba4d69cc1562f64a6ec6758520bdae9696412bfa5453d77/modelcontextprotocol/python-sdk/issues/2433 |
| og:image:alt | Summary mcp/server/stdio.py creates TextIOWrapper(sys.stdout.buffer, encoding="utf-8") without specifying newline="". On Windows, the default newline=None causes \n → \r\n translation, so every JSO... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | DonovanDeHart |
| hostname | github.com |
| expected-hostname | github.com |
| None | e5010f4d2748a3cbef86e1580413ff14701bd99f255268dfb4a2857c77e2cc7c |
| turbo-cache-control | no-preview |
| go-import | github.com/modelcontextprotocol/python-sdk git https://github.com/modelcontextprotocol/python-sdk.git |
| octolytics-dimension-user_id | 182288589 |
| octolytics-dimension-user_login | modelcontextprotocol |
| octolytics-dimension-repository_id | 862584018 |
| octolytics-dimension-repository_nwo | modelcontextprotocol/python-sdk |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 862584018 |
| octolytics-dimension-repository_network_root_nwo | modelcontextprotocol/python-sdk |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 7d23604c4a8ce0274b4c71bb2adbe6e1c9d5904f |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width