3131 CatalogServerStatusResponse ,
3232)
3333from mcpgateway .services .gateway_service import GatewayService
34+ from mcpgateway .utils .create_slug import slugify
3435
3536logger = logging .getLogger (__name__ )
3637
@@ -120,6 +121,9 @@ async def get_catalog_servers(self, request: CatalogListRequest, db) -> CatalogL
120121 for server_data in servers :
121122 server = CatalogServer (** server_data )
122123 server .is_registered = server .url in registered_urls
124+ # Set availability based on registration status (registered servers are assumed available)
125+ # Individual health checks can be done via the /status endpoint
126+ server .is_available = server .is_registered or server_data .get ("is_available" , True )
123127 catalog_servers .append (server )
124128
125129 # Apply filters
@@ -206,18 +210,22 @@ async def register_catalog_server(self, catalog_id: str, request: Optional[Catal
206210 # First-Party
207211 from mcpgateway .schemas import GatewayCreate # pylint: disable=import-outside-toplevel
208212
209- # Detect transport type from URL or use SSE as default
210- url = server_data ["url" ].lower ()
211- # Check for SSE patterns (highest priority)
212- if url .endswith ("/sse" ) or "/sse/" in url :
213- transport = "SSE" # SSE endpoints or paths containing /sse/
214- elif url .startswith ("ws://" ) or url .startswith ("wss://" ):
215- transport = "SSE" # WebSocket URLs typically use SSE transport
216- # Then check for HTTP patterns
217- elif "/mcp" in url or url .endswith ("/" ):
218- transport = "STREAMABLEHTTP" # Generic MCP endpoints typically use HTTP
219- else :
220- transport = "SSE" # Default to SSE for most catalog servers
213+ # Use explicit transport if provided, otherwise auto-detect from URL
214+ transport = server_data .get ("transport" )
215+ if not transport :
216+ # Detect transport type from URL or use SSE as default
217+ url = server_data ["url" ].lower ()
218+ # Check for WebSocket patterns (highest priority)
219+ if url .startswith ("ws://" ) or url .startswith ("wss://" ):
220+ transport = "WEBSOCKET" # WebSocket transport for ws:// and wss:// URLs
221+ # Check for SSE patterns
222+ elif url .endswith ("/sse" ) or "/sse/" in url :
223+ transport = "SSE" # SSE endpoints or paths containing /sse/
224+ # Then check for HTTP patterns
225+ elif "/mcp" in url or url .endswith ("/" ):
226+ transport = "STREAMABLEHTTP" # Generic MCP endpoints typically use HTTP
227+ else :
228+ transport = "SSE" # Default to SSE for most catalog servers
221229
222230 # Check for IPv6 URLs early to provide a clear error message
223231 url = server_data ["url" ]
@@ -237,6 +245,8 @@ async def register_catalog_server(self, catalog_id: str, request: Optional[Catal
237245
238246 # Set authentication based on server requirements
239247 auth_type = server_data .get ("auth_type" , "Open" )
248+ skip_initialization = False # Flag to skip connection test for OAuth servers without creds
249+
240250 if request and request .api_key and auth_type != "Open" :
241251 # Handle all possible auth types from the catalog
242252 if auth_type in ["API Key" , "API" ]:
@@ -248,10 +258,54 @@ async def register_catalog_server(self, catalog_id: str, request: Optional[Catal
248258 gateway_data ["auth_type" ] = "bearer"
249259 gateway_data ["auth_token" ] = request .api_key
250260 else :
251- # For any other auth types, use custom headers
261+ # For any other auth types, use custom headers (as list of dicts)
252262 gateway_data ["auth_type" ] = "authheaders"
253- gateway_data ["auth_header_key" ] = "X-API-Key"
254- gateway_data ["auth_header_value" ] = request .api_key
263+ gateway_data ["auth_headers" ] = [{"key" : "X-API-Key" , "value" : request .api_key }]
264+ elif auth_type in ["OAuth2.1" , "OAuth" ]:
265+ # OAuth server without credentials - register but skip initialization
266+ # User will need to complete OAuth flow later
267+ skip_initialization = True
268+ logger .info (f"Registering OAuth server { server_data ['name' ]} without credentials - OAuth flow required later" )
269+
270+ # For OAuth servers without credentials, register directly without connection test
271+ if skip_initialization :
272+ # Create minimal gateway entry without tool discovery
273+ # First-Party
274+ from mcpgateway .db import Gateway as DbGateway # pylint: disable=import-outside-toplevel
275+
276+ gateway_create = GatewayCreate (** gateway_data )
277+ slug_name = slugify (gateway_data ["name" ])
278+
279+ db_gateway = DbGateway (
280+ name = gateway_data ["name" ],
281+ slug = slug_name ,
282+ url = gateway_data ["url" ],
283+ description = gateway_data ["description" ],
284+ tags = gateway_data .get ("tags" , []),
285+ transport = gateway_data ["transport" ],
286+ capabilities = {},
287+ auth_type = None , # Will be set during OAuth configuration
288+ enabled = False , # Disabled until OAuth is configured
289+ created_via = "catalog" ,
290+ visibility = "public" ,
291+ version = 1 ,
292+ )
293+
294+ db .add (db_gateway )
295+ db .commit ()
296+ db .refresh (db_gateway )
297+
298+ # First-Party
299+ from mcpgateway .schemas import GatewayRead # pylint: disable=import-outside-toplevel
300+
301+ gateway_read = GatewayRead .model_validate (db_gateway )
302+
303+ return CatalogServerRegisterResponse (
304+ success = True ,
305+ server_id = str (gateway_read .id ),
306+ message = f"Successfully registered { gateway_read .name } - OAuth configuration required before activation" ,
307+ error = None ,
308+ )
255309
256310 gateway_create = GatewayCreate (** gateway_data )
257311
@@ -284,9 +338,31 @@ async def register_catalog_server(self, catalog_id: str, request: Optional[Catal
284338
285339 except Exception as e :
286340 logger .error (f"Failed to register catalog server { catalog_id } : { e } " )
341+
342+ # Map common exceptions to user-friendly messages
343+ error_str = str (e )
344+ user_message = "Registration failed"
345+
346+ if "Connection refused" in error_str or "connect" in error_str .lower ():
347+ user_message = "Server is offline or unreachable"
348+ elif "SSL" in error_str or "certificate" in error_str .lower ():
349+ user_message = "SSL certificate verification failed - check server security settings"
350+ elif "timeout" in error_str .lower () or "timed out" in error_str .lower ():
351+ user_message = "Server took too long to respond - it may be slow or unavailable"
352+ elif "401" in error_str or "Unauthorized" in error_str :
353+ user_message = "Authentication failed - check API key or OAuth credentials"
354+ elif "403" in error_str or "Forbidden" in error_str :
355+ user_message = "Access forbidden - check permissions and API key"
356+ elif "404" in error_str or "Not Found" in error_str :
357+ user_message = "Server endpoint not found - check URL is correct"
358+ elif "500" in error_str or "Internal Server Error" in error_str :
359+ user_message = "Remote server error - the MCP server is experiencing issues"
360+ elif "IPv6" in error_str :
361+ user_message = "IPv6 URLs are not supported - please use IPv4 or domain names"
362+
287363 # Don't rollback here - let FastAPI handle it
288364 # db.rollback()
289- return CatalogServerRegisterResponse (success = False , server_id = "" , message = "Registration failed" , error = str ( e ) )
365+ return CatalogServerRegisterResponse (success = False , server_id = "" , message = user_message , error = error_str )
290366
291367 async def check_server_availability (self , catalog_id : str ) -> CatalogServerStatusResponse :
292368 """Check if a catalog server is available.
0 commit comments