Skip to content

Smolagents Adapter

This module implements the SmolAgents adapter.

SmolAgents do not support async tools, so this adapter will only work with the sync context manager.

Example Usage:

with MCPAdapt(StdioServerParameters(command="uv", args=["run", "src/echo.py"]), SmolAgentAdapter()) as tools: print(tools)

SmolAgentsAdapter

Bases: ToolAdapter

Adapter for the smolagents framework.

Note that the smolagents framework do not support async tools at this time so we write only the adapt method.

Source code in src/mcpadapt/smolagents_adapter.py
class SmolAgentsAdapter(ToolAdapter):
    """Adapter for the `smolagents` framework.

    Note that the `smolagents` framework do not support async tools at this time so we
    write only the adapt method.
    """

    def adapt(
        self,
        func: Callable[[dict | None], mcp.types.CallToolResult],
        mcp_tool: mcp.types.Tool,
    ) -> smolagents.Tool:
        """Adapt a MCP tool to a SmolAgents tool.

        Args:
            func: The function to adapt.
            mcp_tool: The MCP tool to adapt.

        Returns:
            A SmolAgents tool.
        """

        class MCPAdaptTool(smolagents.Tool):
            def __init__(
                self,
                name: str,
                description: str,
                inputs: dict[str, dict[str, str]],
                output_type: str,
            ):
                self.name = name
                self.description = description
                self.inputs = inputs
                self.output_type = output_type
                self.is_initialized = True
                self.skip_forward_signature_validation = True

            def forward(self, *args, **kwargs) -> str:
                if len(args) > 0:
                    if len(args) == 1 and isinstance(args[0], dict) and not kwargs:
                        mcp_output = func(args[0])
                    else:
                        raise ValueError(
                            f"tool {self.name} does not support multiple positional arguments or combined positional and keyword arguments"
                        )
                else:
                    mcp_output = func(kwargs)

                if len(mcp_output.content) == 0:
                    raise ValueError(f"tool {self.name} returned an empty content")

                if len(mcp_output.content) > 1:
                    logger.warning(
                        f"tool {self.name} returned multiple content, using the first one"
                    )

                if not isinstance(mcp_output.content[0], mcp.types.TextContent):
                    raise ValueError(
                        f"tool {self.name} returned a non-text content: `{type(mcp_output.content[0])}`"
                    )

                return mcp_output.content[0].text  # type: ignore

        # make sure jsonref are resolved
        input_schema = {
            k: v
            for k, v in jsonref.replace_refs(mcp_tool.inputSchema).items()
            if k != "$defs"
        }

        # make sure mandatory `description` and `type` is provided for each arguments:
        for k, v in input_schema["properties"].items():
            if "description" not in v:
                input_schema["properties"][k]["description"] = "see tool description"
            if "type" not in v:
                input_schema["properties"][k]["type"] = "string"

        tool = MCPAdaptTool(
            name=mcp_tool.name,
            description=mcp_tool.description or "",
            inputs=input_schema["properties"],
            output_type="string",
        )

        return tool

    async def async_adapt(
        self,
        afunc: Callable[[dict | None], Coroutine[Any, Any, mcp.types.CallToolResult]],
        mcp_tool: mcp.types.Tool,
    ) -> smolagents.Tool:
        raise NotImplementedError("async is not supported by the SmolAgents framework.")

adapt

adapt(
    func: Callable[[dict | None], CallToolResult],
    mcp_tool: Tool,
) -> Tool

Adapt a MCP tool to a SmolAgents tool.

Parameters:

Name Type Description Default
func Callable[[dict | None], CallToolResult]

The function to adapt.

required
mcp_tool Tool

The MCP tool to adapt.

required

Returns:

Type Description
Tool

A SmolAgents tool.

Source code in src/mcpadapt/smolagents_adapter.py
def adapt(
    self,
    func: Callable[[dict | None], mcp.types.CallToolResult],
    mcp_tool: mcp.types.Tool,
) -> smolagents.Tool:
    """Adapt a MCP tool to a SmolAgents tool.

    Args:
        func: The function to adapt.
        mcp_tool: The MCP tool to adapt.

    Returns:
        A SmolAgents tool.
    """

    class MCPAdaptTool(smolagents.Tool):
        def __init__(
            self,
            name: str,
            description: str,
            inputs: dict[str, dict[str, str]],
            output_type: str,
        ):
            self.name = name
            self.description = description
            self.inputs = inputs
            self.output_type = output_type
            self.is_initialized = True
            self.skip_forward_signature_validation = True

        def forward(self, *args, **kwargs) -> str:
            if len(args) > 0:
                if len(args) == 1 and isinstance(args[0], dict) and not kwargs:
                    mcp_output = func(args[0])
                else:
                    raise ValueError(
                        f"tool {self.name} does not support multiple positional arguments or combined positional and keyword arguments"
                    )
            else:
                mcp_output = func(kwargs)

            if len(mcp_output.content) == 0:
                raise ValueError(f"tool {self.name} returned an empty content")

            if len(mcp_output.content) > 1:
                logger.warning(
                    f"tool {self.name} returned multiple content, using the first one"
                )

            if not isinstance(mcp_output.content[0], mcp.types.TextContent):
                raise ValueError(
                    f"tool {self.name} returned a non-text content: `{type(mcp_output.content[0])}`"
                )

            return mcp_output.content[0].text  # type: ignore

    # make sure jsonref are resolved
    input_schema = {
        k: v
        for k, v in jsonref.replace_refs(mcp_tool.inputSchema).items()
        if k != "$defs"
    }

    # make sure mandatory `description` and `type` is provided for each arguments:
    for k, v in input_schema["properties"].items():
        if "description" not in v:
            input_schema["properties"][k]["description"] = "see tool description"
        if "type" not in v:
            input_schema["properties"][k]["type"] = "string"

    tool = MCPAdaptTool(
        name=mcp_tool.name,
        description=mcp_tool.description or "",
        inputs=input_schema["properties"],
        output_type="string",
    )

    return tool