Skip to main content

Tools

External capabilities an agent can call — an HTTP endpoint or an MCP server.

Overview

A tool is what an agent of yours calls out to. For the opposite direction — connecting Claude, Cursor or another MCP client to your naturali account — see MCP Server.

A tool is a thin surface onto a runtime tool. What the tool is (name, parameters, execution config) lives on the runtime and is read from there when shaping responses; naturali stores nothing of its own about it. Attach tools to an Agent via its tool_bindings.

See the OpenAPI spec for the full endpoint and schema reference, or browse it rendered under API Reference → Tools.

Data Model

Tool

FieldTypeDescription
idstringPublic tool ID (tool_ prefix).
project_idstringThe owning project.
namestringHuman-readable label.
descriptionstring, nullable
typehttp | mcpThe tool's execution kind.
parametersobjectJSON-Schema-shaped parameters the tool accepts.
executeobject, nullableHTTP execution config (type: http only).
output_mappingobject, nullableHow to map the raw response into a result.
preset_parametersobject, nullableParameters fixed by the platform rather than the caller.
mcpobject, nullableMCP server config (type: mcp only).
actionsstring[], nullableAllowlist of MCP actions exposed (type: mcp only).
denied_actionsstring[], nullableDenylist of MCP actions (type: mcp only).
context_keysstring[], nullableWhich context keys may reach this tool as headers. null forwards every key the caller supplied. See Per-user credentials.
has_headersbooleanWhether execute/MCP auth headers are configured — never returns the header values themselves.
created_atstring (date-time)
updated_atstring (date-time)

Key Concepts

Auth headers are write-only

Auth headers on a tool's execute/mcp config are accepted on write, never returned — a read reports only has_headers.

Per-user credentials reach a tool as a header

A tool endpoint often needs to act as the person the run is for, not as the project: one user's calendar, one user's orders, one user's account on a third-party API. Putting that credential in the tool's own headers cannot express it — those headers are fixed at write time and shared by every call.

The credential travels per call instead, in the tool_context a caller attaches when it starts work. The runtime forwards each entry as a request header on every http/mcp tool call the agents make, named X-Naturali-Context- followed by your key verbatim:

X-Naturali-Context-ocaToken: usr_token_V1StGXR8Z5jdHi6B

Two properties are worth reading twice. The key is appended verbatim — no character is re-cased and no separator is inserted, so ocaToken and oca_token are two different keys producing two different headers. And the prefix is always applied: a caller cannot name an arbitrary header, which is what stops caller-supplied data from overwriting a credential the tool configured for itself.

Read the header case-insensitively. HTTP field names are case-insensitive and HTTP/2 sends them lowercased, so your endpoint will usually see x-naturali-context-ocatoken however you spelled the key:

// Node / Express / Koa — incoming header names are lowercased for you
const ocaToken = req.headers['x-naturali-context-ocatoken'];

Landing the value in Authorization

Most targets want the credential in the header they already use, not in a prefixed one. The tool says so, with a {{context:<key>}} reference in its own headers — the tool knows the header shape its endpoint expects, and the caller only supplies the value:

Authorization: 'Bearer {{context:ocaToken}}'

A reference is legal in headers and nowhere else, url included: a context value comes from the caller, so it must never steer the outbound request. If the key is missing from the tool_context at call time the tool call fails, rather than sending Bearer with nothing after it and coming back as an opaque 401 several steps from the mistake.

Confine it with context_keys

By default every key in the bag goes to every tool the agent can call, including endpoints you do not control. context_keys is the allowlist that stops that: set it to the keys a tool legitimately needs, and a credential meant for one endpoint stops egressing to the rest of the tool set. [] forwards none, and null (the default) forwards all.

A key the tool consumes through a {{context:...}} reference in its own headers is substituted either way — the tool declared that header itself.

Create such a tool — a per-user credential in Authorization, and nothing else in the bag reaching it:

naturali create-tool \
--project-id proj_V1StGXR8Z5jdHi6B \
--name oca \
--type mcp \
--mcp '{"url":"https://mcp.example.com/sse","headers":{"Authorization":"Bearer {{context:ocaToken}}"}}' \
--context-keys ocaToken

Then supply the value when you start the work — see Orchestrations → Handing a run a per-user credential.

The headers are worth trusting only if the endpoint also authenticates the request as coming from your naturali deployment — a shared secret in the tool's own headers, mTLS, or a network restriction. Anyone who can reach the endpoint can set an X-Naturali-Context-* header by hand.

Deleting a tool still in use

Deleting a tool still bound to an agent returns 409 tool_in_use. Detach it from every agent's tool_bindings first.

output_mapping sees the raw response only

output_mapping's JSON Logic is evaluated against the raw HTTP response body directly — a var path has no result. prefix ({ "var": "data.0.score" }, not { "var": "result.data.0.score" }; that prefixed form is Boards' on_complete syntax, a different evaluation context). It has no access to the call's own resolved parameters/input_mapping either, so it cannot echo a request field back into the result — only the target endpoint's response shape decides what comes back. To carry a field through a call that does not return it, see Boards → carrying a field past a column that doesn't return it.

http execution always sends a body, even for GET

An http tool's resolved parameters are sent as the request body regardless of execute.method. Most servers ignore a body on a GET request, but some reject it outright; if a GET tool call fails against a target that behaves this way, that is usually why — switching the target's own route to accept POST, where one is available, sidesteps it.

Examples

naturali create-tool \
--project-id proj_V1StGXR8Z5jdHi6B \
--name get-weather \
--type http