4545from  fastapi .responses  import  JSONResponse , RedirectResponse , StreamingResponse 
4646from  fastapi .staticfiles  import  StaticFiles 
4747from  fastapi .templating  import  Jinja2Templates 
48+ from  jsonpath_ng .ext  import  parse 
49+ from  jsonpath_ng .jsonpath  import  JSONPath 
4850from  pydantic  import  ValidationError 
4951from  sqlalchemy  import  text 
5052from  sqlalchemy .exc  import  IntegrityError 
6163from  mcpgateway .auth  import  get_current_user 
6264from  mcpgateway .bootstrap_db  import  main  as  bootstrap_db 
6365from  mcpgateway .cache  import  ResourceCache , SessionRegistry 
64- from  mcpgateway .config  import  jsonpath_modifier ,  settings 
66+ from  mcpgateway .config  import  settings 
6567from  mcpgateway .db  import  refresh_slugs_on_startup , SessionLocal 
6668from  mcpgateway .db  import  Tool  as  DbTool 
6769from  mcpgateway .handlers .sampling  import  SamplingHandler 
102104from  mcpgateway .services .completion_service  import  CompletionService 
103105from  mcpgateway .services .export_service  import  ExportError , ExportService 
104106from  mcpgateway .services .gateway_service  import  GatewayConnectionError , GatewayError , GatewayNameConflictError , GatewayNotFoundError , GatewayService , GatewayUrlConflictError 
105- from  mcpgateway .services .import_service  import  ConflictStrategy , ImportConflictError 
107+ from  mcpgateway .services .import_service  import  ConflictStrategy , ImportConflictError ,  ImportService ,  ImportValidationError 
106108from  mcpgateway .services .import_service  import  ImportError  as  ImportServiceError 
107- from  mcpgateway .services .import_service  import  ImportService , ImportValidationError 
108109from  mcpgateway .services .logging_service  import  LoggingService 
109110from  mcpgateway .services .prompt_service  import  PromptError , PromptNameConflictError , PromptNotFoundError , PromptService 
110111from  mcpgateway .services .resource_service  import  ResourceError , ResourceNotFoundError , ResourceService , ResourceURIConflictError 
@@ -264,6 +265,75 @@ def get_user_email(user):
264265resource_cache  =  ResourceCache (max_size = settings .resource_cache_size , ttl = settings .resource_cache_ttl )
265266
266267
268+ def  jsonpath_modifier (data : Any , jsonpath : str  =  "$[*]" , mappings : Optional [Dict [str , str ]] =  None ) ->  Union [List , Dict ]:
269+     """ 
270+     Applies the given JSONPath expression and mappings to the data. 
271+     Only return data that is required by the user dynamically. 
272+ 
273+     Args: 
274+         data: The JSON data to query. 
275+         jsonpath: The JSONPath expression to apply. 
276+         mappings: Optional dictionary of mappings where keys are new field names 
277+                   and values are JSONPath expressions. 
278+ 
279+     Returns: 
280+         Union[List, Dict]: A list (or mapped list) or a Dict of extracted data. 
281+ 
282+     Raises: 
283+         HTTPException: If there's an error parsing or executing the JSONPath expressions. 
284+ 
285+     Examples: 
286+         >>> jsonpath_modifier({'a': 1, 'b': 2}, '$.a') 
287+         [1] 
288+         >>> jsonpath_modifier([{'a': 1}, {'a': 2}], '$[*].a') 
289+         [1, 2] 
290+         >>> jsonpath_modifier({'a': {'b': 2}}, '$.a.b') 
291+         [2] 
292+         >>> jsonpath_modifier({'a': 1}, '$.b') 
293+         [] 
294+     """ 
295+     if  not  jsonpath :
296+         jsonpath  =  "$[*]" 
297+ 
298+     try :
299+         main_expr : JSONPath  =  parse (jsonpath )
300+     except  Exception  as  e :
301+         raise  HTTPException (status_code = 400 , detail = f"Invalid main JSONPath expression: { e }  )
302+ 
303+     try :
304+         main_matches  =  main_expr .find (data )
305+     except  Exception  as  e :
306+         raise  HTTPException (status_code = 400 , detail = f"Error executing main JSONPath: { e }  )
307+ 
308+     results  =  [match .value  for  match  in  main_matches ]
309+ 
310+     if  mappings :
311+         mapped_results  =  []
312+         for  item  in  results :
313+             mapped_item  =  {}
314+             for  new_key , mapping_expr_str  in  mappings .items ():
315+                 try :
316+                     mapping_expr  =  parse (mapping_expr_str )
317+                 except  Exception  as  e :
318+                     raise  HTTPException (status_code = 400 , detail = f"Invalid mapping JSONPath for key '{ new_key } { e }  )
319+                 try :
320+                     mapping_matches  =  mapping_expr .find (item )
321+                 except  Exception  as  e :
322+                     raise  HTTPException (status_code = 400 , detail = f"Error executing mapping JSONPath for key '{ new_key } { e }  )
323+                 if  not  mapping_matches :
324+                     mapped_item [new_key ] =  None 
325+                 elif  len (mapping_matches ) ==  1 :
326+                     mapped_item [new_key ] =  mapping_matches [0 ].value 
327+                 else :
328+                     mapped_item [new_key ] =  [m .value  for  m  in  mapping_matches ]
329+             mapped_results .append (mapped_item )
330+         results  =  mapped_results 
331+ 
332+     if  len (results ) ==  1  and  isinstance (results [0 ], dict ):
333+         return  results [0 ]
334+     return  results 
335+ 
336+ 
267337#################### 
268338# Startup/Shutdown # 
269339#################### 
@@ -432,7 +502,7 @@ async def validate_security_configuration():
432502    if  settings .jwt_secret_key  ==  "my-test-key"  and  not  settings .dev_mode :  # nosec B105 - checking for default value 
433503        critical_issues .append ("Using default JWT secret in non-dev mode. Set JWT_SECRET_KEY environment variable!" )
434504
435-     if  settings .basic_auth_password  ==  "changeme"  and  settings .mcpgateway_ui_enabled :  # nosec B105 - checking for default value 
505+     if  settings .basic_auth_password . get_secret_value ()  ==  "changeme"  and  settings .mcpgateway_ui_enabled :  # nosec B105 - checking for default value 
436506        critical_issues .append ("Admin UI enabled with default password. Set BASIC_AUTH_PASSWORD environment variable!" )
437507
438508    if  not  settings .auth_required  and  settings .federation_enabled  and  not  settings .dev_mode :
@@ -469,7 +539,7 @@ async def validate_security_configuration():
469539            logger .info ("  • Generate a strong JWT secret:" )
470540            logger .info ("    python3 -c 'import secrets; print(secrets.token_urlsafe(32))'" )
471541
472-         if  settings .basic_auth_password  ==  "changeme" :  # nosec B105 - checking for default value 
542+         if  settings .basic_auth_password . get_secret_value ()  ==  "changeme" :  # nosec B105 - checking for default value 
473543            logger .info ("  • Set a strong admin password in BASIC_AUTH_PASSWORD" )
474544
475545        if  not  settings .auth_required :
@@ -1011,9 +1081,10 @@ def require_api_key(api_key: str) -> None:
10111081
10121082    Examples: 
10131083        >>> from mcpgateway.config import settings 
1084+         >>> from pydantic import SecretStr 
10141085        >>> settings.auth_required = True 
10151086        >>> settings.basic_auth_user = "admin" 
1016-         >>> settings.basic_auth_password = "secret" 
1087+         >>> settings.basic_auth_password = SecretStr( "secret")  
10171088        >>> 
10181089        >>> # Valid API key 
10191090        >>> require_api_key("admin:secret")  # Should not raise 
@@ -1026,7 +1097,7 @@ def require_api_key(api_key: str) -> None:
10261097        401 
10271098    """ 
10281099    if  settings .auth_required :
1029-         expected  =  f"{ settings .basic_auth_user } { settings .basic_auth_password }  
1100+         expected  =  f"{ settings .basic_auth_user } { settings .basic_auth_password . get_secret_value () }  
10301101        if  api_key  !=  expected :
10311102            raise  HTTPException (status_code = status .HTTP_401_UNAUTHORIZED , detail = "Invalid API key" )
10321103
@@ -2623,8 +2694,10 @@ async def read_resource(resource_id: str, request: Request, db: Session = Depend
26232694    # Ensure a plain JSON-serializable structure 
26242695    try :
26252696        # First-Party 
2626-         from  mcpgateway .models  import  ResourceContent   # pylint: disable=import-outside-toplevel 
2627-         from  mcpgateway .models  import  TextContent   # pylint: disable=import-outside-toplevel 
2697+         from  mcpgateway .models  import  (
2698+             ResourceContent ,  # pylint: disable=import-outside-toplevel 
2699+             TextContent ,  # pylint: disable=import-outside-toplevel 
2700+         )
26282701
26292702        # If already a ResourceContent, serialize directly 
26302703        if  isinstance (content , ResourceContent ):
0 commit comments