Tool nodes¶
A tool node gives an agent an action beyond generating text: it runs a small built-in function or calls an HTTP API and passes the result on. It is an activity node — it has real side effects (network calls), so it is treated as non-deterministic work.
There is one Tool node with three flavours, chosen from the Kind picker in the inspector:
- Builtin — a small function TheYgent ships. Two are available:
echoandhttp_fetch. - REST — call any HTTP API, optionally authenticated through a saved connection.
- MCP — a tool exposed by a connected server. Picking MCP swaps the node to the
mcp_tooltype, which has its own page: MCP tools.
This page covers Builtin and REST. Whichever flavour you pick, a tool node is used in one of two ways — as a step in the graph, or as a capability the model calls on its own.
Two ways to use a tool¶
A single tool node can only be one of these at a time — it is either a capability or a step, never both. The editor and the validator enforce that; mixing capability wiring and data wiring on the same node is a validation error.
Wire a data edge into the tool's in port and read its result from out. The node runs at a fixed point in the graph, once per run. Its arguments come from the graph — you template them with $in references. Use this when you decide when the tool runs (fetch a page, then summarise it).
Wire the tool's violet use handle into an llm node's tools port with a tool edge. Now the model decides when — and whether — to call the tool during its reasoning loop, and it supplies the arguments itself. Use this to give an assistant abilities it invokes autonomously.
Two rules make capability tools work:
- The node's id is the function name the model sees. Rename the node (in the inspector's label row, which also renames its id) to give the model a clear, descriptive name like
search_web. - A capability tool must describe itself. It needs a
description, and a REST capability also needs aparameterSchema(see below). The two built-ins already self-describe, so they work as capabilities with no extra config. A capability tool is exempt from the "required in-port must be connected" rule — itsinport is left unfed on purpose.
Ports¶
Both Builtin and REST tool nodes share the same ports.
| Port | Direction | Required | Description |
|---|---|---|---|
in |
in | yes* | The node's input, used when the tool runs as a step. *Left unfed when the node is a capability (exempt from the required-port check). |
out |
out | — | The tool's return value on success. |
err |
out | — | The error message when the call fails. Its port type is error. |
use |
out | — | The violet capability handle. Wire it into an llm's tools port to expose the tool to the model. |
Built-in tools¶
Built-ins are chosen with the Builtin kind. Set the tool field to the built-in's name and pass its arguments in args.
| Field | Type | Default | Description |
|---|---|---|---|
tool |
string | (required) | The built-in's name: echo or http_fetch. |
args |
object | {} |
Arguments for the built-in. Values are literals or $in references. |
There are exactly two built-ins:
echo¶
Returns the value it is given, unchanged. It touches no network — it exists to test that tool dispatch and argument templating work.
| Argument | Type | Default | Description |
|---|---|---|---|
value |
any | null |
The value echoed back verbatim. |
{
"id": "echo_it",
"type": "tool",
"kind": "activity",
"config": { "tool": "echo", "args": { "value": "$in" } }
}
http_fetch¶
Performs an HTTP GET and returns the response as an object with three keys: status (the integer status code), body (the response text), and headers (an object). A non-200 status is a normal return value, not an error — only a transport failure (a timeout, an unreachable host, a refused connection) binds the node's err port.
| Argument | Type | Default | Description |
|---|---|---|---|
url |
string | (required) | The absolute http(s) URL to fetch. |
timeout_s |
number | 10 |
Per-request timeout in seconds. |
{
"id": "fetch_page",
"type": "tool",
"kind": "activity",
"config": { "tool": "http_fetch", "args": { "url": "$in", "timeout_s": 15 } }
}
Argument names use snake_case
http_fetch takes timeout_s, not timeoutSeconds. Built-in argument names are the function's own parameter names. This is distinct from the REST tool's timeoutSeconds config field below.
The REST (HTTP) tool¶
The REST kind calls any HTTP endpoint. In the inspector you configure the URL template, the method, an optional connection for authentication, and — for capability tools — a parameter schema. The remaining fields are edited in the node's Code view (the per-node JSON editor).
| Field | Type | Default | Description |
|---|---|---|---|
tool |
string | (required) | A logical name for the tool. |
urlTemplate |
string | null |
The request URL. $in placeholders are filled in (from the wired input in step mode, or from the model's arguments in capability mode). |
method |
enum | null |
One of GET, POST, PUT, PATCH, DELETE. |
connection |
string | null |
The id of an http_auth connection that supplies authentication server-side. The IR stores the id, never the secret. |
headers |
object | {} |
Extra request headers. |
bodyTemplate |
object | null |
The request body for POST/PUT/PATCH. For GraphQL, a {"query": …, "variables": {…}} shape. |
parameterSchema |
object | null |
A JSON Schema describing what the model fills in. Required when the tool is a capability. |
responseMap |
string | null |
A mini-JSONPath (e.g. $.data.items[0]) that reshapes the response before it leaves the out port. |
idempotencyKey |
string | null |
Sent as an Idempotency-Key header; {runId} and {nodeId} are expanded. |
timeoutSeconds |
number | null |
Per-request timeout. Defaults to 30 seconds when unset. |
description |
string | null |
One line telling the model when to call the tool (capability mode). |
args |
object | {} |
Templated values for the URL/body placeholders in step mode. |
Behavior of an HTTP call¶
- A non-2xx status is a normal result. The
outport receives{status, body, headers}for any status the server returns. Only a transport failure (timeout, DNS failure, connection refused) or a failure while setting up authentication bindserr. - Redirects are not followed. A 3xx response comes back as an ordinary value on
out. This protects your authentication headers from leaking to a redirect target. responseMapnever fails a 2xx. If the JSONPath does not match anything, the unmapped response is returned instead of an error.- A connection's
baseUrl(if set) prefixes a relativeurlTemplate, and a connection's staticheadersmerge underneath the node's own headers.
Authentication with connections and secrets¶
A REST tool never carries a credential in the agent document. Instead it references a connection by id, and the credential lives encrypted on your machine. When the tool runs, the connection is resolved server-side, inside the step — the secret is injected into the request there and never appears in the IR or in observability data.
Create a connection¶
In the editor, click empty canvas so nothing is selected. The inspector then shows a Connections (tool / MCP auth) panel. Choose New connection, then:
- Give it a Name.
- Set Kind to
http_auth. - Put non-secret settings — the auth type, header names, base URL — in Config (non-secret JSON).
- Put the credential in the Secret (write-only) field. It is encrypted at rest and never shown again.
Back on the REST tool node, pick the connection from the connection (http auth) dropdown.
Rotating a secret does not change the agent version
A connection's secret is stored under a stable reference, so editing (rotating) the secret leaves every agent's content hash unchanged. See Versioning for why the hash matters.
Auth types¶
Set auth.type in the connection's config. The secret you supply is used according to the type:
auth.type |
The secret is… | What the tool sends |
|---|---|---|
bearer |
the token | Authorization: Bearer <secret> |
api_key |
the key | The key in a header named by auth.header (default X-API-Key) |
basic |
the password | Authorization: Basic base64(<auth.username>:<secret>) |
oauth2_client_credentials |
the client secret | POSTs auth.tokenUrl (grant_type=client_credentials, auth.clientId, optional auth.scope), then sends the returned access token as a bearer |
If a connection has no auth type or no secret, the request proceeds without authentication.
A connection config for a bearer-token API looks like:
…with the token itself supplied in the write-only Secret field.
Encryption key
Encrypted secrets are keyed by THEYGENT_SECRET_KEY. If you do not set it, an ephemeral key is generated with a loud warning and your stored secrets will not survive a restart. See the environment reference.
The success / error contract¶
On success, a tool binds its return value to out (and any other non-error handle). On failure it binds the error message to err and the run keeps going — a tool error is structured output, not a crashed run. Downstream nodes fed only by the un-taken branch are skipped. Wire the err port somewhere (an output node, a fallback branch) if you want to handle failures; leave it unwired to simply stop that branch.
Worked example — a tool as a step¶
Fetch a URL, then summarise the page with an llm.
graph LR
in([input]) --> fetch["tool · http_fetch"]
fetch -->|out| sum["llm · summarize"]
sum --> out([output])
The input value (a URL string) flows into http_fetch via $in; the fetched {status, body, headers} object flows to the llm, whose message drills into it with $in.in.body. The tool's err handle is left unwired here, so a transport failure leaves the out branch dead and the run completes with an honest "upstream error" note instead of a value.
Worked example — a tool as a capability¶
Give an assistant the ability to search, and let it decide when to call it.
graph LR
in([input]) --> llm["llm · assistant"]
llm --> out([output])
tool["tool · search_web"] -. tool .-> llm
The search_web node's use handle is wired into the llm's tools port (the dotted tool edge). Because it is a capability, its URL placeholders are filled by the model's function arguments, and its parameterSchema tells the model what to supply:
{
"id": "search_web",
"type": "tool",
"kind": "activity",
"config": {
"tool": "search_web",
"urlTemplate": "https://api.example.com/search?q=$in.query",
"method": "GET",
"connection": "con_01hzy…",
"description": "Search the web for a query and return the top results.",
"parameterSchema": {
"type": "object",
"properties": { "query": { "type": "string", "description": "the search query" } },
"required": ["query"]
}
}
}
The $in.query placeholder in the URL and the query property in the schema must line up: every placeholder needs a matching property, and every property needs a placeholder. A mismatch is a validation error caught before the agent runs.
Works well with¶
- The llm node — the
toolsport that turns a tool into a model capability. - MCP tools — tools hosted by external servers, wired the same way.
- Input references — the
$ingrammar for templating arguments and URLs. - Observability — inspect a tool node's input and output in the run waterfall.