From f83bc05a5f61d7771d15c453fd318c00c3ac97b6 Mon Sep 17 00:00:00 2001 From: xbzz1018 Date: Thu, 24 Sep 2026 20:13:18 +0800 Subject: [PATCH] fix(mcp): resolve generated file paths containing spaces (#5819) * fix(mcp): resolve generated file paths containing spaces * fix(mcp): avoid partial rewrites at whitespace suffixes --------- Co-authored-by: xbzz1018 <186267668+xbzz1018@users.noreply.github.com> --- .../packages/harness/deerflow/mcp/tools.py | 54 +++++++ backend/tests/test_mcp_file_migration.py | 135 ++++++++++++++++++ 2 files changed, 189 insertions(+) diff --git a/backend/packages/harness/deerflow/mcp/tools.py b/backend/packages/harness/deerflow/mcp/tools.py index f6b2d9603c..de11cba254 100644 --- a/backend/packages/harness/deerflow/mcp/tools.py +++ b/backend/packages/harness/deerflow/mcp/tools.py @@ -288,6 +288,50 @@ def _rewrite_unique_bare_filenames( return rewritten +def _rewrite_changed_paths_with_spaces( + text: str, + *, + changed_files: Iterable[Path], + thread_id: str, + user_id: str, + source_base_dir: Path | None, +) -> str: + """Rewrite changed paths with spaces only at unambiguous text boundaries.""" + candidates: dict[str, set[str]] = {} + for path in changed_files: + spellings = [str(path)] + if path.anchor == "/": + # Some servers print file URIs without percent-encoding spaces. + spellings.extend((f"file:{path}", f"file://{path}")) + if source_base_dir is not None: + try: + relative = path.relative_to(source_base_dir).as_posix() + except ValueError: + pass + else: + spellings.extend((relative, f"./{relative}")) + matching_spellings = [spelling for spelling in spellings if any(char.isspace() for char in spelling) and spelling in text] + if not matching_spellings: + continue + virtual_path = _local_uri_to_virtual_path(str(path), thread_id=thread_id, user_id=user_id) + if virtual_path is None: + continue + for spelling in matching_spellings: + candidates.setdefault(spelling, set()).add(virtual_path) + + rewritten = text + for spelling in sorted(candidates, key=len, reverse=True): + destinations = candidates[spelling] + if len(destinations) != 1 or spelling not in rewritten: + continue + # Whitespace after an unquoted spelling may continue a longer filename. + # Require punctuation or the end of the text instead of rewriting a prefix. + pattern = re.compile(rf"(?