# This file was auto-generated by Fern from our API Definition.

import typing
from ..core.client_wrapper import SyncClientWrapper
from ..core.request_options import RequestOptions
from ..types.tool import Tool
from ..core.jsonable_encoder import jsonable_encoder
from ..core.unchecked_base_model import construct_type
from ..errors.unprocessable_entity_error import UnprocessableEntityError
from ..types.http_validation_error import HttpValidationError
from json.decoder import JSONDecodeError
from ..core.api_error import ApiError
from ..types.tool_return_message import ToolReturnMessage
from ..types.app_model import AppModel
from ..types.action_model import ActionModel
from .types.list_mcp_servers_response_value import ListMcpServersResponseValue
from .types.add_mcp_server_request import AddMcpServerRequest
from .types.add_mcp_server_response_item import AddMcpServerResponseItem
from ..core.serialization import convert_and_respect_annotation_metadata
from ..types.mcp_tool import McpTool
from .types.delete_mcp_server_response_item import DeleteMcpServerResponseItem
from ..core.client_wrapper import AsyncClientWrapper

# this is used as the default value for optional parameters
OMIT = typing.cast(typing.Any, ...)


class ToolsClient:
    def __init__(self, *, client_wrapper: SyncClientWrapper):
        self._client_wrapper = client_wrapper

    def retrieve(self, tool_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Tool:
        """
        Get a tool by ID

        Parameters
        ----------
        tool_id : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.retrieve(
            tool_id="tool_id",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def delete(
        self, tool_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.Optional[typing.Any]:
        """
        Delete a tool by name

        Parameters
        ----------
        tool_id : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.Optional[typing.Any]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.delete(
            tool_id="tool_id",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.Optional[typing.Any],
                    construct_type(
                        type_=typing.Optional[typing.Any],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def modify(
        self,
        tool_id: str,
        *,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_code: typing.Optional[str] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Update an existing tool

        Parameters
        ----------
        tool_id : str

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_code : typing.Optional[str]
            The source code of the function.

        source_type : typing.Optional[str]
            The type of the source code.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.modify(
            tool_id="tool_id",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="PATCH",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def list(
        self,
        *,
        after: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        name: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.List[Tool]:
        """
        Get a list of all tools available to agents belonging to the org of the user

        Parameters
        ----------
        after : typing.Optional[str]

        limit : typing.Optional[int]

        name : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[Tool]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.list()
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="GET",
            params={
                "after": after,
                "limit": limit,
                "name": name,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[Tool],
                    construct_type(
                        type_=typing.List[Tool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def create(
        self,
        *,
        source_code: str,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Create a new tool

        Parameters
        ----------
        source_code : str
            The source code of the function.

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_type : typing.Optional[str]
            The source type of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.create(
            source_code="source_code",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="POST",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def upsert(
        self,
        *,
        source_code: str,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Create or update a tool

        Parameters
        ----------
        source_code : str
            The source code of the function.

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_type : typing.Optional[str]
            The source type of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.upsert(
            source_code="source_code",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="PUT",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def add_base_tool(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[Tool]:
        """
        Upsert base tools

        Parameters
        ----------
        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[Tool]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.add_base_tool()
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/add-base-tools",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[Tool],
                    construct_type(
                        type_=typing.List[Tool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def run_tool_from_source(
        self,
        *,
        source_code: str,
        args: typing.Dict[str, typing.Optional[typing.Any]],
        env_vars: typing.Optional[typing.Dict[str, str]] = OMIT,
        name: typing.Optional[str] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> ToolReturnMessage:
        """
        Attempt to build a tool from source, then run it on the provided arguments

        Parameters
        ----------
        source_code : str
            The source code of the function.

        args : typing.Dict[str, typing.Optional[typing.Any]]
            The arguments to pass to the tool.

        env_vars : typing.Optional[typing.Dict[str, str]]
            The environment variables to pass to the tool.

        name : typing.Optional[str]
            The name of the tool to run.

        source_type : typing.Optional[str]
            The type of the source code.

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        ToolReturnMessage
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.run_tool_from_source(
            source_code="source_code",
            args={"key": "value"},
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/run",
            method="POST",
            json={
                "source_code": source_code,
                "args": args,
                "env_vars": env_vars,
                "name": name,
                "source_type": source_type,
                "args_json_schema": args_json_schema,
                "json_schema": json_schema,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    ToolReturnMessage,
                    construct_type(
                        type_=ToolReturnMessage,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def list_composio_apps(
        self, *, user_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[AppModel]:
        """
        Get a list of all Composio apps

        Parameters
        ----------
        user_id : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[AppModel]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.list_composio_apps()
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/composio/apps",
            method="GET",
            headers={
                "user-id": str(user_id) if user_id is not None else None,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[AppModel],
                    construct_type(
                        type_=typing.List[AppModel],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def list_composio_actions_by_app(
        self, composio_app_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[ActionModel]:
        """
        Get a list of all Composio actions for a specific app

        Parameters
        ----------
        composio_app_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[ActionModel]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.list_composio_actions_by_app(
            composio_app_name="composio_app_name",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/composio/apps/{jsonable_encoder(composio_app_name)}/actions",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[ActionModel],
                    construct_type(
                        type_=typing.List[ActionModel],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def add_composio_tool(
        self, composio_action_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> Tool:
        """
        Add a new Composio tool by action name (Composio refers to each tool as an `Action`)

        Parameters
        ----------
        composio_action_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.add_composio_tool(
            composio_action_name="composio_action_name",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/composio/{jsonable_encoder(composio_action_name)}",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def list_mcp_servers(
        self, *, user_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.Dict[str, ListMcpServersResponseValue]:
        """
        Get a list of all configured MCP servers

        Parameters
        ----------
        user_id : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.Dict[str, ListMcpServersResponseValue]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.list_mcp_servers()
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/mcp/servers",
            method="GET",
            headers={
                "user-id": str(user_id) if user_id is not None else None,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.Dict[str, ListMcpServersResponseValue],
                    construct_type(
                        type_=typing.Dict[str, ListMcpServersResponseValue],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def add_mcp_server(
        self, *, request: AddMcpServerRequest, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[AddMcpServerResponseItem]:
        """
        Add a new MCP server to the Letta MCP server config

        Parameters
        ----------
        request : AddMcpServerRequest

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[AddMcpServerResponseItem]
            Successful Response

        Examples
        --------
        from letta_client import Letta, StdioServerConfig

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.add_mcp_server(
            request=StdioServerConfig(
                server_name="server_name",
                command="command",
                args=["args"],
            ),
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            "v1/tools/mcp/servers",
            method="PUT",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=AddMcpServerRequest, direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[AddMcpServerResponseItem],
                    construct_type(
                        type_=typing.List[AddMcpServerResponseItem],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def list_mcp_tools_by_server(
        self, mcp_server_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[McpTool]:
        """
        Get a list of all tools for a specific MCP server

        Parameters
        ----------
        mcp_server_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[McpTool]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.list_mcp_tools_by_server(
            mcp_server_name="mcp_server_name",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}/tools",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[McpTool],
                    construct_type(
                        type_=typing.List[McpTool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def add_mcp_tool(
        self, mcp_server_name: str, mcp_tool_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> Tool:
        """
        Register a new MCP tool as a Letta server by MCP server + tool name

        Parameters
        ----------
        mcp_server_name : str

        mcp_tool_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.add_mcp_tool(
            mcp_server_name="mcp_server_name",
            mcp_tool_name="mcp_tool_name",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}/{jsonable_encoder(mcp_tool_name)}",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    def delete_mcp_server(
        self, mcp_server_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[DeleteMcpServerResponseItem]:
        """
        Add a new MCP server to the Letta MCP server config

        Parameters
        ----------
        mcp_server_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[DeleteMcpServerResponseItem]
            Successful Response

        Examples
        --------
        from letta_client import Letta

        client = Letta(
            token="YOUR_TOKEN",
        )
        client.tools.delete_mcp_server(
            mcp_server_name="mcp_server_name",
        )
        """
        _response = self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[DeleteMcpServerResponseItem],
                    construct_type(
                        type_=typing.List[DeleteMcpServerResponseItem],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)


class AsyncToolsClient:
    def __init__(self, *, client_wrapper: AsyncClientWrapper):
        self._client_wrapper = client_wrapper

    async def retrieve(self, tool_id: str, *, request_options: typing.Optional[RequestOptions] = None) -> Tool:
        """
        Get a tool by ID

        Parameters
        ----------
        tool_id : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.retrieve(
                tool_id="tool_id",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def delete(
        self, tool_id: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.Optional[typing.Any]:
        """
        Delete a tool by name

        Parameters
        ----------
        tool_id : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.Optional[typing.Any]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.delete(
                tool_id="tool_id",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.Optional[typing.Any],
                    construct_type(
                        type_=typing.Optional[typing.Any],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def modify(
        self,
        tool_id: str,
        *,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_code: typing.Optional[str] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Update an existing tool

        Parameters
        ----------
        tool_id : str

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_code : typing.Optional[str]
            The source code of the function.

        source_type : typing.Optional[str]
            The type of the source code.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.modify(
                tool_id="tool_id",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/{jsonable_encoder(tool_id)}",
            method="PATCH",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def list(
        self,
        *,
        after: typing.Optional[str] = None,
        limit: typing.Optional[int] = None,
        name: typing.Optional[str] = None,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> typing.List[Tool]:
        """
        Get a list of all tools available to agents belonging to the org of the user

        Parameters
        ----------
        after : typing.Optional[str]

        limit : typing.Optional[int]

        name : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[Tool]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.list()


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="GET",
            params={
                "after": after,
                "limit": limit,
                "name": name,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[Tool],
                    construct_type(
                        type_=typing.List[Tool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def create(
        self,
        *,
        source_code: str,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Create a new tool

        Parameters
        ----------
        source_code : str
            The source code of the function.

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_type : typing.Optional[str]
            The source type of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.create(
                source_code="source_code",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="POST",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def upsert(
        self,
        *,
        source_code: str,
        description: typing.Optional[str] = OMIT,
        tags: typing.Optional[typing.Sequence[str]] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        return_char_limit: typing.Optional[int] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> Tool:
        """
        Create or update a tool

        Parameters
        ----------
        source_code : str
            The source code of the function.

        description : typing.Optional[str]
            The description of the tool.

        tags : typing.Optional[typing.Sequence[str]]
            Metadata tags.

        source_type : typing.Optional[str]
            The source type of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        return_char_limit : typing.Optional[int]
            The maximum number of characters in the response.

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.upsert(
                source_code="source_code",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/",
            method="PUT",
            json={
                "description": description,
                "tags": tags,
                "source_code": source_code,
                "source_type": source_type,
                "json_schema": json_schema,
                "args_json_schema": args_json_schema,
                "return_char_limit": return_char_limit,
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def add_base_tool(self, *, request_options: typing.Optional[RequestOptions] = None) -> typing.List[Tool]:
        """
        Upsert base tools

        Parameters
        ----------
        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[Tool]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.add_base_tool()


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/add-base-tools",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[Tool],
                    construct_type(
                        type_=typing.List[Tool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def run_tool_from_source(
        self,
        *,
        source_code: str,
        args: typing.Dict[str, typing.Optional[typing.Any]],
        env_vars: typing.Optional[typing.Dict[str, str]] = OMIT,
        name: typing.Optional[str] = OMIT,
        source_type: typing.Optional[str] = OMIT,
        args_json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        json_schema: typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]] = OMIT,
        request_options: typing.Optional[RequestOptions] = None,
    ) -> ToolReturnMessage:
        """
        Attempt to build a tool from source, then run it on the provided arguments

        Parameters
        ----------
        source_code : str
            The source code of the function.

        args : typing.Dict[str, typing.Optional[typing.Any]]
            The arguments to pass to the tool.

        env_vars : typing.Optional[typing.Dict[str, str]]
            The environment variables to pass to the tool.

        name : typing.Optional[str]
            The name of the tool to run.

        source_type : typing.Optional[str]
            The type of the source code.

        args_json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The args JSON schema of the function.

        json_schema : typing.Optional[typing.Dict[str, typing.Optional[typing.Any]]]
            The JSON schema of the function (auto-generated from source_code if not provided)

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        ToolReturnMessage
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.run_tool_from_source(
                source_code="source_code",
                args={"key": "value"},
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/run",
            method="POST",
            json={
                "source_code": source_code,
                "args": args,
                "env_vars": env_vars,
                "name": name,
                "source_type": source_type,
                "args_json_schema": args_json_schema,
                "json_schema": json_schema,
            },
            headers={
                "content-type": "application/json",
            },
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    ToolReturnMessage,
                    construct_type(
                        type_=ToolReturnMessage,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def list_composio_apps(
        self, *, user_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[AppModel]:
        """
        Get a list of all Composio apps

        Parameters
        ----------
        user_id : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[AppModel]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.list_composio_apps()


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/composio/apps",
            method="GET",
            headers={
                "user-id": str(user_id) if user_id is not None else None,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[AppModel],
                    construct_type(
                        type_=typing.List[AppModel],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def list_composio_actions_by_app(
        self, composio_app_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[ActionModel]:
        """
        Get a list of all Composio actions for a specific app

        Parameters
        ----------
        composio_app_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[ActionModel]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.list_composio_actions_by_app(
                composio_app_name="composio_app_name",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/composio/apps/{jsonable_encoder(composio_app_name)}/actions",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[ActionModel],
                    construct_type(
                        type_=typing.List[ActionModel],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def add_composio_tool(
        self, composio_action_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> Tool:
        """
        Add a new Composio tool by action name (Composio refers to each tool as an `Action`)

        Parameters
        ----------
        composio_action_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.add_composio_tool(
                composio_action_name="composio_action_name",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/composio/{jsonable_encoder(composio_action_name)}",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def list_mcp_servers(
        self, *, user_id: typing.Optional[str] = None, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.Dict[str, ListMcpServersResponseValue]:
        """
        Get a list of all configured MCP servers

        Parameters
        ----------
        user_id : typing.Optional[str]

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.Dict[str, ListMcpServersResponseValue]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.list_mcp_servers()


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/mcp/servers",
            method="GET",
            headers={
                "user-id": str(user_id) if user_id is not None else None,
            },
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.Dict[str, ListMcpServersResponseValue],
                    construct_type(
                        type_=typing.Dict[str, ListMcpServersResponseValue],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def add_mcp_server(
        self, *, request: AddMcpServerRequest, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[AddMcpServerResponseItem]:
        """
        Add a new MCP server to the Letta MCP server config

        Parameters
        ----------
        request : AddMcpServerRequest

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[AddMcpServerResponseItem]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta, StdioServerConfig

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.add_mcp_server(
                request=StdioServerConfig(
                    server_name="server_name",
                    command="command",
                    args=["args"],
                ),
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            "v1/tools/mcp/servers",
            method="PUT",
            json=convert_and_respect_annotation_metadata(
                object_=request, annotation=AddMcpServerRequest, direction="write"
            ),
            request_options=request_options,
            omit=OMIT,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[AddMcpServerResponseItem],
                    construct_type(
                        type_=typing.List[AddMcpServerResponseItem],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def list_mcp_tools_by_server(
        self, mcp_server_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[McpTool]:
        """
        Get a list of all tools for a specific MCP server

        Parameters
        ----------
        mcp_server_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[McpTool]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.list_mcp_tools_by_server(
                mcp_server_name="mcp_server_name",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}/tools",
            method="GET",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[McpTool],
                    construct_type(
                        type_=typing.List[McpTool],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def add_mcp_tool(
        self, mcp_server_name: str, mcp_tool_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> Tool:
        """
        Register a new MCP tool as a Letta server by MCP server + tool name

        Parameters
        ----------
        mcp_server_name : str

        mcp_tool_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        Tool
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.add_mcp_tool(
                mcp_server_name="mcp_server_name",
                mcp_tool_name="mcp_tool_name",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}/{jsonable_encoder(mcp_tool_name)}",
            method="POST",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    Tool,
                    construct_type(
                        type_=Tool,  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)

    async def delete_mcp_server(
        self, mcp_server_name: str, *, request_options: typing.Optional[RequestOptions] = None
    ) -> typing.List[DeleteMcpServerResponseItem]:
        """
        Add a new MCP server to the Letta MCP server config

        Parameters
        ----------
        mcp_server_name : str

        request_options : typing.Optional[RequestOptions]
            Request-specific configuration.

        Returns
        -------
        typing.List[DeleteMcpServerResponseItem]
            Successful Response

        Examples
        --------
        import asyncio

        from letta_client import AsyncLetta

        client = AsyncLetta(
            token="YOUR_TOKEN",
        )


        async def main() -> None:
            await client.tools.delete_mcp_server(
                mcp_server_name="mcp_server_name",
            )


        asyncio.run(main())
        """
        _response = await self._client_wrapper.httpx_client.request(
            f"v1/tools/mcp/servers/{jsonable_encoder(mcp_server_name)}",
            method="DELETE",
            request_options=request_options,
        )
        try:
            if 200 <= _response.status_code < 300:
                return typing.cast(
                    typing.List[DeleteMcpServerResponseItem],
                    construct_type(
                        type_=typing.List[DeleteMcpServerResponseItem],  # type: ignore
                        object_=_response.json(),
                    ),
                )
            if _response.status_code == 422:
                raise UnprocessableEntityError(
                    typing.cast(
                        HttpValidationError,
                        construct_type(
                            type_=HttpValidationError,  # type: ignore
                            object_=_response.json(),
                        ),
                    )
                )
            _response_json = _response.json()
        except JSONDecodeError:
            raise ApiError(status_code=_response.status_code, body=_response.text)
        raise ApiError(status_code=_response.status_code, body=_response_json)
