diff --git a/src/solace_agent_mesh/agent/adk/setup.py b/src/solace_agent_mesh/agent/adk/setup.py index 53d83cb0b..f6bacf969 100644 --- a/src/solace_agent_mesh/agent/adk/setup.py +++ b/src/solace_agent_mesh/agent/adk/setup.py @@ -5,6 +5,7 @@ import functools import inspect import logging +import os from typing import TYPE_CHECKING, Any, Callable, Dict, List, Optional, Set, Tuple, Type, Union from google.adk import tools as adk_tools_module @@ -501,6 +502,35 @@ async def _load_builtin_group_tool(component: "SamAgentComponent", tool_config: ) return loaded_tools, enabled_builtin_tools, [] +def validate_filesystem_path(path, log_identifier=""): + """ + Validates that a filesystem path exists and is accessible. + + Args: + path: The filesystem path to validate + log_identifier: Optional identifier for logging + + Returns: + bool: True if the path exists and is accessible, False otherwise + + Raises: + ValueError: If the path doesn't exist or isn't accessible + """ + if not path: + raise ValueError(f"{log_identifier} Filesystem path is empty or None") + + if not os.path.exists(path): + raise ValueError(f"{log_identifier} Filesystem path does not exist: {path}") + + if not os.path.isdir(path): + raise ValueError(f"{log_identifier} Filesystem path is not a directory: {path}") + + # Check if the directory is readable and writable + if not os.access(path, os.R_OK | os.W_OK): + raise ValueError(f"{log_identifier} Filesystem path is not readable and writable: {path}") + + return True + async def _load_mcp_tool(component: "SamAgentComponent", tool_config: Dict) -> ToolLoadingResult: """Loads an MCP toolset based on connection parameters.""" from pydantic import TypeAdapter @@ -548,6 +578,31 @@ async def _load_mcp_tool(component: "SamAgentComponent", tool_config: Dict) -> T raise ValueError( f"MCP tool 'args' parameter must be a list, got {type(args_list)}" ) + + # Check if this is the filesystem MCP server + if args_list and any("@modelcontextprotocol/server-filesystem" in arg for arg in args_list): + # Find the index of the server-filesystem argument + server_fs_index = -1 + for i, arg in enumerate(args_list): + if "@modelcontextprotocol/server-filesystem" in arg: + server_fs_index = i + break + + # All arguments after server-filesystem are directory paths + if server_fs_index >= 0 and server_fs_index + 1 < len(args_list): + directory_paths = args_list[server_fs_index + 1:] + + for path in directory_paths: + try: + validate_filesystem_path(path, log_identifier=component.log_identifier) + log.info( + "%s Validated filesystem path for MCP server: %s", + component.log_identifier, + path + ) + except ValueError as e: + log.error("%s", str(e)) + raise ValueError(f"MCP filesystem server path validation failed: {e}") final_connection_args = { k: v for k, v in connection_args.items() diff --git a/src/solace_agent_mesh/agent/sac/component.py b/src/solace_agent_mesh/agent/sac/component.py index 8dd7bd8b6..012a65a04 100644 --- a/src/solace_agent_mesh/agent/sac/component.py +++ b/src/solace_agent_mesh/agent/sac/component.py @@ -416,28 +416,6 @@ def __init__(self, **kwargs): # We still need a future to signal completion from the async thread. self._async_init_future = concurrent.futures.Future() - publish_interval_sec = self.agent_card_publishing_config.get( - "interval_seconds" - ) - if publish_interval_sec and publish_interval_sec > 0: - log.info( - "%s Scheduling agent card publishing every %d seconds.", - self.log_identifier, - publish_interval_sec, - ) - # Register timer with callback - self.add_timer( - delay_ms=1000, - timer_id=self._card_publish_timer_id, - interval_ms=publish_interval_sec * 1000, - callback=lambda timer_data: publish_agent_card(self), - ) - else: - log.warning( - "%s Agent card publishing interval not configured or invalid, card will not be published periodically.", - self.log_identifier, - ) - # Set up health check timer if enabled health_check_interval_seconds = self.agent_discovery_config.get( "health_check_interval_seconds", HEALTH_CHECK_INTERVAL_SECONDS @@ -3160,6 +3138,7 @@ async def _perform_async_init(self): "%s _perform_async_init: _async_init_future is None or already done before signaling failure.", self.log_identifier, ) + raise e def cleanup(self): """Clean up resources on component shutdown.""" @@ -3551,16 +3530,62 @@ async def _resolve_early_embeds_and_handle_signals( ) return raw_text, [], "" + def _publish_agent_card(self) -> None: + """ + Schedules periodic publishing of the agent card based on configuration. + """ + try: + publish_interval_sec = self.agent_card_publishing_config.get( + "interval_seconds" + ) + if publish_interval_sec and publish_interval_sec > 0: + log.info( + "%s Scheduling agent card publishing every %d seconds.", + self.log_identifier, + publish_interval_sec, + ) + # Register timer with callback + self.add_timer( + delay_ms=1000, + timer_id=self._card_publish_timer_id, + interval_ms=publish_interval_sec * 1000, + callback=lambda timer_data: publish_agent_card(self), + ) + else: + log.warning( + "%s Agent card publishing interval not configured or invalid, card will not be published periodically.", + self.log_identifier, + ) + except Exception as e: + log.exception( + "%s Error during _publish_agent_card setup: %s", + self.log_identifier, + e, + ) + raise e + async def _async_setup_and_run(self) -> None: """ Main async logic for the agent component. This is called by the base class's `_run_async_operations`. """ - # Call base class to initialize Trust Manager - await super()._async_setup_and_run() + try: + # Call base class to initialize Trust Manager + await super()._async_setup_and_run() - # Perform agent-specific async initialization - await self._perform_async_init() + # Perform agent-specific async initialization + await self._perform_async_init() + + self._publish_agent_card() + + except Exception as e: + log.exception( + "%s Error during _async_setup_and_run: %s", + self.log_identifier, + e, + ) + self.cleanup() + raise e def _pre_async_cleanup(self) -> None: """