@@ -566,6 +566,51 @@ def sanitize_data_for_db(obj):
566566 return obj
567567
568568
569+ def sanitize_metadata (metadata : dict ) -> dict :
570+ """
571+ Return a JSON-safe copy of a metadata dict for database storage.
572+
573+ The middleware metadata accumulates non-serializable Python objects
574+ (e.g. callable tool functions, MCP client instances) that cause
575+ PostgreSQL JSON inserts to fail. This helper strips those out while
576+ preserving the primitive data needed for file-to-chat linking.
577+ """
578+ if not isinstance (metadata , dict ):
579+ return metadata
580+
581+ def _sanitize (obj ):
582+ if isinstance (obj , (str , int , float , bool , type (None ))):
583+ return obj
584+ if isinstance (obj , dict ):
585+ return {
586+ k : _sanitize (v )
587+ for k , v in obj .items ()
588+ if not callable (v ) and _is_serializable (v )
589+ }
590+ if isinstance (obj , list ):
591+ return [_sanitize (v ) for v in obj if not callable (v ) and _is_serializable (v )]
592+ if callable (obj ):
593+ return None
594+ # Last resort: try to see if it's serializable
595+ try :
596+ json .dumps (obj )
597+ return obj
598+ except (TypeError , ValueError ):
599+ return None
600+
601+ def _is_serializable (obj ):
602+ """Quick check whether a value can survive JSON serialization."""
603+ if isinstance (obj , (str , int , float , bool , type (None ), dict , list )):
604+ return True
605+ try :
606+ json .dumps (obj )
607+ return True
608+ except (TypeError , ValueError ):
609+ return False
610+
611+ return _sanitize (metadata )
612+
613+
569614def extract_folders_after_data_docs (path ):
570615 # Convert the path to a Path object if it's not already
571616 path = Path (path )
0 commit comments