From 0d25dfb5f4cebe3ab04b544c2d6bcb47c29eb508 Mon Sep 17 00:00:00 2001 From: Afonso Coutinho Date: Wed, 3 Jun 2026 00:35:54 +0100 Subject: [PATCH] fix: shared MCP truncate() crashes on None/non-string tool output (#1605) --- mcp_servers/_common.py | 4 ++++ tests/test_mcp_common_truncate.py | 27 +++++++++++++++++++++++++++ 2 files changed, 31 insertions(+) create mode 100644 tests/test_mcp_common_truncate.py diff --git a/mcp_servers/_common.py b/mcp_servers/_common.py index 641c852..341bfe6 100644 --- a/mcp_servers/_common.py +++ b/mcp_servers/_common.py @@ -13,6 +13,10 @@ SEARCH_TIMEOUT = 30 def truncate(text: str, limit: int = MAX_OUTPUT_CHARS) -> str: """Truncate text to *limit* characters with a suffix note.""" + if not isinstance(text, str): + # Tool output is occasionally None or a non-string; len(None) would + # raise. Coerce so this shared helper never crashes a tool response. + text = "" if text is None else str(text) if len(text) > limit: return text[:limit] + f"\n... (truncated, {len(text)} chars total)" return text diff --git a/tests/test_mcp_common_truncate.py b/tests/test_mcp_common_truncate.py new file mode 100644 index 0000000..867581f --- /dev/null +++ b/tests/test_mcp_common_truncate.py @@ -0,0 +1,27 @@ +"""Regression: the shared MCP truncate() must tolerate non-string input.""" +import importlib.machinery +import importlib.util +from pathlib import Path + +_PATH = Path(__file__).resolve().parents[1] / "mcp_servers" / "_common.py" + + +def _load(): + loader = importlib.machinery.SourceFileLoader("odysseus_mcp_common", str(_PATH)) + spec = importlib.util.spec_from_loader(loader.name, loader) + module = importlib.util.module_from_spec(spec) + loader.exec_module(module) + return module + + +def test_truncate_handles_none_and_nonstring(): + c = _load() + assert c.truncate(None) == "" + assert c.truncate(12345) == "12345" + + +def test_truncate_string_behaviour_unchanged(): + c = _load() + assert c.truncate("hello", limit=100) == "hello" + out = c.truncate("x" * 50, limit=10) + assert out.startswith("x" * 10) and "truncated" in out