toolError
Builds a failed tool result that says why with one code, in the text a model reads and in structuredContent.error.
Builds a failed tool result that says why with one code, twice: in the text every model reads (Error [invalid_input]: …) and in structuredContent.error ({ code, retryable }) for browsers, devtools and code that branches on it. Reach for it when you answer a call yourself, for example in a registerTool handler of your own; inside a WebTool you throw a WebToolError instead. toolErrorCode reads the code back, and RETRYABLE says which codes are worth retrying.
Import
import { RETRYABLE, toolError, toolErrorCode } from "@webmcp-agents/core";
import type { ToolErrorCode } from "@webmcp-agents/core";
Example
import {
type ModelContextToolResult,
RETRYABLE,
toolError,
toolErrorCode,
} from "@webmcp-agents/core";
const result = toolError(
"unavailable",
'No tool "place_order" on this page. Go to /checkout first.',
);
toolErrorCode(result); // "unavailable"
RETRYABLE.unavailable; // true: go to /checkout, then call again
// Text from a declarative form can't set isError, so it is coded text.
const fromForm: ModelContextToolResult = {
content: [
{ type: "text", text: "Error [needs_user]: Ask which size." },
],
};
toolErrorCode(fromForm); // "needs_user"
Options
toolError(code, message)
| Name | Type | Default | Description |
|---|---|---|---|
code |
ToolErrorCode |
required | Why the call failed; one of the codes below. |
message |
string |
required | What happened, and what to do next. A leading Error: or Error [code]: is stripped, so the code is not repeated. |
toolErrorCode(result)
| Name | Type | Default | Description |
|---|---|---|---|
result |
ModelContextToolResult | null | undefined |
required | The result to read the code from. |
Error codes
ToolErrorCode is one of these. Each says what the agent should do next.
| Code | Means | The agent should | RETRYABLE |
|---|---|---|---|
cancelled |
the person stopped it | stop; retry only if asked | false |
interrupted |
the page navigated or the tool changed mid-call | check the page, then retry | true |
timeout |
no answer in time (it may or may not have worked) | check the page, then retry or try another way | true |
unavailable |
no such tool on this page right now | get to the page that has it | true |
invalid_input |
the arguments were wrong | fix them and retry | true |
needs_user |
the person has to choose or confirm | ask them | false |
failed |
the tool ran and could not do it | read the message | false |
RETRYABLE is a Readonly<Record<ToolErrorCode, boolean>>: whether calling again (after checking the page or fixing the input) can succeed without asking the person.
Returns
toolError returns a failed ModelContextToolResult:
{
content: [{ type: "text", text: `Error [${code}]: ${message}` }],
isError: true,
structuredContent: { error: { code, retryable: RETRYABLE[code] } },
}
toolErrorCode returns the code from structuredContent.error.code, else from an Error [code]: prefix on the first text content, and undefined when there is no known code.
Notes
- In a
WebToolyou do not calltoolErroryourself: throw aWebToolErrorto pick the code. Other throws becomefailed, bad inputinvalid_input, and a confirm-gated tool with nothing to confirm onunavailable. - A plain
Error: …text has no code, sotoolErrorCodereturnsundefinedfor it. The chat codes such a text asfailed. - A declarative form cannot set
isError, so it answers with the coded text;toolErrorCodereads that too. - An unknown code in either place is ignored (
undefined), so a result from another library never throws here.
See also
- Errors, the codes and when each fires
WebToolError, the way to pick a code from a toolerrorResult, turn any thrown value into a coded resultexecuteTool, which returns results like these