From e52c112774b262781d62020c5faa47d6e193e04f Mon Sep 17 00:00:00 2001 From: Ruiming Zhao Date: Tue, 11 Aug 2026 15:29:42 -0700 Subject: [PATCH] fix(core): warn when advertised MCP archives are rejected --- python/packages/core/agent_framework/_skills.py | 16 +++++++++------- .../packages/core/tests/core/test_mcp_skills.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/python/packages/core/agent_framework/_skills.py b/python/packages/core/agent_framework/_skills.py index d1fb1fb397..dda42b9a19 100644 --- a/python/packages/core/agent_framework/_skills.py +++ b/python/packages/core/agent_framework/_skills.py @@ -4721,16 +4721,16 @@ async def _download(self, entry: _McpSkillIndexEntry) -> tuple[bytes, str | None blob = _mcp_first_blob(result) if blob is None: - logger.debug("Skipping skill '%s': archive resource returned no binary content", entry.name) + logger.warning("Skipping skill '%s': archive resource returned no binary content", entry.name) return None data, mime_type = blob if not data: - logger.debug("Skipping skill '%s': archive resource returned empty content", entry.name) + logger.warning("Skipping skill '%s': archive resource returned empty content", entry.name) return None if len(data) > self._max_size_bytes: - logger.debug( + logger.warning( "Skipping skill '%s': archive resource exceeds the maximum allowed size (%d bytes)", entry.name, self._max_size_bytes, @@ -4749,7 +4749,9 @@ def _build_skill(self, entry: _McpSkillIndexEntry, data: bytes, mime_type: str | """ archive_format = _detect_archive_format(data, mime_type, entry.url) if archive_format is _ArchiveFormat.UNKNOWN: - logger.debug("Skipping skill '%s': unsupported archive media type '%s'", entry.name, mime_type or "(none)") + logger.warning( + "Skipping skill '%s': unsupported archive media type '%s'", entry.name, mime_type or "(none)" + ) return None try: @@ -4766,13 +4768,13 @@ def _skill_from_files(self, entry: _McpSkillIndexEntry, files: dict[str, bytes]) """Assemble an in-memory :class:`FileSkill` from the archive's extracted files.""" skill_md_key = self._find_skill_md(files) if skill_md_key is None: - logger.debug("Skipping skill '%s': archive contains no SKILL.md", entry.name) + logger.warning("Skipping skill '%s': archive contains no SKILL.md", entry.name) return None try: content = files[skill_md_key].decode("utf-8") except UnicodeDecodeError: - logger.debug("Skipping skill '%s': SKILL.md is not valid UTF-8", entry.name) + logger.warning("Skipping skill '%s': SKILL.md is not valid UTF-8", entry.name) return None frontmatter = FileSkillsSource._extract_frontmatter( # pyright: ignore[reportPrivateUsage] @@ -4782,7 +4784,7 @@ def _skill_from_files(self, entry: _McpSkillIndexEntry, files: dict[str, bytes]) return None if frontmatter.name != entry.name: - logger.debug( + logger.warning( "Skipping skill '%s': SKILL.md frontmatter name '%s' does not match the advertised entry name", entry.name, frontmatter.name, diff --git a/python/packages/core/tests/core/test_mcp_skills.py b/python/packages/core/tests/core/test_mcp_skills.py index 469f3cf513..a20551540f 100644 --- a/python/packages/core/tests/core/test_mcp_skills.py +++ b/python/packages/core/tests/core/test_mcp_skills.py @@ -917,6 +917,23 @@ async def test_frontmatter_name_mismatch_is_skipped(self) -> None: skills = await source.get_skills(_SOURCE_CTX) assert skills == [] + async def test_frontmatter_name_mismatch_is_logged_as_warning(self, caplog: pytest.LogCaptureFixture) -> None: + url = "skill://archives/packaged-skill.zip" + index = _make_archive_index("packaged-skill", url) + mismatched = ARCHIVE_SKILL_MD.replace("name: packaged-skill", "name: different-name") + archive = _make_zip({"SKILL.md": mismatched.encode()}) + client = _archive_client(index, url, archive, "application/zip") + + source = MCPSkillsSource(client=client) + with caplog.at_level("WARNING", logger="agent_framework._skills"): + skills = await source.get_skills(_SOURCE_CTX) + + assert skills == [] + assert any( + record.levelname == "WARNING" and "does not match the advertised entry name" in record.message + for record in caplog.records + ) + @pytest.mark.asyncio async def test_archive_without_skill_md_is_skipped(self) -> None: url = "skill://archives/packaged-skill.zip"