From 40c3e122e8e5cce4d080c3b87805ea7bae92c96c Mon Sep 17 00:00:00 2001 From: nate nowack Date: Sun, 2 Aug 2026 08:33:32 -0500 Subject: [PATCH] Write downloaded skill text as UTF-8 (#4715) --- fastmcp_slim/fastmcp/utilities/skills.py | 2 +- tests/utilities/test_skills.py | 47 ++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/fastmcp_slim/fastmcp/utilities/skills.py b/fastmcp_slim/fastmcp/utilities/skills.py index 2c93b1f7f..73b859548 100644 --- a/fastmcp_slim/fastmcp/utilities/skills.py +++ b/fastmcp_slim/fastmcp/utilities/skills.py @@ -205,7 +205,7 @@ async def download_skill( # Write content if isinstance(content, mcp_types.TextResourceContents): - file_path.write_text(content.text) + file_path.write_text(content.text, encoding="utf-8") elif isinstance(content, mcp_types.BlobResourceContents): file_path.write_bytes(base64.b64decode(content.blob)) else: diff --git a/tests/utilities/test_skills.py b/tests/utilities/test_skills.py index 62268ac91..90c6f5726 100644 --- a/tests/utilities/test_skills.py +++ b/tests/utilities/test_skills.py @@ -269,6 +269,53 @@ class TestDownloadSkill: downloaded = (result / "SKILL.md").read_text() assert downloaded == original + async def test_writes_text_resources_as_utf8( + self, tmp_path: Path, monkeypatch: pytest.MonkeyPatch + ): + content = "Use the tool — then stop." + manifest = { + "skill": "unicode", + "files": [ + { + "path": "SKILL.md", + "size": len(content.encode("utf-8")), + "hash": "sha256:unicode", + } + ], + } + client = FakeResourceReader( + { + "skill://unicode/_manifest": [ + text_resource("skill://unicode/_manifest", json.dumps(manifest)) + ], + "skill://unicode/SKILL.md": [ + text_resource("skill://unicode/SKILL.md", content) + ], + } + ) + original_write_text = Path.write_text + + def locale_sensitive_write_text( + path: Path, + data: str, + encoding: str | None = None, + errors: str | None = None, + newline: str | None = None, + ) -> int: + return original_write_text( + path, + data, + encoding=encoding or "ascii", + errors=errors, + newline=newline, + ) + + monkeypatch.setattr(Path, "write_text", locale_sensitive_write_text) + + result = await download_skill(cast(Client, client), "unicode", tmp_path) + + assert (result / "SKILL.md").read_text(encoding="utf-8") == content + async def test_raises_if_exists_without_overwrite( self, skills_server: FastMCP, tmp_path: Path ):