{"record":{"id":"920abd2914e303f8","repo":"agentscope-ai/agentscope","slug":"self-class-name-is-an-external-tool-and","errorCode":null,"errorMessage":"{self.__class__.__name__} is an external tool and should not be called directly","messagePattern":"(.+?) is an external tool and should not be called directly","errorType":"exception","errorClass":"RuntimeError","httpStatus":null,"severity":"error","filePath":"src/agentscope/tool/_base.py","lineNumber":170,"sourceCode":"        Subclasses should override this method instead of\n        :meth:`__call__`.  The base implementation raises\n        :exc:`NotImplementedError` for non-external tools and\n        :exc:`RuntimeError` for external tools.\n\n        Args:\n            **kwargs: Tool input arguments.\n\n        Returns:\n            `ToolChunk | AsyncGenerator[ToolChunk, None]`:\n                A single :class:`~agentscope.tool.ToolChunk` or an\n                async generator that yields them.\n        \"\"\"\n        if not self.is_external_tool:\n            raise NotImplementedError(\n                f\"{self.__class__.__name__} does not implement call\",\n            )\n\n        raise RuntimeError(\n            f\"{self.__class__.__name__} is an external tool and should not \"\n            f\"be called directly\",\n        )\n\n    async def __call__(\n        self,\n        *args: Any,\n        **kwargs: Any,\n    ) -> ToolChunk | AsyncGenerator[ToolChunk, None]:\n        \"\"\"Invoke the tool, layering any registered middlewares around\n        :meth:`call`.\n\n        Tools are always invoked with keyword arguments only. ``*args`` is\n        accepted in the signature solely to stay Liskov-compatible with\n        subclasses that override ``__call__`` with their own positional\n        parameters; any positional argument actually passed here is rejected\n        (raising :exc:`TypeError`) so it fails loudly instead of being silently\n        dropped.","sourceCodeStart":152,"sourceCodeEnd":188,"githubUrl":"https://github.com/agentscope-ai/agentscope/blob/e90f1c7592896cc95f6e5ee506194f533378247d/src/agentscope/tool/_base.py#L152-L188","documentation":"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.","triggerScenarios":"Calling tool(...) directly on an external/MCP tool instance instead of letting the toolkit execute it through its adapter chain (execute_chain / MCP session).","commonSituations":"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.","solutions":["Invoke the tool via the toolkit/agent so it is dispatched through the MCP adapter","Don't call external tools directly; use the toolkit's run interface","If it should be local, don't mark/construct it as an external tool"],"exampleFix":"# before\nresult = await mcp_tool(query='x')\n# after\nresult = await toolkit.call_tool(mcp_tool.name, query='x')","handlingStrategy":"try-catch","validationCode":"assert not tool.is_external_tool, 'route external tools through the toolkit'","typeGuard":"def is_local_tool(tool) -> bool:\n    return not getattr(tool, 'is_external_tool', False)","tryCatchPattern":"try:\n    await tool(**kwargs)\nexcept RuntimeError as e:\n    if 'external tool' in str(e):\n        result = await toolkit.call_tool(tool.name, **kwargs)\n    else: raise","preventionTips":["Never call MCP tools directly; dispatch via the toolkit","Check is_external_tool before manual invocation"],"tags":["mcp","external-tool","runtime-error"],"backgroundTag":"invalid-tool-invocation","analyzedSha":"e90f1c7592896cc95f6e5ee506194f533378247d","analyzedAt":"2026-08-28T18:24:12.087Z","schemaVersion":2},"datasetVersion":"2026-08-28T21:17:43.275Z"}