From f5490583691f20f0fb2c1318a89e1834780dd001 Mon Sep 17 00:00:00 2001 From: red person Date: Wed, 3 Jun 2026 08:11:35 +0300 Subject: [PATCH] Normalize stored MCP CLI JSON (#1554) --- scripts/odysseus-mcp | 26 ++++++++++++++++++-------- tests/test_mcp_cli_json.py | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 8 deletions(-) create mode 100644 tests/test_mcp_cli_json.py diff --git a/scripts/odysseus-mcp b/scripts/odysseus-mcp index 0a6d58c..0e86f81 100755 --- a/scripts/odysseus-mcp +++ b/scripts/odysseus-mcp @@ -33,15 +33,25 @@ except ModuleNotFoundError as e: sys.exit(2) +def _json_list(raw) -> list: + try: + value = json.loads(raw) if raw else [] + except (TypeError, json.JSONDecodeError): + return [] + return value if isinstance(value, list) else [] + + +def _json_dict(raw) -> dict: + try: + value = json.loads(raw) if raw else {} + except (TypeError, json.JSONDecodeError): + return {} + return value if isinstance(value, dict) else {} + + def _serialize(s: "McpServer", redact_env: bool = True) -> dict: - try: - args_arr = json.loads(s.args) if s.args else [] - except json.JSONDecodeError: - args_arr = [] - try: - env_obj = json.loads(s.env) if s.env else {} - except json.JSONDecodeError: - env_obj = {} + args_arr = _json_list(s.args) + env_obj = _json_dict(s.env) if redact_env and isinstance(env_obj, dict): env_obj = {k: ("***" if v else "") for k, v in env_obj.items()} return { diff --git a/tests/test_mcp_cli_json.py b/tests/test_mcp_cli_json.py new file mode 100644 index 0000000..4301b71 --- /dev/null +++ b/tests/test_mcp_cli_json.py @@ -0,0 +1,33 @@ +import importlib.machinery +import importlib.util +import sys +import types +from pathlib import Path +from unittest.mock import MagicMock + + +ROOT = Path(__file__).resolve().parents[1] + + +def _load_cli(monkeypatch): + db = types.ModuleType("core.database") + db.SessionLocal = MagicMock() + db.McpServer = MagicMock() + monkeypatch.setitem(sys.modules, "core.database", db) + path = ROOT / "scripts" / "odysseus-mcp" + loader = importlib.machinery.SourceFileLoader("odysseus_mcp_cli", 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_mcp_json_helpers_reject_wrong_shapes(monkeypatch): + cli = _load_cli(monkeypatch) + + assert cli._json_list('["a"]') == ["a"] + assert cli._json_list('{"not":"list"}') == [] + assert cli._json_list("{bad") == [] + assert cli._json_dict('{"A":"B"}') == {"A": "B"} + assert cli._json_dict('["bad"]') == {} + assert cli._json_dict("{bad") == {}