agentscope-ai/agentscope · error · RuntimeError

{self.__class__.__name__} is an external tool and should not

Error message

{self.__class__.__name__} is an external tool and should not be called directly

What it means

ToolBase.call() raises RuntimeError when invoked on a tool flagged as external (e.g. an MCP tool): execution must be routed through the adapter/runtime, not a direct Python call.

Source

Thrown at src/agentscope/tool/_base.py:170

        Subclasses should override this method instead of
        :meth:`__call__`.  The base implementation raises
        :exc:`NotImplementedError` for non-external tools and
        :exc:`RuntimeError` for external tools.

        Args:
            **kwargs: Tool input arguments.

        Returns:
            `ToolChunk | AsyncGenerator[ToolChunk, None]`:
                A single :class:`~agentscope.tool.ToolChunk` or an
                async generator that yields them.
        """
        if not self.is_external_tool:
            raise NotImplementedError(
                f"{self.__class__.__name__} does not implement call",
            )

        raise RuntimeError(
            f"{self.__class__.__name__} is an external tool and should not "
            f"be called directly",
        )

    async def __call__(
        self,
        *args: Any,
        **kwargs: Any,
    ) -> ToolChunk | AsyncGenerator[ToolChunk, None]:
        """Invoke the tool, layering any registered middlewares around
        :meth:`call`.

        Tools are always invoked with keyword arguments only. ``*args`` is
        accepted in the signature solely to stay Liskov-compatible with
        subclasses that override ``__call__`` with their own positional
        parameters; any positional argument actually passed here is rejected
        (raising :exc:`TypeError`) so it fails loudly instead of being silently
        dropped.

View on GitHub (pinned to e90f1c7592)

Solutions

  1. Invoke the tool via the toolkit/agent so it is dispatched through the MCP adapter
  2. Don't call external tools directly; use the toolkit's run interface
  3. If it should be local, don't mark/construct it as an external tool

Example fix

# before
result = await mcp_tool(query='x')
# after
result = await toolkit.call_tool(mcp_tool.name, query='x')
Defensive patterns

Strategy: try-catch

Validate before calling

assert not tool.is_external_tool, 'route external tools through the toolkit'

Type guard

def is_local_tool(tool) -> bool:
    return not getattr(tool, 'is_external_tool', False)

Try / catch

try:
    await tool(**kwargs)
except RuntimeError as e:
    if 'external tool' in str(e):
        result = await toolkit.call_tool(tool.name, **kwargs)
    else: raise

Prevention

When it happens

Trigger: Calling tool(...) directly on an external/MCP tool instance instead of letting the toolkit execute it through its adapter chain (execute_chain / MCP session).

Common situations: Grabbing a tool reference from a toolkit and calling it manually in tests or scripts; the external flag is set but the caller treats it like a local tool.

Related errors


AI-assisted analysis of agentscope-ai/agentscope@e90f1c7592 (2026-08-28). Data as JSON: /api/errors/920abd2914e303f8. Report an issue: GitHub.