55import asyncio
66import concurrent .futures
77import threading
8+ import warnings
89from abc import ABC , abstractmethod
910from collections .abc import Coroutine
1011from contextlib import AsyncExitStack
1617from haystack .core .serialization import generate_qualified_class_name , import_class_by_name
1718from haystack .tools import Tool
1819from haystack .tools .errors import ToolInvocationError
20+ from haystack .utils .url_validation import is_valid_http_url
1921
2022from mcp import ClientSession , StdioServerParameters , types
2123from mcp .client .sse import sse_client
@@ -351,18 +353,19 @@ class SSEClient(MCPClient):
351353 MCP client that connects to servers using SSE transport.
352354 """
353355
354- def __init__ (self , base_url : str , token : str | None = None , timeout : int = 5 ) -> None :
356+ def __init__ (self , server_info : "SSEServerInfo" ) -> None :
355357 """
356- Initialize an SSE MCP client.
358+ Initialize an SSE MCP client using server configuration .
357359
358- :param base_url: Base URL of the server
359- :param token: Authentication token for the server (optional)
360- :param timeout: Connection timeout in seconds
360+ :param server_info: Configuration object containing URL, token, timeout, etc.
361361 """
362362 super ().__init__ ()
363- self .base_url : str = base_url .rstrip ("/" ) # Remove any trailing slashes
364- self .token : str | None = token
365- self .timeout : int = timeout
363+
364+ # in post_init we validate the url and set the url field so it is guaranteed to be valid
365+ # safely ignore the mypy warning here
366+ self .url : str = server_info .url # type: ignore[assignment]
367+ self .token : str | None = server_info .token
368+ self .timeout : int = server_info .timeout
366369
367370 async def connect (self ) -> list [Tool ]:
368371 """
@@ -371,12 +374,11 @@ async def connect(self) -> list[Tool]:
371374 :returns: List of available tools on the server
372375 :raises MCPConnectionError: If connection to the server fails
373376 """
374- sse_url = f"{ self .base_url } /sse"
375377 headers = {"Authorization" : f"Bearer { self .token } " } if self .token else None
376378 sse_transport = await self .exit_stack .enter_async_context (
377- sse_client (sse_url , headers = headers , timeout = self .timeout )
379+ sse_client (self . url , headers = headers , timeout = self .timeout )
378380 )
379- return await self ._initialize_session_with_transport (sse_transport , f"HTTP server at { self .base_url } " )
381+ return await self ._initialize_session_with_transport (sse_transport , f"HTTP server at { self .url } " )
380382
381383
382384@dataclass
@@ -432,22 +434,51 @@ class SSEServerInfo(MCPServerInfo):
432434 """
433435 Data class that encapsulates SSE MCP server connection parameters.
434436
435- :param base_url: Base URL of the MCP server
437+ :param url: Full URL of the MCP server (including /sse endpoint)
438+ :param base_url: Base URL of the MCP server (deprecated, use url instead)
436439 :param token: Authentication token for the server (optional)
437440 :param timeout: Connection timeout in seconds
438441 """
439442
440- base_url : str
443+ url : str | None = None
444+ base_url : str | None = None # deprecated
441445 token : str | None = None
442446 timeout : int = 30
443447
448+ def __post_init__ (self ):
449+ """Validate that either url or base_url is provided."""
450+ if not self .url and not self .base_url :
451+ message = "Either url or base_url must be provided"
452+ raise ValueError (message )
453+ if self .url and self .base_url :
454+ message = "Only one of url or base_url should be provided, if both are provided, base_url will be ignored"
455+ warnings .warn (message , DeprecationWarning , stacklevel = 2 )
456+
457+ if self .base_url :
458+ if not is_valid_http_url (self .base_url ):
459+ message = f"Invalid base_url: { self .base_url } "
460+ raise ValueError (message )
461+
462+ warnings .warn (
463+ "base_url is deprecated and will be removed in a future version. Use url instead." ,
464+ DeprecationWarning ,
465+ stacklevel = 2 ,
466+ )
467+ # from now on only use url for the lifetime of the SSEServerInfo instance, never base_url
468+ self .url = f"{ self .base_url .rstrip ('/' )} /sse"
469+
470+ elif not is_valid_http_url (self .url ):
471+ message = f"Invalid url: { self .url } "
472+ raise ValueError (message )
473+
444474 def create_client (self ) -> MCPClient :
445475 """
446476 Create an SSE MCP client.
447477
448- :returns: Configured HttpMCPClient instance
478+ :returns: Configured MCPClient instance
449479 """
450- return SSEClient (self .base_url , self .token , self .timeout )
480+ # Pass the validated SSEServerInfo instance directly
481+ return SSEClient (server_info = self )
451482
452483
453484@dataclass
@@ -491,7 +522,7 @@ class MCPTool(Tool):
491522 # Create tool instance
492523 tool = MCPTool(
493524 name="add",
494- server_info=SSEServerInfo(base_url ="http://localhost:8000")
525+ server_info=SSEServerInfo(url ="http://localhost:8000/sse ")
495526 )
496527
497528 # Use the tool
0 commit comments