From 98e4233768fe9713fb44e34fc561b527f206c144 Mon Sep 17 00:00:00 2001 From: avinash Date: Tue, 16 Sep 2025 17:34:14 -0700 Subject: [PATCH 1/8] add support for google ADK --- scalekit/actions/frameworks/google_adk.py | 121 +++++++++ scalekit/actions/frameworks/langchain.py | 13 +- scalekit/actions/frameworks/types/__init__.py | 13 + .../frameworks/types/google_adk_tool.py | 251 ++++++++++++++++++ scalekit/actions/frameworks/util.py | 44 +++ setup.py | 2 +- 6 files changed, 432 insertions(+), 12 deletions(-) create mode 100644 scalekit/actions/frameworks/google_adk.py create mode 100644 scalekit/actions/frameworks/types/__init__.py create mode 100644 scalekit/actions/frameworks/types/google_adk_tool.py create mode 100644 scalekit/actions/frameworks/util.py diff --git a/scalekit/actions/frameworks/google_adk.py b/scalekit/actions/frameworks/google_adk.py new file mode 100644 index 0000000..a46f5c2 --- /dev/null +++ b/scalekit/actions/frameworks/google_adk.py @@ -0,0 +1,121 @@ +from typing import Optional, Any, Dict, List, Callable +from scalekit.tools import ToolsClient +from scalekit.v1.tools.tools_pb2 import Filter, ScopedToolFilter +from scalekit.actions.frameworks.types.google_adk_tool import ( + create_scalekit_google_adk_tool, + ScalekitGoogleAdkTool, + check_google_adk_availability, + get_missing_dependencies +) +from scalekit.actions.frameworks.util import extract_tool_metadata, convert_to_mcp_input_schema + + +class GoogleADK: + def __init__(self, tools_client: ToolsClient, execute_callback: Callable): + if not execute_callback: + raise ValueError("execute_callback is required. GoogleADK must be initialized with ActionClient's execute_tool method.") + + self.tools = tools_client + self.execute_callback = execute_callback + self._availability_checked = False + self._is_available = False + + def is_available(self) -> bool: + """ + Check if Google ADK dependencies are available + + :returns: True if all Google ADK dependencies are installed, False otherwise + """ + if not self._availability_checked: + self._is_available = check_google_adk_availability() + self._availability_checked = True + return self._is_available + + def get_missing_dependencies(self) -> Dict[str, str]: + """ + Get information about missing Google ADK dependencies + + :returns: Dictionary mapping dependency names to installation commands + """ + return get_missing_dependencies() + + def get_tools( + self, + identifier: str, + providers: Optional[List[str]] = None, + tool_names: Optional[List[str]] = None, + connection_names: Optional[List[str]] = None, + page_size: Optional[int] = None, + page_token: Optional[str] = None + ) -> List[ScalekitGoogleAdkTool]: + """ + Get scoped tools from Scalekit and convert them to Google ADK compatible tools + + :param identifier: Identifier to scope the tools list + :param providers: List of provider names to filter by + :param tool_names: List of tool names to filter by + :param connection_names: List of connection names to filter by + :param page_size: Maximum number of tools to return per page + :param page_token: Token from a previous response for pagination + :returns: List of Google ADK compatible tools + :raises ImportError: If Google ADK dependencies are not installed + """ + if identifier is None or identifier == "": + raise ValueError("Identifier must be provided to get tools") + + # Check if Google ADK dependencies are available + if not self.is_available(): + missing = self.get_missing_dependencies() + missing_deps = list(missing.keys()) + install_commands = list(missing.values()) + + raise ImportError( + f"Google ADK dependencies not found: {', '.join(missing_deps)}\n" + f"To use Google ADK integration, please install the missing dependencies:\n" + + "\n".join(f" {cmd}" for cmd in install_commands) + "\n\n" + "For more information, see the Scalekit SDK documentation." + ) + + # Create ScopedToolFilter if any filter parameters are provided + scoped_filter = None + if providers or tool_names or connection_names: + scoped_filter = ScopedToolFilter( + providers=providers or [], + tool_names=tool_names or [], + connection_names=connection_names or [] + ) + + # Call list_scoped_tools which returns (response, metadata) tuple + result_tuple = self.tools.list_scoped_tools(identifier, scoped_filter, page_size, page_token) + + # Extract the response[0] (the actual ListScopedToolsResponse proto object) + response = result_tuple[0] + + google_adk_tools = [] + for scoped_tool in response.tools: + google_adk_tool = self._convert_tool_to_google_adk_tool( + scoped_tool.tool, + scoped_tool.connected_account_id + ) + google_adk_tools.append(google_adk_tool) + + return google_adk_tools + + def _convert_tool_to_google_adk_tool(self, tool, connected_account_id: str): + """Convert a Scalekit Tool to Google ADK compatible tool""" + + # Extract tool metadata using common utility + tool_name, tool_description, definition_dict = extract_tool_metadata(tool) + + # Convert Scalekit tool to MCP input schema + input_schema = convert_to_mcp_input_schema(definition_dict) + + # Create and return ScalekitGoogleAdkTool instance + return create_scalekit_google_adk_tool( + tool_name=tool_name, + tool_description=tool_description, + input_schema=input_schema, + connected_account_id=connected_account_id, + execute_callback=self.execute_callback + ) + diff --git a/scalekit/actions/frameworks/langchain.py b/scalekit/actions/frameworks/langchain.py index 665702c..6a81bde 100644 --- a/scalekit/actions/frameworks/langchain.py +++ b/scalekit/actions/frameworks/langchain.py @@ -2,6 +2,7 @@ from langchain_core.tools import StructuredTool from scalekit.tools import ToolsClient from scalekit.v1.tools.tools_pb2 import Filter, ScopedToolFilter +from scalekit.actions.frameworks.util import struct_to_dict, extract_tool_metadata class LangChain: @@ -63,13 +64,7 @@ def get_tools( def _convert_tool_to_structured_tool(self, tool, connected_account_id: str) -> StructuredTool: """Convert a Scalekit Tool to LangChain StructuredTool""" - - definition_dict = self._struct_to_dict(tool.definition) if hasattr(tool, 'definition') and tool.definition else {} - - - tool_name = definition_dict.get('name', getattr(tool, 'provider', 'unknown') + '_tool') - tool_description = definition_dict.get('description', 'Scalekit tool') - + tool_name, tool_description, definition_dict = extract_tool_metadata(tool) args_schema = definition_dict.get("input_schema", {}) @@ -119,7 +114,3 @@ async def call_tool_async(**arguments: Dict[str, Any]) -> str: coroutine=call_tool_async, ) - def _struct_to_dict(self, struct) -> Dict[str, Any]: - """Convert protobuf Struct to Python dict""" - from google.protobuf.json_format import MessageToDict - return MessageToDict(struct) \ No newline at end of file diff --git a/scalekit/actions/frameworks/types/__init__.py b/scalekit/actions/frameworks/types/__init__.py new file mode 100644 index 0000000..6a4c410 --- /dev/null +++ b/scalekit/actions/frameworks/types/__init__.py @@ -0,0 +1,13 @@ +from .google_adk_tool import ( + ScalekitGoogleAdkTool, + create_scalekit_google_adk_tool, + check_google_adk_availability, + get_missing_dependencies +) + +__all__ = [ + 'ScalekitGoogleAdkTool', + 'create_scalekit_google_adk_tool', + 'check_google_adk_availability', + 'get_missing_dependencies' +] \ No newline at end of file diff --git a/scalekit/actions/frameworks/types/google_adk_tool.py b/scalekit/actions/frameworks/types/google_adk_tool.py new file mode 100644 index 0000000..6300925 --- /dev/null +++ b/scalekit/actions/frameworks/types/google_adk_tool.py @@ -0,0 +1,251 @@ +from typing import Any, Dict, Callable, Protocol, runtime_checkable + + +@runtime_checkable +class McpToolProtocol(Protocol): + """Protocol for Google ADK McpTool compatibility""" + def _get_declaration(self): ... + async def _run_async_impl(self, *, args: Dict[str, Any], tool_context, credential): ... + + +@runtime_checkable +class AuthCredentialProtocol(Protocol): + """Protocol for Google ADK AuthCredential compatibility""" + pass + + +@runtime_checkable +class ToolContextProtocol(Protocol): + """Protocol for Google ADK ToolContext compatibility""" + pass + + +@runtime_checkable +class McpToolTypeProtocol(Protocol): + """Protocol for MCP Tool type compatibility""" + name: str + description: str + inputSchema: Dict[str, Any] + + +@runtime_checkable +class FunctionDeclarationProtocol(Protocol): + """Protocol for Google AI FunctionDeclaration compatibility""" + pass + + +# Dynamic imports with helpful error messages +def _import_google_adk(): + """Import Google ADK with helpful error message if not available""" + try: + from google.genai.adk import McpTool + from google.genai.adk.core import AuthCredential, ToolContext + return McpTool, AuthCredential, ToolContext + except ImportError as e: + raise ImportError( + "Google ADK not found. To use Google ADK integration, please install:\n" + "pip install google-genai-adk\n\n" + "For more information, see: https://github.com/google/generative-ai-adk\n" + f"Original error: {e}" + ) + + +def _import_mcp_types(): + """Import MCP types with helpful error message if not available""" + try: + from mcp import types as mcp_types + return mcp_types + except ImportError as e: + raise ImportError( + "MCP (Model Context Protocol) not found. To use Google ADK integration, please install:\n" + "pip install mcp\n\n" + "For more information, see: https://github.com/modelcontextprotocol/python-sdk\n" + f"Original error: {e}" + ) + + +def _import_function_declaration(): + """Import Google AI FunctionDeclaration with helpful error message if not available""" + try: + from google.ai.generativelanguage import FunctionDeclaration + return FunctionDeclaration + except ImportError as e: + raise ImportError( + "Google AI Generative Language not found. To use Google ADK integration, please install:\n" + "pip install google-ai-generativelanguage\n\n" + "For more information, see: https://googleapis.dev/python/generativelanguage/latest/\n" + f"Original error: {e}" + ) + + +class ScalekitGoogleAdkTool: + """Google ADK Tool wrapper for Scalekit tools using MCP protocol""" + + def __init__( + self, + tool_name: str, + tool_description: str, + input_schema: Dict[str, Any], + connected_account_id: str, + execute_callback: Callable + ): + """ + Initialize ScalekitGoogleAdkTool + + :param tool_name: Name of the Scalekit tool + :param tool_description: Description of the tool + :param input_schema: Input schema in MCP format + :param connected_account_id: Connected account ID for execution + :param execute_callback: Callback function for tool execution (ActionClient.execute_tool) + """ + # Import dependencies dynamically + McpTool, AuthCredential, ToolContext = _import_google_adk() + mcp_types = _import_mcp_types() + FunctionDeclaration = _import_function_declaration() + + # Store types for later use + self._McpTool = McpTool + self._AuthCredential = AuthCredential + self._ToolContext = ToolContext + self._FunctionDeclaration = FunctionDeclaration + + # Create MCP tool definition + mcp_tool_def = mcp_types.Tool( + name=tool_name, + description=tool_description, + inputSchema=input_schema + ) + + # Initialize as McpTool-like object + self._mcp_tool = McpTool( + mcp_tool=mcp_tool_def, + mcp_session_manager=None, + auth_scheme=None, + auth_credential=None, + ) + + # Store Scalekit-specific properties + self.tool_name = tool_name + self.tool_description = tool_description + self.input_schema = input_schema + self.connected_account_id = connected_account_id + self.execute_callback = execute_callback + + def _get_declaration(self): + """ + Get Google ADK function declaration for this tool + Delegates to wrapped McpTool which handles MCP -> Google ADK conversion + """ + return self._mcp_tool._get_declaration() + + async def _run_async_impl( + self, + *, + args: Dict[str, Any], + tool_context, + credential + ) -> str: + """ + Execute the Scalekit tool using the execute_callback + + :param args: Tool execution arguments + :param tool_context: Google ADK tool context + :param credential: Authentication credential + :returns: Tool execution result as string + """ + try: + # Call the execute_callback (ActionClient.execute_tool) + response = await self.execute_callback( + tool_input=args, + tool_name=self.tool_name, + connected_account_id=self.connected_account_id + ) + + # Extract result data + result_data = response.data if hasattr(response, 'data') else {} + execution_id = response.execution_id if hasattr(response, 'execution_id') else None + + # Format the response + result_dict = dict(result_data) if result_data else {} + if execution_id: + result_dict['execution_id'] = execution_id + + return str(result_dict) if result_dict else f"Tool {self.tool_name} executed successfully" + + except Exception as e: + return f"Error executing tool {self.tool_name}: {str(e)}" + + +def create_scalekit_google_adk_tool( + tool_name: str, + tool_description: str, + input_schema: Dict[str, Any], + connected_account_id: str, + execute_callback: Callable +) -> ScalekitGoogleAdkTool: + """ + Factory function to create ScalekitGoogleAdkTool instances + + This function will raise ImportError with helpful messages if required + Google ADK dependencies are not installed. + + :param tool_name: Name of the Scalekit tool + :param tool_description: Description of the tool + :param input_schema: Input schema in MCP format + :param connected_account_id: Connected account ID for execution + :param execute_callback: Callback function for tool execution + :returns: ScalekitGoogleAdkTool instance + :raises ImportError: If Google ADK dependencies are not installed + """ + try: + return ScalekitGoogleAdkTool( + tool_name=tool_name, + tool_description=tool_description, + input_schema=input_schema, + connected_account_id=connected_account_id, + execute_callback=execute_callback + ) + except ImportError: + # Re-raise with context about where the error occurred + raise + + +def check_google_adk_availability() -> bool: + """ + Check if Google ADK dependencies are available + + :returns: True if all dependencies are available, False otherwise + """ + try: + _import_google_adk() + _import_mcp_types() + _import_function_declaration() + return True + except ImportError: + return False + + +def get_missing_dependencies() -> Dict[str, str]: + """ + Get information about missing Google ADK dependencies + + :returns: Dictionary mapping dependency names to installation commands + """ + missing = {} + + try: + _import_google_adk() + except ImportError: + missing["google-genai-adk"] = "pip install google-genai-adk" + + try: + _import_mcp_types() + except ImportError: + missing["mcp"] = "pip install mcp" + + try: + _import_function_declaration() + except ImportError: + missing["google-ai-generativelanguage"] = "pip install google-ai-generativelanguage" + + return missing \ No newline at end of file diff --git a/scalekit/actions/frameworks/util.py b/scalekit/actions/frameworks/util.py new file mode 100644 index 0000000..d82e061 --- /dev/null +++ b/scalekit/actions/frameworks/util.py @@ -0,0 +1,44 @@ +from typing import Dict, Any + + +def struct_to_dict(struct) -> Dict[str, Any]: + """ + Convert protobuf Struct to Python dict + + :param struct: Protobuf Struct object + :returns: Python dictionary representation + """ + from google.protobuf.json_format import MessageToDict + return MessageToDict(struct) + + +def extract_tool_metadata(tool, default_provider: str = 'unknown'): + """ + Extract common tool metadata from Scalekit tool definition + + :param tool: Scalekit tool object + :param default_provider: Default provider name if not found + :returns: Tuple of (tool_name, tool_description, definition_dict) + """ + definition_dict = struct_to_dict(tool.definition) if hasattr(tool, 'definition') and tool.definition else {} + + tool_name = definition_dict.get('name', getattr(tool, 'provider', default_provider) + '_tool') + tool_description = definition_dict.get('description', 'Scalekit tool') + + return tool_name, tool_description, definition_dict + + +def convert_to_mcp_input_schema(definition_dict: Dict[str, Any]) -> Dict[str, Any]: + """ + Convert Scalekit tool definition to MCP input schema format + + :param definition_dict: Scalekit tool definition dictionary + :returns: MCP-compatible input schema + """ + input_schema = definition_dict.get("input_schema", {}) + + return { + "type": "object", + "properties": input_schema.get("properties", {}), + "required": input_schema.get("required", []) + } \ No newline at end of file diff --git a/setup.py b/setup.py index 97b17c6..7424b6e 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="scalekit-sdk-python", - version="2.4.1", + version="4.4.4", packages=find_packages(), install_requires=[ "grpcio>=1.64.1", From ba42b52dcd5c9b689ca4b2116177565c397da859 Mon Sep 17 00:00:00 2001 From: avinash Date: Fri, 19 Sep 2025 08:49:49 -0700 Subject: [PATCH 2/8] add support for google ADK --- .python-version | 1 + scalekit/actions/actions.py | 4 + scalekit/actions/frameworks/google_adk.py | 7 +- .../frameworks/types/google_adk_tool.py | 204 +++++++++--------- setup.py | 3 +- 5 files changed, 107 insertions(+), 112 deletions(-) create mode 100644 .python-version diff --git a/.python-version b/.python-version new file mode 100644 index 0000000..2c07333 --- /dev/null +++ b/.python-version @@ -0,0 +1 @@ +3.11 diff --git a/scalekit/actions/actions.py b/scalekit/actions/actions.py index d9563f6..d3bece6 100644 --- a/scalekit/actions/actions.py +++ b/scalekit/actions/actions.py @@ -12,6 +12,7 @@ apply_pre_modifiers, apply_post_modifiers ) from scalekit.actions.frameworks.langchain import LangChain +from scalekit.actions.frameworks.google_adk import GoogleADK from scalekit.common.exceptions import ScalekitNotFoundException @@ -42,6 +43,9 @@ def __init__(self,tools_client, connected_accounts_client, mcp_client=None): # Initialize LangChain with tools client and execute callback self.langchain = LangChain(tools_client, execute_callback=self.execute_tool) + + # Initialize Google ADK with tools client and execute callback + self.google_adk = GoogleADK(tools_client, execute_callback=self.execute_tool) def execute_tool( self, diff --git a/scalekit/actions/frameworks/google_adk.py b/scalekit/actions/frameworks/google_adk.py index a46f5c2..789f763 100644 --- a/scalekit/actions/frameworks/google_adk.py +++ b/scalekit/actions/frameworks/google_adk.py @@ -70,10 +70,11 @@ def get_tools( install_commands = list(missing.values()) raise ImportError( - f"Google ADK dependencies not found: {', '.join(missing_deps)}\n" - f"To use Google ADK integration, please install the missing dependencies:\n" + f"Google ADK not found: {', '.join(missing_deps)}\n" + f"To use Google ADK integration, please install:\n" + "\n".join(f" {cmd}" for cmd in install_commands) + "\n\n" - "For more information, see the Scalekit SDK documentation." + "Note: MCP is already included as a Scalekit SDK dependency.\n" + "For more information, see: https://google.github.io/adk-docs/" ) # Create ScopedToolFilter if any filter parameters are provided diff --git a/scalekit/actions/frameworks/types/google_adk_tool.py b/scalekit/actions/frameworks/types/google_adk_tool.py index 6300925..4876b1f 100644 --- a/scalekit/actions/frameworks/types/google_adk_tool.py +++ b/scalekit/actions/frameworks/types/google_adk_tool.py @@ -1,6 +1,9 @@ from typing import Any, Dict, Callable, Protocol, runtime_checkable +from mcp import types as mcp_types + + @runtime_checkable class McpToolProtocol(Protocol): """Protocol for Google ADK McpTool compatibility""" @@ -38,48 +41,20 @@ class FunctionDeclarationProtocol(Protocol): def _import_google_adk(): """Import Google ADK with helpful error message if not available""" try: - from google.genai.adk import McpTool - from google.genai.adk.core import AuthCredential, ToolContext - return McpTool, AuthCredential, ToolContext + from google.adk.tools import BaseTool + from google.adk.core import AuthCredential, ToolContext + return BaseTool, AuthCredential, ToolContext except ImportError as e: raise ImportError( "Google ADK not found. To use Google ADK integration, please install:\n" - "pip install google-genai-adk\n\n" - "For more information, see: https://github.com/google/generative-ai-adk\n" - f"Original error: {e}" - ) - - -def _import_mcp_types(): - """Import MCP types with helpful error message if not available""" - try: - from mcp import types as mcp_types - return mcp_types - except ImportError as e: - raise ImportError( - "MCP (Model Context Protocol) not found. To use Google ADK integration, please install:\n" - "pip install mcp\n\n" - "For more information, see: https://github.com/modelcontextprotocol/python-sdk\n" - f"Original error: {e}" - ) - - -def _import_function_declaration(): - """Import Google AI FunctionDeclaration with helpful error message if not available""" - try: - from google.ai.generativelanguage import FunctionDeclaration - return FunctionDeclaration - except ImportError as e: - raise ImportError( - "Google AI Generative Language not found. To use Google ADK integration, please install:\n" - "pip install google-ai-generativelanguage\n\n" - "For more information, see: https://googleapis.dev/python/generativelanguage/latest/\n" + "pip install google-adk\n\n" + "For more information, see: https://google.github.io/adk-docs/\n" f"Original error: {e}" ) class ScalekitGoogleAdkTool: - """Google ADK Tool wrapper for Scalekit tools using MCP protocol""" + """Google ADK Tool wrapper for Scalekit tools inheriting from Google BaseTool""" def __init__( self, @@ -98,33 +73,90 @@ def __init__( :param connected_account_id: Connected account ID for execution :param execute_callback: Callback function for tool execution (ActionClient.execute_tool) """ - # Import dependencies dynamically - McpTool, AuthCredential, ToolContext = _import_google_adk() - mcp_types = _import_mcp_types() - FunctionDeclaration = _import_function_declaration() + # Import Google ADK dependencies dynamically + BaseTool, AuthCredential, ToolContext = _import_google_adk() # Store types for later use - self._McpTool = McpTool + self._BaseTool = BaseTool self._AuthCredential = AuthCredential self._ToolContext = ToolContext - self._FunctionDeclaration = FunctionDeclaration - # Create MCP tool definition - mcp_tool_def = mcp_types.Tool( - name=tool_name, - description=tool_description, - inputSchema=input_schema - ) + # Create dynamic class that inherits from BaseTool + class _DynamicScalekitTool(BaseTool): + def __init__(self_inner, tool_name, tool_description, input_schema, connected_account_id, execute_callback): + # Initialize parent BaseTool + super().__init__() + + # Store Scalekit-specific properties + self_inner.tool_name = tool_name + self_inner.tool_description = tool_description + self_inner.input_schema = input_schema + self_inner.connected_account_id = connected_account_id + self_inner.execute_callback = execute_callback + + def _get_declaration(self_inner): + """ + Get Google ADK function declaration for this tool + Override from BaseTool to provide Scalekit tool definition + """ + # Convert MCP input schema to Google ADK format + from google.ai.generativelanguage import FunctionDeclaration, Schema, Type + + # Create function declaration from Scalekit tool + return FunctionDeclaration( + name=self_inner.tool_name, + description=self_inner.tool_description, + parameters=Schema( + type=Type.OBJECT, + properties=self_inner.input_schema.get('properties', {}), + required=self_inner.input_schema.get('required', []) + ) + ) + + async def _run_async_impl( + self_inner, + *, + args: Dict[str, Any], + tool_context, + credential + ) -> str: + """ + Execute the Scalekit tool using the execute_callback + Override from BaseTool to provide Scalekit execution + + :param args: Tool execution arguments + :param tool_context: Google ADK tool context + :param credential: Authentication credential + :returns: Tool execution result as string + """ + try: + # Call the execute_callback (ActionClient.execute_tool) + response = await self_inner.execute_callback( + tool_input=args, + tool_name=self_inner.tool_name, + connected_account_id=self_inner.connected_account_id + ) + + # Extract result data + result_data = response.data if hasattr(response, 'data') else {} + execution_id = response.execution_id if hasattr(response, 'execution_id') else None + + # Format the response + result_dict = dict(result_data) if result_data else {} + if execution_id: + result_dict['execution_id'] = execution_id + + return str(result_dict) if result_dict else f"Tool {self_inner.tool_name} executed successfully" + + except Exception as e: + return f"Error executing tool {self_inner.tool_name}: {str(e)}" - # Initialize as McpTool-like object - self._mcp_tool = McpTool( - mcp_tool=mcp_tool_def, - mcp_session_manager=None, - auth_scheme=None, - auth_credential=None, + # Create instance of the dynamic class + self._tool_instance = _DynamicScalekitTool( + tool_name, tool_description, input_schema, connected_account_id, execute_callback ) - # Store Scalekit-specific properties + # Store properties for external access self.tool_name = tool_name self.tool_description = tool_description self.input_schema = input_schema @@ -132,48 +164,16 @@ def __init__( self.execute_callback = execute_callback def _get_declaration(self): - """ - Get Google ADK function declaration for this tool - Delegates to wrapped McpTool which handles MCP -> Google ADK conversion - """ - return self._mcp_tool._get_declaration() + """Delegate to the BaseTool instance""" + return self._tool_instance._get_declaration() - async def _run_async_impl( - self, - *, - args: Dict[str, Any], - tool_context, - credential - ) -> str: - """ - Execute the Scalekit tool using the execute_callback - - :param args: Tool execution arguments - :param tool_context: Google ADK tool context - :param credential: Authentication credential - :returns: Tool execution result as string - """ - try: - # Call the execute_callback (ActionClient.execute_tool) - response = await self.execute_callback( - tool_input=args, - tool_name=self.tool_name, - connected_account_id=self.connected_account_id - ) - - # Extract result data - result_data = response.data if hasattr(response, 'data') else {} - execution_id = response.execution_id if hasattr(response, 'execution_id') else None - - # Format the response - result_dict = dict(result_data) if result_data else {} - if execution_id: - result_dict['execution_id'] = execution_id - - return str(result_dict) if result_dict else f"Tool {self.tool_name} executed successfully" - - except Exception as e: - return f"Error executing tool {self.tool_name}: {str(e)}" + async def _run_async_impl(self, *, args: Dict[str, Any], tool_context, credential) -> str: + """Delegate to the BaseTool instance""" + return await self._tool_instance._run_async_impl(args=args, tool_context=tool_context, credential=credential) + + def get_tool_instance(self): + """Get the actual Google ADK BaseTool instance""" + return self._tool_instance def create_scalekit_google_adk_tool( @@ -214,12 +214,10 @@ def check_google_adk_availability() -> bool: """ Check if Google ADK dependencies are available - :returns: True if all dependencies are available, False otherwise + :returns: True if Google ADK is installed, False otherwise """ try: _import_google_adk() - _import_mcp_types() - _import_function_declaration() return True except ImportError: return False @@ -236,16 +234,6 @@ def get_missing_dependencies() -> Dict[str, str]: try: _import_google_adk() except ImportError: - missing["google-genai-adk"] = "pip install google-genai-adk" - - try: - _import_mcp_types() - except ImportError: - missing["mcp"] = "pip install mcp" - - try: - _import_function_declaration() - except ImportError: - missing["google-ai-generativelanguage"] = "pip install google-ai-generativelanguage" + missing["google-adk"] = "pip install google-adk" return missing \ No newline at end of file diff --git a/setup.py b/setup.py index 7424b6e..d229c28 100644 --- a/setup.py +++ b/setup.py @@ -3,7 +3,7 @@ setup( name="scalekit-sdk-python", - version="4.4.4", + version="4.4.5", packages=find_packages(), install_requires=[ "grpcio>=1.64.1", @@ -22,6 +22,7 @@ "Faker~=25.8.0", "pydantic~=2.10.6", "langchain-core>=0.3.36,<0.4", + "mcp>=1.0.0", ], url="https://github.com/scalekit-inc/scalekit-sdk-python", license="MIT", From 80a99b32e21bce001369e5fd356fe00adf033a11 Mon Sep 17 00:00:00 2001 From: avinash Date: Wed, 24 Sep 2025 17:32:30 +0530 Subject: [PATCH 3/8] add google ADK support --- requirements.txt | 3 +- scalekit/actions/frameworks/google_adk.py | 20 +- scalekit/actions/frameworks/langchain.py | 4 - scalekit/actions/frameworks/types/__init__.py | 6 +- .../frameworks/types/google_adk_tool.py | 237 +++++------------- scalekit/actions/frameworks/util.py | 31 ++- setup.py | 2 +- tests/test_connect.py | 11 + 8 files changed, 113 insertions(+), 201 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1addc0a..d1f44ad 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,4 +9,5 @@ pydantic~=2.10.6 PyJWT~=2.9.0 cryptography~=45.0.6 setuptools~=80.9.0 -langchain-core~=0.3.74 \ No newline at end of file +langchain-core~=0.3.74 +mcp~=1.12.4 \ No newline at end of file diff --git a/scalekit/actions/frameworks/google_adk.py b/scalekit/actions/frameworks/google_adk.py index 789f763..fb57e76 100644 --- a/scalekit/actions/frameworks/google_adk.py +++ b/scalekit/actions/frameworks/google_adk.py @@ -2,12 +2,11 @@ from scalekit.tools import ToolsClient from scalekit.v1.tools.tools_pb2 import Filter, ScopedToolFilter from scalekit.actions.frameworks.types.google_adk_tool import ( - create_scalekit_google_adk_tool, ScalekitGoogleAdkTool, check_google_adk_availability, get_missing_dependencies ) -from scalekit.actions.frameworks.util import extract_tool_metadata, convert_to_mcp_input_schema +from scalekit.actions.frameworks.util import extract_tool_metadata, build_mcp_tool_from_spec, struct_to_dict class GoogleADK: @@ -105,17 +104,12 @@ def get_tools( def _convert_tool_to_google_adk_tool(self, tool, connected_account_id: str): """Convert a Scalekit Tool to Google ADK compatible tool""" - # Extract tool metadata using common utility - tool_name, tool_description, definition_dict = extract_tool_metadata(tool) - - # Convert Scalekit tool to MCP input schema - input_schema = convert_to_mcp_input_schema(definition_dict) - - # Create and return ScalekitGoogleAdkTool instance - return create_scalekit_google_adk_tool( - tool_name=tool_name, - tool_description=tool_description, - input_schema=input_schema, + + spec = struct_to_dict(tool) + mcp_tool = build_mcp_tool_from_spec(spec) + + return ScalekitGoogleAdkTool( + mcp_tool=mcp_tool, connected_account_id=connected_account_id, execute_callback=self.execute_callback ) diff --git a/scalekit/actions/frameworks/langchain.py b/scalekit/actions/frameworks/langchain.py index 6a81bde..f055e62 100644 --- a/scalekit/actions/frameworks/langchain.py +++ b/scalekit/actions/frameworks/langchain.py @@ -71,10 +71,6 @@ def _convert_tool_to_structured_tool(self, tool, connected_account_id: str) -> S def _call(**arguments: Dict[str, Any]) -> str: try: - # Import here to avoid circular imports - from scalekit.actions.types import ToolInput - - # Call connect.execute_tool via callback (includes modifiers and enhanced handling) response = self.execute_callback( tool_input=arguments, diff --git a/scalekit/actions/frameworks/types/__init__.py b/scalekit/actions/frameworks/types/__init__.py index 6a4c410..536bf03 100644 --- a/scalekit/actions/frameworks/types/__init__.py +++ b/scalekit/actions/frameworks/types/__init__.py @@ -1,13 +1,11 @@ from .google_adk_tool import ( - ScalekitGoogleAdkTool, - create_scalekit_google_adk_tool, + ScalekitGoogleAdkTool, check_google_adk_availability, get_missing_dependencies ) __all__ = [ - 'ScalekitGoogleAdkTool', - 'create_scalekit_google_adk_tool', + 'ScalekitGoogleAdkTool', 'check_google_adk_availability', 'get_missing_dependencies' ] \ No newline at end of file diff --git a/scalekit/actions/frameworks/types/google_adk_tool.py b/scalekit/actions/frameworks/types/google_adk_tool.py index 4876b1f..96bc0b7 100644 --- a/scalekit/actions/frameworks/types/google_adk_tool.py +++ b/scalekit/actions/frameworks/types/google_adk_tool.py @@ -1,49 +1,17 @@ from typing import Any, Dict, Callable, Protocol, runtime_checkable -from mcp import types as mcp_types - - -@runtime_checkable -class McpToolProtocol(Protocol): - """Protocol for Google ADK McpTool compatibility""" - def _get_declaration(self): ... - async def _run_async_impl(self, *, args: Dict[str, Any], tool_context, credential): ... - - -@runtime_checkable -class AuthCredentialProtocol(Protocol): - """Protocol for Google ADK AuthCredential compatibility""" - pass - - -@runtime_checkable -class ToolContextProtocol(Protocol): - """Protocol for Google ADK ToolContext compatibility""" - pass - - -@runtime_checkable -class McpToolTypeProtocol(Protocol): - """Protocol for MCP Tool type compatibility""" - name: str - description: str - inputSchema: Dict[str, Any] - - -@runtime_checkable -class FunctionDeclarationProtocol(Protocol): - """Protocol for Google AI FunctionDeclaration compatibility""" - pass +from mcp.types import Tool as McpBaseTool # Dynamic imports with helpful error messages def _import_google_adk(): """Import Google ADK with helpful error message if not available""" try: - from google.adk.tools import BaseTool - from google.adk.core import AuthCredential, ToolContext - return BaseTool, AuthCredential, ToolContext + from google.adk.tools.mcp_tool.mcp_tool import MCPTool + from google.adk.tools.tool_context import ToolContext + from google.adk.auth.auth_credential import AuthCredential + return MCPTool, AuthCredential, ToolContext except ImportError as e: raise ImportError( "Google ADK not found. To use Google ADK integration, please install:\n" @@ -52,162 +20,77 @@ def _import_google_adk(): f"Original error: {e}" ) +McpTool,AuthCredential,ToolContext = _import_google_adk() + +@runtime_checkable +class McpToolProtocol(Protocol): + """Protocol for Google ADK McpTool compatibility""" + def _get_declaration(self): ... + async def _run_async_impl(self, *, args, tool_context, credentials): ... + + -class ScalekitGoogleAdkTool: + + +class ScalekitGoogleAdkTool(McpTool,McpToolProtocol): """Google ADK Tool wrapper for Scalekit tools inheriting from Google BaseTool""" def __init__( self, - tool_name: str, - tool_description: str, - input_schema: Dict[str, Any], + mcp_tool: McpBaseTool, connected_account_id: str, execute_callback: Callable ): """ Initialize ScalekitGoogleAdkTool - - :param tool_name: Name of the Scalekit tool - :param tool_description: Description of the tool - :param input_schema: Input schema in MCP format :param connected_account_id: Connected account ID for execution :param execute_callback: Callback function for tool execution (ActionClient.execute_tool) """ - # Import Google ADK dependencies dynamically - BaseTool, AuthCredential, ToolContext = _import_google_adk() - - # Store types for later use - self._BaseTool = BaseTool - self._AuthCredential = AuthCredential - self._ToolContext = ToolContext - - # Create dynamic class that inherits from BaseTool - class _DynamicScalekitTool(BaseTool): - def __init__(self_inner, tool_name, tool_description, input_schema, connected_account_id, execute_callback): - # Initialize parent BaseTool - super().__init__() - - # Store Scalekit-specific properties - self_inner.tool_name = tool_name - self_inner.tool_description = tool_description - self_inner.input_schema = input_schema - self_inner.connected_account_id = connected_account_id - self_inner.execute_callback = execute_callback - - def _get_declaration(self_inner): - """ - Get Google ADK function declaration for this tool - Override from BaseTool to provide Scalekit tool definition - """ - # Convert MCP input schema to Google ADK format - from google.ai.generativelanguage import FunctionDeclaration, Schema, Type - - # Create function declaration from Scalekit tool - return FunctionDeclaration( - name=self_inner.tool_name, - description=self_inner.tool_description, - parameters=Schema( - type=Type.OBJECT, - properties=self_inner.input_schema.get('properties', {}), - required=self_inner.input_schema.get('required', []) - ) - ) - - async def _run_async_impl( - self_inner, - *, - args: Dict[str, Any], - tool_context, - credential - ) -> str: - """ - Execute the Scalekit tool using the execute_callback - Override from BaseTool to provide Scalekit execution - - :param args: Tool execution arguments - :param tool_context: Google ADK tool context - :param credential: Authentication credential - :returns: Tool execution result as string - """ - try: - # Call the execute_callback (ActionClient.execute_tool) - response = await self_inner.execute_callback( - tool_input=args, - tool_name=self_inner.tool_name, - connected_account_id=self_inner.connected_account_id - ) - - # Extract result data - result_data = response.data if hasattr(response, 'data') else {} - execution_id = response.execution_id if hasattr(response, 'execution_id') else None - - # Format the response - result_dict = dict(result_data) if result_data else {} - if execution_id: - result_dict['execution_id'] = execution_id - - return str(result_dict) if result_dict else f"Tool {self_inner.tool_name} executed successfully" - - except Exception as e: - return f"Error executing tool {self_inner.tool_name}: {str(e)}" - - # Create instance of the dynamic class - self._tool_instance = _DynamicScalekitTool( - tool_name, tool_description, input_schema, connected_account_id, execute_callback + + super().__init__( + mcp_tool=mcp_tool, + mcp_session_manager=None, + auth_scheme=None, + auth_credential=None, ) - - # Store properties for external access - self.tool_name = tool_name - self.tool_description = tool_description - self.input_schema = input_schema + self.connected_account_id = connected_account_id self.execute_callback = execute_callback - - def _get_declaration(self): - """Delegate to the BaseTool instance""" - return self._tool_instance._get_declaration() - - async def _run_async_impl(self, *, args: Dict[str, Any], tool_context, credential) -> str: - """Delegate to the BaseTool instance""" - return await self._tool_instance._run_async_impl(args=args, tool_context=tool_context, credential=credential) - - def get_tool_instance(self): - """Get the actual Google ADK BaseTool instance""" - return self._tool_instance - - -def create_scalekit_google_adk_tool( - tool_name: str, - tool_description: str, - input_schema: Dict[str, Any], - connected_account_id: str, - execute_callback: Callable -) -> ScalekitGoogleAdkTool: - """ - Factory function to create ScalekitGoogleAdkTool instances - - This function will raise ImportError with helpful messages if required - Google ADK dependencies are not installed. - - :param tool_name: Name of the Scalekit tool - :param tool_description: Description of the tool - :param input_schema: Input schema in MCP format - :param connected_account_id: Connected account ID for execution - :param execute_callback: Callback function for tool execution - :returns: ScalekitGoogleAdkTool instance - :raises ImportError: If Google ADK dependencies are not installed - """ - try: - return ScalekitGoogleAdkTool( - tool_name=tool_name, - tool_description=tool_description, - input_schema=input_schema, - connected_account_id=connected_account_id, - execute_callback=execute_callback - ) - except ImportError: - # Re-raise with context about where the error occurred - raise + self.name = mcp_tool.name + self.description = mcp_tool.description + + + + + + + async def _run_async_impl(self, *, args, **kwargs) -> str: + + try: + # Call connect.execute_tool via callback (includes modifiers and enhanced handling) + response = self.execute_callback( + tool_input=args, + tool_name=self.name, + connected_account_id=self.connected_account_id + ) + + result_data = response.data if hasattr(response, 'data') else {} + + execution_id = response.execution_id if hasattr(response, 'execution_id') else None + + # Format the response + result_dict = dict(result_data) if result_data else {} + if execution_id: + result_dict['execution_id'] = execution_id + + return str(result_dict) if result_dict else f"Tool {self.name} executed successfully" + + except Exception as e: + return f"Error executing tool {self.name}: {str(e)}" + + + + def check_google_adk_availability() -> bool: diff --git a/scalekit/actions/frameworks/util.py b/scalekit/actions/frameworks/util.py index d82e061..2959150 100644 --- a/scalekit/actions/frameworks/util.py +++ b/scalekit/actions/frameworks/util.py @@ -1,4 +1,5 @@ from typing import Dict, Any +from mcp.types import Tool as McpBaseTool, ToolAnnotations def struct_to_dict(struct) -> Dict[str, Any]: @@ -41,4 +42,32 @@ def convert_to_mcp_input_schema(definition_dict: Dict[str, Any]) -> Dict[str, An "type": "object", "properties": input_schema.get("properties", {}), "required": input_schema.get("required", []) - } \ No newline at end of file + } + +def build_mcp_tool_from_spec(spec: Dict[str, Any]) -> McpBaseTool: + """Converts the raw spec dict into an MCP Tool instance. + + Mapping performed: + definition.input_schema -> inputSchema + definition.annotations.* snake_case -> ToolAnnotations camelCase + """ + definition = spec["definition"] + ann_raw = definition.get("annotations", {}) + ann_map = { + "title": ann_raw.get("title"), + "readOnlyHint": ann_raw.get("read_only_hint"), + "destructiveHint": ann_raw.get("destructive_hint"), + "idempotentHint": ann_raw.get("idempotent_hint"), + "openWorldHint": ann_raw.get("open_world_hint"), + } + # Filter out None values + ann_clean = {k: v for k, v in ann_map.items() if v is not None} + annotations = ToolAnnotations(**ann_clean) if ann_clean else None + + mcp_tool = McpBaseTool( + name=definition["name"], + description=definition.get("description"), + inputSchema=definition["input_schema"], + annotations=annotations, + ) + return mcp_tool \ No newline at end of file diff --git a/setup.py b/setup.py index d229c28..1336472 100644 --- a/setup.py +++ b/setup.py @@ -20,7 +20,7 @@ "deprecation>=2.1.0", "python-dotenv>=1.1.0", "Faker~=25.8.0", - "pydantic~=2.10.6", + "pydantic>=2.10.6", "langchain-core>=0.3.36,<0.4", "mcp>=1.0.0", ], diff --git a/tests/test_connect.py b/tests/test_connect.py index 20a6713..e1ea4c0 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -767,3 +767,14 @@ def test_create_connected_account_request_structure(self): self.assertTrue(empty_proto.authorization_details.HasField("oauth_token")) self.assertEqual(empty_proto.authorization_details.oauth_token.access_token, "") + def test_google_adk_get_tools(self): + + tool = self.scalekit_client.actions.google_adk.get_tools( + identifier=self.test_identifier, + tool_names=["gmail_fetch_mails"], + connection_names=["GMAIL"] + ) + self.assertIsInstance(tool, list) + self.assertGreaterEqual(len(tool), 1) + print(tool) + From b6d504b0b9f8ae0f2190cc2ba7988dfa5f2133a6 Mon Sep 17 00:00:00 2001 From: avinash Date: Wed, 24 Sep 2025 17:53:01 +0530 Subject: [PATCH 4/8] change google_adk to google --- scalekit/actions/actions.py | 2 +- tests/test_connect.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/scalekit/actions/actions.py b/scalekit/actions/actions.py index d3bece6..def67dc 100644 --- a/scalekit/actions/actions.py +++ b/scalekit/actions/actions.py @@ -45,7 +45,7 @@ def __init__(self,tools_client, connected_accounts_client, mcp_client=None): self.langchain = LangChain(tools_client, execute_callback=self.execute_tool) # Initialize Google ADK with tools client and execute callback - self.google_adk = GoogleADK(tools_client, execute_callback=self.execute_tool) + self.google = GoogleADK(tools_client, execute_callback=self.execute_tool) def execute_tool( self, diff --git a/tests/test_connect.py b/tests/test_connect.py index e1ea4c0..2504a11 100644 --- a/tests/test_connect.py +++ b/tests/test_connect.py @@ -1,4 +1,5 @@ from basetest import BaseTest +from scalekit.actions.frameworks.types import ScalekitGoogleAdkTool from scalekit.actions.types import ExecuteToolResponse, MagicLinkResponse, ListConnectedAccountsResponse, DeleteConnectedAccountResponse, GetConnectedAccountAuthResponse, ToolMapping, CreateConnectedAccountResponse from scalekit.actions.modifier import Modifier @@ -769,12 +770,13 @@ def test_create_connected_account_request_structure(self): def test_google_adk_get_tools(self): - tool = self.scalekit_client.actions.google_adk.get_tools( + tool = self.scalekit_client.actions.google.get_tools( identifier=self.test_identifier, tool_names=["gmail_fetch_mails"], connection_names=["GMAIL"] ) self.assertIsInstance(tool, list) self.assertGreaterEqual(len(tool), 1) + self.assertTrue(isinstance(tool[0], ScalekitGoogleAdkTool)) print(tool) From 886ba88125f615002ba77b5683dc9eee370c6eee Mon Sep 17 00:00:00 2001 From: avinash Date: Wed, 24 Sep 2025 19:00:15 +0530 Subject: [PATCH 5/8] remove protocols for now --- scalekit/actions/frameworks/types/google_adk_tool.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/scalekit/actions/frameworks/types/google_adk_tool.py b/scalekit/actions/frameworks/types/google_adk_tool.py index 96bc0b7..b35e69c 100644 --- a/scalekit/actions/frameworks/types/google_adk_tool.py +++ b/scalekit/actions/frameworks/types/google_adk_tool.py @@ -22,17 +22,8 @@ def _import_google_adk(): McpTool,AuthCredential,ToolContext = _import_google_adk() -@runtime_checkable -class McpToolProtocol(Protocol): - """Protocol for Google ADK McpTool compatibility""" - def _get_declaration(self): ... - async def _run_async_impl(self, *, args, tool_context, credentials): ... - - - - -class ScalekitGoogleAdkTool(McpTool,McpToolProtocol): +class ScalekitGoogleAdkTool(McpTool): """Google ADK Tool wrapper for Scalekit tools inheriting from Google BaseTool""" def __init__( From f68e2639d6cb62f14d1cebd75de47eb29b5d21f8 Mon Sep 17 00:00:00 2001 From: avinash Date: Fri, 26 Sep 2025 18:21:36 +0530 Subject: [PATCH 6/8] Let users install Langchain and ADK as external dependency --- scalekit/actions/actions.py | 40 ++++++++++++++++-- scalekit/actions/frameworks/google_adk.py | 41 ++---------------- scalekit/actions/frameworks/langchain.py | 4 +- scalekit/actions/frameworks/types/__init__.py | 4 -- .../frameworks/types/google_adk_tool.py | 42 ++----------------- setup.py | 1 - tests/{test_connect.py => test_actions.py} | 40 +++++++++++------- 7 files changed, 69 insertions(+), 103 deletions(-) rename tests/{test_connect.py => test_actions.py} (97%) diff --git a/scalekit/actions/actions.py b/scalekit/actions/actions.py index aa02b20..be1fd97 100644 --- a/scalekit/actions/actions.py +++ b/scalekit/actions/actions.py @@ -11,8 +11,6 @@ Modifier, ModifierType, ToolNames, apply_pre_modifiers, apply_post_modifiers ) -from scalekit.actions.frameworks.langchain import LangChain -from scalekit.actions.frameworks.google_adk import GoogleADK from scalekit.common.exceptions import ScalekitNotFoundException @@ -40,12 +38,46 @@ def __init__(self,tools_client, connected_accounts_client, mcp_client=None): self.connected_accounts = connected_accounts_client self.mcp = mcp_client self._modifiers: List[Modifier] = [] + self._google = None + self._langchain = None # Initialize LangChain with tools client and execute callback - self.langchain = LangChain(tools_client, execute_callback=self.execute_tool) + # Initialize Google ADK with tools client and execute callback - self.google = GoogleADK(tools_client, execute_callback=self.execute_tool) + #self.google = GoogleADK(tools_client, execute_callback=self.execute_tool) + + @property + def langchain(self): + """Get LangChain framework instance""" + if self._langchain is None: + try: + from scalekit.actions.frameworks.langchain import LangChain + self._langchain = LangChain(self.tools, execute_callback=self.execute_tool) + except ImportError as e: + raise ImportError( + "LangChain not found. To use LangChain integration, please install:\n" + "pip install langchain\n\n" + "For more information, see: https://python.langchain.com/docs/\n" + ) + return self._langchain + + + @property + def google(self): + """Get Google ADK framework instance""" + if self._google is None: + try: + from scalekit.actions.frameworks.google_adk import GoogleADK + self._google = GoogleADK(self.tools, execute_callback=self.execute_tool) + except ImportError as e: + raise ImportError( + "Google ADK not found. To use Google ADK integration, please install:\n" + "pip install google-adk\n\n" + "For more information, see: https://google.github.io/adk-docs/\n" + ) + + return self._google def execute_tool( self, diff --git a/scalekit/actions/frameworks/google_adk.py b/scalekit/actions/frameworks/google_adk.py index fb57e76..e1626f4 100644 --- a/scalekit/actions/frameworks/google_adk.py +++ b/scalekit/actions/frameworks/google_adk.py @@ -3,10 +3,8 @@ from scalekit.v1.tools.tools_pb2 import Filter, ScopedToolFilter from scalekit.actions.frameworks.types.google_adk_tool import ( ScalekitGoogleAdkTool, - check_google_adk_availability, - get_missing_dependencies ) -from scalekit.actions.frameworks.util import extract_tool_metadata, build_mcp_tool_from_spec, struct_to_dict +from scalekit.actions.frameworks.util import build_mcp_tool_from_spec, struct_to_dict class GoogleADK: @@ -16,27 +14,7 @@ def __init__(self, tools_client: ToolsClient, execute_callback: Callable): self.tools = tools_client self.execute_callback = execute_callback - self._availability_checked = False - self._is_available = False - - def is_available(self) -> bool: - """ - Check if Google ADK dependencies are available - - :returns: True if all Google ADK dependencies are installed, False otherwise - """ - if not self._availability_checked: - self._is_available = check_google_adk_availability() - self._availability_checked = True - return self._is_available - - def get_missing_dependencies(self) -> Dict[str, str]: - """ - Get information about missing Google ADK dependencies - - :returns: Dictionary mapping dependency names to installation commands - """ - return get_missing_dependencies() + def get_tools( self, @@ -61,20 +39,7 @@ def get_tools( """ if identifier is None or identifier == "": raise ValueError("Identifier must be provided to get tools") - - # Check if Google ADK dependencies are available - if not self.is_available(): - missing = self.get_missing_dependencies() - missing_deps = list(missing.keys()) - install_commands = list(missing.values()) - - raise ImportError( - f"Google ADK not found: {', '.join(missing_deps)}\n" - f"To use Google ADK integration, please install:\n" - + "\n".join(f" {cmd}" for cmd in install_commands) + "\n\n" - "Note: MCP is already included as a Scalekit SDK dependency.\n" - "For more information, see: https://google.github.io/adk-docs/" - ) + # Create ScopedToolFilter if any filter parameters are provided scoped_filter = None diff --git a/scalekit/actions/frameworks/langchain.py b/scalekit/actions/frameworks/langchain.py index f055e62..537be85 100644 --- a/scalekit/actions/frameworks/langchain.py +++ b/scalekit/actions/frameworks/langchain.py @@ -1,8 +1,8 @@ from typing import Optional, Any, Dict, List, Callable from langchain_core.tools import StructuredTool from scalekit.tools import ToolsClient -from scalekit.v1.tools.tools_pb2 import Filter, ScopedToolFilter -from scalekit.actions.frameworks.util import struct_to_dict, extract_tool_metadata +from scalekit.v1.tools.tools_pb2 import ScopedToolFilter +from scalekit.actions.frameworks.util import extract_tool_metadata class LangChain: diff --git a/scalekit/actions/frameworks/types/__init__.py b/scalekit/actions/frameworks/types/__init__.py index 536bf03..3385a72 100644 --- a/scalekit/actions/frameworks/types/__init__.py +++ b/scalekit/actions/frameworks/types/__init__.py @@ -1,11 +1,7 @@ from .google_adk_tool import ( ScalekitGoogleAdkTool, - check_google_adk_availability, - get_missing_dependencies ) __all__ = [ 'ScalekitGoogleAdkTool', - 'check_google_adk_availability', - 'get_missing_dependencies' ] \ No newline at end of file diff --git a/scalekit/actions/frameworks/types/google_adk_tool.py b/scalekit/actions/frameworks/types/google_adk_tool.py index b35e69c..2809b4c 100644 --- a/scalekit/actions/frameworks/types/google_adk_tool.py +++ b/scalekit/actions/frameworks/types/google_adk_tool.py @@ -1,17 +1,14 @@ -from typing import Any, Dict, Callable, Protocol, runtime_checkable - - - +from typing import Callable from mcp.types import Tool as McpBaseTool # Dynamic imports with helpful error messages def _import_google_adk(): """Import Google ADK with helpful error message if not available""" try: - from google.adk.tools.mcp_tool.mcp_tool import MCPTool + from google.adk.tools.mcp_tool.mcp_tool import McpTool from google.adk.tools.tool_context import ToolContext from google.adk.auth.auth_credential import AuthCredential - return MCPTool, AuthCredential, ToolContext + return McpTool, AuthCredential, ToolContext except ImportError as e: raise ImportError( "Google ADK not found. To use Google ADK integration, please install:\n" @@ -78,36 +75,3 @@ async def _run_async_impl(self, *, args, **kwargs) -> str: except Exception as e: return f"Error executing tool {self.name}: {str(e)}" - - - - - - -def check_google_adk_availability() -> bool: - """ - Check if Google ADK dependencies are available - - :returns: True if Google ADK is installed, False otherwise - """ - try: - _import_google_adk() - return True - except ImportError: - return False - - -def get_missing_dependencies() -> Dict[str, str]: - """ - Get information about missing Google ADK dependencies - - :returns: Dictionary mapping dependency names to installation commands - """ - missing = {} - - try: - _import_google_adk() - except ImportError: - missing["google-adk"] = "pip install google-adk" - - return missing \ No newline at end of file diff --git a/setup.py b/setup.py index 8db8b5c..94b0e9b 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,6 @@ "python-dotenv>=1.1.0", "Faker~=25.8.0", "pydantic>=2.10.6", - "langchain-core>=0.3.36,<0.4", "mcp>=1.0.0", ], url="https://github.com/scalekit-inc/scalekit-sdk-python", diff --git a/tests/test_connect.py b/tests/test_actions.py similarity index 97% rename from tests/test_connect.py rename to tests/test_actions.py index 2504a11..2fb14d0 100644 --- a/tests/test_connect.py +++ b/tests/test_actions.py @@ -1,5 +1,4 @@ from basetest import BaseTest -from scalekit.actions.frameworks.types import ScalekitGoogleAdkTool from scalekit.actions.types import ExecuteToolResponse, MagicLinkResponse, ListConnectedAccountsResponse, DeleteConnectedAccountResponse, GetConnectedAccountAuthResponse, ToolMapping, CreateConnectedAccountResponse from scalekit.actions.modifier import Modifier @@ -549,15 +548,7 @@ def test_create_connected_account_validation(self): authorization_details=oauth_auth_details ) self.assertIn("identifier is required", str(context.exception)) - - # Test missing authorization_details - with self.assertRaises(ValueError) as context: - self.scalekit_client.connect.create_connected_account( - connection_name="GMAIL", - identifier="test_id", - authorization_details={} - ) - self.assertIn("authorization_details is required", str(context.exception)) + # Tests for new get_or_create_connected_account functionality def test_get_or_create_connected_account_method_exists(self): @@ -770,13 +761,32 @@ def test_create_connected_account_request_structure(self): def test_google_adk_get_tools(self): - tool = self.scalekit_client.actions.google.get_tools( + google = self.scalekit_client.actions.google + self.assertIsNotNone(google) + + from scalekit.actions.frameworks.google_adk import ScalekitGoogleAdkTool + + tools = google.get_tools( + identifier=self.test_identifier, + tool_names=["gmail_fetch_mails"], + connection_names=["GMAIL"] + ) + self.assertIsInstance(tools, list) + self.assertGreaterEqual(len(tools), 1) + self.assertIsInstance(tools[0], ScalekitGoogleAdkTool) + print(tools) + + def test_langchain_adk_get_tools(self): + + lang = self.scalekit_client.actions.langchain + self.assertIsNotNone(lang) + + tools = lang.get_tools( identifier=self.test_identifier, tool_names=["gmail_fetch_mails"], connection_names=["GMAIL"] ) - self.assertIsInstance(tool, list) - self.assertGreaterEqual(len(tool), 1) - self.assertTrue(isinstance(tool[0], ScalekitGoogleAdkTool)) - print(tool) + self.assertIsInstance(tools, list) + self.assertGreaterEqual(len(tools), 1) + print(tools) From 2618ffb349faaa2e529df71126a4c7a9a56487db Mon Sep 17 00:00:00 2001 From: avinash Date: Fri, 26 Sep 2025 18:29:11 +0530 Subject: [PATCH 7/8] Let users install Langchain and ADK as external dependency --- requirements.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3ca678e..86c103e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -9,5 +9,4 @@ pydantic~=2.11.2 PyJWT~=2.9.0 cryptography~=45.0.6 setuptools~=80.9.0 -langchain-core~=0.3.74 mcp~=1.12.4 \ No newline at end of file From 94b34dd94e4d5433c3de04d40e0c6e4940f514fd Mon Sep 17 00:00:00 2001 From: avinash Date: Fri, 26 Sep 2025 18:34:59 +0530 Subject: [PATCH 8/8] update mcp version --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 94b0e9b..e7f3b8d 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ "python-dotenv>=1.1.0", "Faker~=25.8.0", "pydantic>=2.10.6", - "mcp>=1.0.0", + "mcp>= 1.15.0", ], url="https://github.com/scalekit-inc/scalekit-sdk-python", license="MIT",