Description and Issues
generate_cli_command writes both generated files with the platform's locale encoding instead of UTF-8:
# fastmcp/cli/generate.py:770 and :784
output_path.write_text(script)
skill_path.write_text(skill_content)
Path.write_text() without encoding= uses locale.getpreferredencoding(False), which on a Chinese Windows install is cp936/GBK. Two distinct failure modes follow from that, both hit by ordinary tool descriptions:
1. Generation aborts. Any character GBK cannot encode (emoji, most typographic symbols) raises out of the CLI:
File ".../fastmcp/cli/generate.py", line 770, in generate_cli_command
output_path.write_text(script)
File ".../Python311/Lib/pathlib.py", line 1079, in write_text
return f.write(data)
UnicodeEncodeError: 'gbk' codec can't encode character '\U0001f642' in position 681
2. The generated CLI is not importable. When the text is GBK-encodable — i.e. any Chinese, Japanese or Cyrillic description — the write succeeds but the bytes on disk are GBK, while CPython decodes source files as UTF-8. So the file the tool just produced cannot be run:
bytes on disk : b'DESC = "\xb2\xe9\xd1\xaf Boston \xb5\xc4\xcc\xec\xc6\xf8"\r\n'
valid UTF-8 : NO -> 'utf-8' codec can't decode byte 0xb2 in position 8: invalid start byte
importable : NO -> (unicode error) 'utf-8' codec can't decode byte 0xb2 in position 0: invalid start character
SKILL.md has the same problem, and it is worse there: skill files are read back by agents and the convention is UTF-8, so a GBK SKILL.md is silently unreadable to whatever consumes it.
This is inconsistent with the rest of the codebase, which already pins the encoding on the files it writes — fastmcp/mcp_config.py:348 and fastmcp/utilities/skills.py:208 both pass encoding="utf-8". generate.py looks like the two call sites that were missed.
Steps to Reproduce
Click to expand
Needs a non-UTF-8 locale to see the failure (Windows with a zh-CN system locale here). On a UTF-8 machine both writes already succeed, which is why CI does not catch it.
import asyncio, tempfile
from pathlib import Path
from unittest.mock import patch
from fastmcp import FastMCP
from fastmcp.cli import generate as generate_module
from fastmcp.cli.generate import generate_cli_command
from fastmcp.client import Client
server = FastMCP("WeatherServer")
@server.tool
def get_weather(city: str) -> str:
"""查询 Boston 的天气 — returns a forecast 🙂"""
return f"sunny in {city}"
async def main():
out = Path(tempfile.mkdtemp()) / "cli.py"
with (
patch.object(generate_module, "resolve_server_spec", lambda spec, **kw: "fake://server"),
patch.object(generate_module, "_build_client", lambda resolved, **kw: Client(server)),
):
await generate_cli_command("weather-server", str(out))
print(out.read_bytes()[:80])
asyncio.run(main())
Drop the emoji from the docstring to get failure mode 2 instead of mode 1.
Expected Behavior
Both generated files are written as UTF-8 regardless of the platform locale, so that a CLI produced on any machine is importable on any other, and SKILL.md is valid UTF-8.
Proposed fix
Pass encoding="utf-8" at the two call sites, matching mcp_config.py and utilities/skills.py, plus a regression test that asserts the produced bytes decode as UTF-8 (which holds on every platform, and only actually fails on a non-UTF-8 locale).
Two related notes, for whatever they are worth:
fastmcp/cli/install/claude_desktop.py:118 reads the user's existing claude_desktop_config.json the same way (.read_text() with no encoding). That file is JSON, hence UTF-8 by RFC 8259, so a config containing a non-ASCII path or server name fails to load on a cp936 machine. Same class, different command, and it needs the read side fixed as well as the write side.
- The repo's own guard test is affected by the same rule:
tests/server/auth/test_redirect_validation.py:232 does path.read_text() over the auth modules, and on this machine it raises UnicodeDecodeError: 'gbk' codec can't decode byte 0x94 — so that security guard silently does not run outside UTF-8 locales. Happy to fold that one in if you want it in the same PR.
I can open a PR with the fix and the regression test if this is wanted.
Description and Issues
generate_cli_commandwrites both generated files with the platform's locale encoding instead of UTF-8:Path.write_text()withoutencoding=useslocale.getpreferredencoding(False), which on a Chinese Windows install iscp936/GBK. Two distinct failure modes follow from that, both hit by ordinary tool descriptions:1. Generation aborts. Any character GBK cannot encode (emoji, most typographic symbols) raises out of the CLI:
2. The generated CLI is not importable. When the text is GBK-encodable — i.e. any Chinese, Japanese or Cyrillic description — the write succeeds but the bytes on disk are GBK, while CPython decodes source files as UTF-8. So the file the tool just produced cannot be run:
SKILL.mdhas the same problem, and it is worse there: skill files are read back by agents and the convention is UTF-8, so a GBKSKILL.mdis silently unreadable to whatever consumes it.This is inconsistent with the rest of the codebase, which already pins the encoding on the files it writes —
fastmcp/mcp_config.py:348andfastmcp/utilities/skills.py:208both passencoding="utf-8".generate.pylooks like the two call sites that were missed.Steps to Reproduce
Click to expand
Needs a non-UTF-8 locale to see the failure (Windows with a zh-CN system locale here). On a UTF-8 machine both writes already succeed, which is why CI does not catch it.
Drop the emoji from the docstring to get failure mode 2 instead of mode 1.
Expected Behavior
Both generated files are written as UTF-8 regardless of the platform locale, so that a CLI produced on any machine is importable on any other, and
SKILL.mdis valid UTF-8.Proposed fix
Pass
encoding="utf-8"at the two call sites, matchingmcp_config.pyandutilities/skills.py, plus a regression test that asserts the produced bytes decode as UTF-8 (which holds on every platform, and only actually fails on a non-UTF-8 locale).Two related notes, for whatever they are worth:
fastmcp/cli/install/claude_desktop.py:118reads the user's existingclaude_desktop_config.jsonthe same way (.read_text()with no encoding). That file is JSON, hence UTF-8 by RFC 8259, so a config containing a non-ASCII path or server name fails to load on a cp936 machine. Same class, different command, and it needs the read side fixed as well as the write side.tests/server/auth/test_redirect_validation.py:232doespath.read_text()over the auth modules, and on this machine it raisesUnicodeDecodeError: 'gbk' codec can't decode byte 0x94— so that security guard silently does not run outside UTF-8 locales. Happy to fold that one in if you want it in the same PR.I can open a PR with the fix and the regression test if this is wanted.