Skip to content

Commit 531c0c7

Browse files
feat(admin): add gateway filtering and fix selection persistence (#1561)
Add gateway-based filtering support to Admin search endpoints for Tools, Resources, and Prompts. This allows filtering by one or more gateway IDs, including items with no gateway (NULL). API Changes: - Add optional gateway_id query parameter to admin_search_tools, admin_search_resources, and admin_search_prompts endpoints - Support filtering by multiple gateway IDs (comma-separated) - Support filtering for NULL gateway using literal 'null' string UI Changes: - Update gateway filter label from 'REST' to 'REST/A2A' for clarity - Fix tool/resource/prompt selection persistence on Add/Edit Virtual Server pages when changing filters or clearing search Technical Details: - Store selections in data-selected-* attributes and window fallbacks - Restore selections after HTMX content replacement - Fix parseInt usage for UUID values (use string comparison instead) - Pass gateway_id parameter through all search API calls Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
1 parent b028857 commit 531c0c7

File tree

3 files changed

+1896
-239
lines changed

3 files changed

+1896
-239
lines changed

mcpgateway/admin.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5853,6 +5853,7 @@ async def admin_search_tools(
58535853
q: str = Query("", description="Search query"),
58545854
include_inactive: bool = False,
58555855
limit: int = Query(100, ge=1, le=1000, description="Maximum number of results to return"),
5856+
gateway_id: Optional[str] = Query(None, description="Filter by gateway ID(s), comma-separated"),
58565857
db: Session = Depends(get_db),
58575858
user=Depends(get_current_user_with_permissions),
58585859
):
@@ -5866,6 +5867,7 @@ async def admin_search_tools(
58665867
q (str): Search query string to match against tool names, IDs, or descriptions
58675868
include_inactive (bool): Whether to include inactive tools in the search results
58685869
limit (int): Maximum number of results to return (1-1000)
5870+
gateway_id (Optional[str]): Filter by gateway ID(s), comma-separated
58695871
db (Session): Database session dependency
58705872
user: Current user making the request
58715873

@@ -5886,6 +5888,24 @@ async def admin_search_tools(
58865888

58875889
query = select(DbTool.id, DbTool.original_name, DbTool.custom_name, DbTool.display_name, DbTool.description)
58885890

5891+
# Apply gateway filter if provided. Support special sentinel 'null' to
5892+
# request tools with NULL gateway_id (e.g., RestTool/no gateway).
5893+
if gateway_id:
5894+
gateway_ids = [gid.strip() for gid in gateway_id.split(",") if gid.strip()]
5895+
if gateway_ids:
5896+
# Treat literal 'null' (case-insensitive) as a request for NULL gateway_id
5897+
null_requested = any(gid.lower() == "null" for gid in gateway_ids)
5898+
non_null_ids = [gid for gid in gateway_ids if gid.lower() != "null"]
5899+
if non_null_ids and null_requested:
5900+
query = query.where(or_(DbTool.gateway_id.in_(non_null_ids), DbTool.gateway_id.is_(None)))
5901+
LOGGER.debug(f"Filtering tool search by gateway IDs (including NULL): {non_null_ids} + NULL")
5902+
elif null_requested:
5903+
query = query.where(DbTool.gateway_id.is_(None))
5904+
LOGGER.debug("Filtering tool search by NULL gateway_id (RestTool)")
5905+
else:
5906+
query = query.where(DbTool.gateway_id.in_(non_null_ids))
5907+
LOGGER.debug(f"Filtering tool search by gateway IDs: {non_null_ids}")
5908+
58895909
if not include_inactive:
58905910
query = query.where(DbTool.enabled.is_(True))
58915911

@@ -6396,6 +6416,7 @@ async def admin_search_resources(
63966416
q: str = Query("", description="Search query"),
63976417
include_inactive: bool = False,
63986418
limit: int = Query(100, ge=1, le=1000),
6419+
gateway_id: Optional[str] = Query(None, description="Filter by gateway ID(s), comma-separated"),
63996420
db: Session = Depends(get_db),
64006421
user=Depends(get_current_user_with_permissions),
64016422
):
@@ -6409,6 +6430,7 @@ async def admin_search_resources(
64096430
q (str): Search query string.
64106431
include_inactive (bool): When True include resources that are inactive.
64116432
limit (int): Maximum number of results to return (bounded by the query parameter).
6433+
gateway_id (Optional[str]): Filter by gateway ID(s), comma-separated.
64126434
db (Session): Database session (injected dependency).
64136435
user: Authenticated user object from dependency injection.
64146436

@@ -6427,6 +6449,23 @@ async def admin_search_resources(
64276449
team_ids = [t.id for t in user_teams]
64286450

64296451
query = select(DbResource.id, DbResource.name, DbResource.description)
6452+
6453+
# Apply gateway filter if provided
6454+
if gateway_id:
6455+
gateway_ids = [gid.strip() for gid in gateway_id.split(",") if gid.strip()]
6456+
if gateway_ids:
6457+
null_requested = any(gid.lower() == "null" for gid in gateway_ids)
6458+
non_null_ids = [gid for gid in gateway_ids if gid.lower() != "null"]
6459+
if non_null_ids and null_requested:
6460+
query = query.where(or_(DbResource.gateway_id.in_(non_null_ids), DbResource.gateway_id.is_(None)))
6461+
LOGGER.debug(f"Filtering resource search by gateway IDs (including NULL): {non_null_ids} + NULL")
6462+
elif null_requested:
6463+
query = query.where(DbResource.gateway_id.is_(None))
6464+
LOGGER.debug("Filtering resource search by NULL gateway_id")
6465+
else:
6466+
query = query.where(DbResource.gateway_id.in_(non_null_ids))
6467+
LOGGER.debug(f"Filtering resource search by gateway IDs: {non_null_ids}")
6468+
64306469
if not include_inactive:
64316470
query = query.where(DbResource.enabled.is_(True))
64326471

@@ -6459,6 +6498,7 @@ async def admin_search_prompts(
64596498
q: str = Query("", description="Search query"),
64606499
include_inactive: bool = False,
64616500
limit: int = Query(100, ge=1, le=1000),
6501+
gateway_id: Optional[str] = Query(None, description="Filter by gateway ID(s), comma-separated"),
64626502
db: Session = Depends(get_db),
64636503
user=Depends(get_current_user_with_permissions),
64646504
):
@@ -6472,6 +6512,7 @@ async def admin_search_prompts(
64726512
q (str): Search query string.
64736513
include_inactive (bool): When True include prompts that are inactive.
64746514
limit (int): Maximum number of results to return (bounded by the query parameter).
6515+
gateway_id (Optional[str]): Filter by gateway ID(s), comma-separated.
64756516
db (Session): Database session (injected dependency).
64766517
user: Authenticated user object from dependency injection.
64776518

@@ -6490,6 +6531,23 @@ async def admin_search_prompts(
64906531
team_ids = [t.id for t in user_teams]
64916532

64926533
query = select(DbPrompt.id, DbPrompt.name, DbPrompt.description)
6534+
6535+
# Apply gateway filter if provided
6536+
if gateway_id:
6537+
gateway_ids = [gid.strip() for gid in gateway_id.split(",") if gid.strip()]
6538+
if gateway_ids:
6539+
null_requested = any(gid.lower() == "null" for gid in gateway_ids)
6540+
non_null_ids = [gid for gid in gateway_ids if gid.lower() != "null"]
6541+
if non_null_ids and null_requested:
6542+
query = query.where(or_(DbPrompt.gateway_id.in_(non_null_ids), DbPrompt.gateway_id.is_(None)))
6543+
LOGGER.debug(f"Filtering prompt search by gateway IDs (including NULL): {non_null_ids} + NULL")
6544+
elif null_requested:
6545+
query = query.where(DbPrompt.gateway_id.is_(None))
6546+
LOGGER.debug("Filtering prompt search by NULL gateway_id")
6547+
else:
6548+
query = query.where(DbPrompt.gateway_id.in_(non_null_ids))
6549+
LOGGER.debug(f"Filtering prompt search by gateway IDs: {non_null_ids}")
6550+
64936551
if not include_inactive:
64946552
query = query.where(DbPrompt.enabled.is_(True))
64956553

0 commit comments

Comments
 (0)