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,8 @@ 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+ # pylint: disable=import-outside-toplevel
2698+ from mcpgateway .models import ResourceContent , TextContent
26282699
26292700 # If already a ResourceContent, serialize directly
26302701 if isinstance (content , ResourceContent ):
0 commit comments