From c22c709df2dc63c7843704be6a78a00e27d10e36 Mon Sep 17 00:00:00 2001 From: verveguy Date: Sun, 4 Jan 2026 21:47:31 -0500 Subject: [PATCH 1/2] fix(mcp-hmr): watch target directory by default --- packages/mcp-hmr/mcp_hmr.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/packages/mcp-hmr/mcp_hmr.py b/packages/mcp-hmr/mcp_hmr.py index 745b003..037bef3 100644 --- a/packages/mcp-hmr/mcp_hmr.py +++ b/packages/mcp-hmr/mcp_hmr.py @@ -9,6 +9,37 @@ __all__ = "mcp_server", "run_with_hmr" +def _resolve_watch_path(module_or_path: str) -> str: + """Resolve a stable watch directory for hot reload. + + Why this exists: + `AsyncReloader` expects a non-empty, valid path to watch. In mcp-hmr 0.0.3.2 + the watcher was initialized with an empty string, which can result in no + files being watched (or errors) depending on the platform / watcher backend. + + Strategy: + - If the target is a file path, watch its parent directory. + - If the target is an importable module, watch the directory containing the + module's file (or package __init__.py). + - Fall back to the current working directory. + """ + # path:attr target + if (p := Path(module_or_path)).is_file(): + return str(p.resolve().parent) + + # module:attr target + spec = find_spec(module_or_path) + if spec is not None: + if spec.origin: + # For packages, origin points at __init__.py; for modules, origin is the .py file. + return str(Path(spec.origin).resolve().parent) + if spec.submodule_search_locations: + # Namespace packages may have no origin; use their search location. + return str(Path(next(iter(spec.submodule_search_locations))).resolve()) + + return str(Path.cwd()) + + def mcp_server(target: str): module, attr = target.rsplit(":", 1) @@ -80,7 +111,7 @@ async def main(): class Reloader(AsyncReloader): def __init__(self): - super().__init__("") + super().__init__(_resolve_watch_path(module)) self.error_filter.exclude_filenames.add(__file__) async def __aenter__(self): From 53aee10f0ee7db4234631f6c5b52fab8309738f9 Mon Sep 17 00:00:00 2001 From: verveguy Date: Sun, 4 Jan 2026 22:31:54 -0500 Subject: [PATCH 2/2] fix(mcp-hmr): make watch path resolution more robust --- packages/mcp-hmr/mcp_hmr.py | 31 +++++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 8 deletions(-) diff --git a/packages/mcp-hmr/mcp_hmr.py b/packages/mcp-hmr/mcp_hmr.py index 037bef3..4c5b198 100644 --- a/packages/mcp-hmr/mcp_hmr.py +++ b/packages/mcp-hmr/mcp_hmr.py @@ -24,18 +24,33 @@ def _resolve_watch_path(module_or_path: str) -> str: - Fall back to the current working directory. """ # path:attr target - if (p := Path(module_or_path)).is_file(): + p = Path(module_or_path) + if p.is_file(): return str(p.resolve().parent) + # Some callers may provide a directory path directly; watch it as-is. + if p.is_dir(): + return str(p.resolve()) + # module:attr target - spec = find_spec(module_or_path) + try: + spec = find_spec(module_or_path) + except (ImportError, ModuleNotFoundError, TypeError, ValueError): + spec = None + if spec is not None: - if spec.origin: - # For packages, origin points at __init__.py; for modules, origin is the .py file. - return str(Path(spec.origin).resolve().parent) - if spec.submodule_search_locations: - # Namespace packages may have no origin; use their search location. - return str(Path(next(iter(spec.submodule_search_locations))).resolve()) + # For built-in / frozen modules, spec.origin can be strings like "built-in". + # Use has_location to ensure origin is a real filesystem location. + if getattr(spec, "has_location", False) and spec.origin: + origin_path = Path(spec.origin) + if origin_path.is_file(): + # For packages, origin points at __init__.py; for modules, origin is the .py file. + return str(origin_path.resolve().parent) + + # Namespace packages may have no origin; use their search location. + locations = list(spec.submodule_search_locations or ()) + if locations: + return str(Path(locations[0]).resolve()) return str(Path.cwd())