diff --git a/surfsense_backend/app/routes/search_source_connectors_routes.py b/surfsense_backend/app/routes/search_source_connectors_routes.py index b241aa2fb..a813b2cc2 100644 --- a/surfsense_backend/app/routes/search_source_connectors_routes.py +++ b/surfsense_backend/app/routes/search_source_connectors_routes.py @@ -81,6 +81,7 @@ from app.utils.periodic_scheduler import ( delete_periodic_schedule, update_periodic_schedule, ) +from app.utils.connector_naming import ensure_unique_connector_name from app.utils.rbac import check_permission # Set up logging @@ -189,6 +190,12 @@ async def create_search_source_connector( # Prepare connector data connector_data = connector.model_dump() + # MCP connectors support multiple instances — ensure unique name + if connector.connector_type == SearchSourceConnectorType.MCP_CONNECTOR: + connector_data["name"] = await ensure_unique_connector_name( + session, connector_data["name"], search_space_id, user.id + ) + # Automatically set next_scheduled_at if periodic indexing is enabled if ( connector.periodic_indexing_enabled @@ -2715,9 +2722,14 @@ async def create_mcp_connector( "You don't have permission to create connectors in this search space", ) + # Ensure unique name across MCP connectors in this search space + unique_name = await ensure_unique_connector_name( + session, connector_data.name, search_space_id, user.id + ) + # Create the connector with single server config db_connector = SearchSourceConnector( - name=connector_data.name, + name=unique_name, connector_type=SearchSourceConnectorType.MCP_CONNECTOR, is_indexable=False, # MCP connectors are not indexable config={"server_config": connector_data.server_config.model_dump()}, diff --git a/surfsense_backend/app/utils/connector_naming.py b/surfsense_backend/app/utils/connector_naming.py index 7d3efc001..9fdec3e79 100644 --- a/surfsense_backend/app/utils/connector_naming.py +++ b/surfsense_backend/app/utils/connector_naming.py @@ -159,6 +159,44 @@ async def check_duplicate_connector( return (result.scalar() or 0) > 0 +async def ensure_unique_connector_name( + session: AsyncSession, + name: str, + search_space_id: int, + user_id: UUID, +) -> str: + """ + Ensure a connector name is unique within a user's search space. + + If the name already exists, appends a counter suffix: (2), (3), etc. + Uses the same suffix format as generate_unique_connector_name. + + Args: + session: Database session + name: Desired connector name + search_space_id: The search space ID + user_id: The user ID + + Returns: + Unique name, either the original or with a counter suffix + """ + result = await session.execute( + select(SearchSourceConnector.name).where( + SearchSourceConnector.search_space_id == search_space_id, + SearchSourceConnector.user_id == user_id, + ) + ) + existing_names = {row[0] for row in result.all()} + + if name not in existing_names: + return name + + counter = 2 + while f"{name} ({counter})" in existing_names: + counter += 1 + return f"{name} ({counter})" + + async def generate_unique_connector_name( session: AsyncSession, connector_type: SearchSourceConnectorType,