diff -ruN a/src/pyghidra_mcp/context.py b/src/pyghidra_mcp/context.py
--- a/src/pyghidra_mcp/context.py	2026-09-25 07:20:21.000000000 +0100
+++ b/src/pyghidra_mcp/context.py	2026-09-30 04:01:37.329592826 +0100
@@ -9,7 +9,10 @@
 from pathlib import Path
 from typing import TYPE_CHECKING, Any, Union
 
-import chromadb
+try:
+    import chromadb
+except ImportError:
+    chromadb = None
 
 from pyghidra_mcp.decompiler_pool import DecompilerPool
 from pyghidra_mcp.import_detection import is_ghidra_importable
@@ -44,7 +47,7 @@
     ghidra_analysis_complete: bool
     file_path: Path | None = None
     load_time: float | None = None
-    code_collection: chromadb.Collection | None = None
+    code_collection: Any | None = None
     strings: list | None = None
 
     @property
diff -ruN a/src/pyghidra_mcp/indexing_mixin.py b/src/pyghidra_mcp/indexing_mixin.py
--- a/src/pyghidra_mcp/indexing_mixin.py	2026-09-25 07:20:21.000000000 +0100
+++ b/src/pyghidra_mcp/indexing_mixin.py	2026-09-30 04:01:37.329970548 +0100
@@ -6,8 +6,12 @@
 from pathlib import Path
 from typing import Any
 
-import chromadb
-from chromadb.config import Settings
+try:
+    import chromadb
+    from chromadb.config import Settings
+except ImportError:
+    chromadb = None
+    Settings = None
 
 from pyghidra_mcp.tools import DEFAULT_DECOMPILE_TIMEOUT_SECONDS, GhidraTools
 
@@ -25,6 +29,12 @@
     programs: dict[str, Any]
 
     def _init_indexing_state(self, pyghidra_mcp_dir: Path, *, threaded: bool) -> None:
+        if chromadb is None:
+            self.chroma_client = None
+            self.index_executor = None
+            self._index_futures = {}
+            self._index_lock = threading.Lock()
+            return
         chromadb_path = pyghidra_mcp_dir / "chromadb"
         chromadb_path.mkdir(parents=True, exist_ok=True)
         self.chroma_client = chromadb.PersistentClient(
@@ -148,6 +158,8 @@
         collection on disk. Such a collection (and any legacy one lacking the
         marker) is deleted here so the caller rebuilds it from scratch.
         """
+        if self.chroma_client is None:
+            return None
         name = self._normalize_collection_name(name)
         try:
             collection = self.chroma_client.get_collection(name=name)
@@ -171,6 +183,8 @@
         )
 
     def _init_chroma_code_collection_for_program(self, program_info: Any) -> None:
+        if self.chroma_client is None:
+            return
         from ghidra.program.model.listing import Function
 
         logger.info("Initializing Chroma code collection for %s", program_info.name)
diff -ruN a/src/pyghidra_mcp/server.py b/src/pyghidra_mcp/server.py
--- a/src/pyghidra_mcp/server.py	2026-09-25 07:20:21.000000000 +0100
+++ b/src/pyghidra_mcp/server.py	2026-09-30 04:01:37.330038554 +0100
@@ -36,6 +36,17 @@
 )
 logger = logging.getLogger(__name__)
 
+import builtins
+_orig_print = builtins.print
+def _safe_mcp_print(*args, **kwargs):
+    if kwargs.get("file") is None or kwargs.get("file") is sys.stdout:
+        kwargs["file"] = sys.stderr
+    try:
+        _orig_print(*args, **kwargs)
+    except (ValueError, OSError):
+        pass
+builtins.print = _safe_mcp_print
+
 
 class _SigintShutdownHandler:
     """Turn the first Ctrl+C into normal Python shutdown, then force-exit.
