Build
Tools
Choose where a tool runs, understand when an Invocation parks, and make external side effects safe to retry.
Choose a tool mode by asking one question: who should run the code?
| Mode | Runner | Best fit |
|---|---|---|
builtin | nvoken | The fixed nvoken_fetch public-page reader |
host | Your application or worker | Long work, private systems, browser actions, human input |
callback | Your public HTTPS endpoint | Unattended work that finishes within ten seconds |
| MCP | A remote MCP server | An existing streamable-HTTP tool server |
| Provider | The model provider | Anthropic server-side web search |
You can mix these modes in one execution spec. Tools are declared on each Invocation; there is no nvoken tool registry.
Host tools
A host tool has a name, description, and bounded JSON input schema:
{
"name": "lookup_order",
"description": "Look up the current state of one order.",
"mode": "host",
"input_schema": {
"type": "object",
"properties": { "order_id": { "type": "string" } },
"required": ["order_id"],
"additionalProperties": false
}
}When the model calls it, the Invocation moves to waiting. nvoken exposes the
pending ToolCall with a stable ID and holds no execution lease. Your host runs
the tool and submits the result:
POST /v1/invocations/{invocation_id}/tool-results
{
"results": [{
"tool_call_id": "tcal_…",
"content": { "order_id": "1842", "state": "shipped" },
"is_error": false
}]
}The first accepted result wins. Retrying the same result is safe; changing it
returns tool_result_conflict. A partial batch leaves the turn waiting.
The SDK Agent facades can attach local handlers and run this park/answer/resume loop for you. Use the lower-level handle when another queue or process owns the tool.
Ask the user with a host tool
A structured question is an ordinary host tool named ask_user. The TypeScript
SDK ships the same question shape for confirm, select, multiselect, and text
input.
import { askUserTool, Client } from "@deepnoodle/nvoken";
const agent = new Client().agent({
agentKey: "support",
spec: { tools: [askUserTool((question) => ui.prompt(question))] },
});If the person dismisses the prompt, return { canceled: true } as a normal
tool result. An error tells the model the tool broke and often causes an
unhelpful retry.
The wait is unbounded by default. A question can remain parked for days without holding a worker or consuming the Invocation's active time.
Finish host tools with nobody watching
Set notify on admission to receive signed invocation.waiting and
invocation.settled webhooks. A waiting notification includes the pending
ToolCall IDs. The receiver reads the authoritative Invocation, claims each
side-effecting call in its own database, runs it, and submits the result.
Return 2xx from the notification receiver within ten seconds after verifying
and enqueueing the work. Delivery is at least once, and the notification's
Idempotency-Key is stable across retries.
An attached client and a webhook worker may race to answer the same host tool.
Result acceptance is deduplicated, but the external effect is not. Take a claim
on tool_call_id before charging a card, sending a message, or changing data.
Callback tools
Use mode: callback when nvoken should deliver the call directly to your public
HTTPS endpoint. The turn parks, nvoken signs and retries the request, and the
endpoint returns the result in its response.
The response must arrive within ten seconds and use this envelope:
{ "content": { "available": true }, "is_error": false }The signature is HMAC-SHA256 over:
v1.<delivery_id>.<unix_timestamp>.<raw_request_body>Verify the version, key ID, timestamp, and signature against the raw body before
parsing JSON. Deduplicate on tool_call_id or the delivery
Idempotency-Key. Callback delivery is at least once.
A slow or queue-backed operation belongs in host mode. Returning 202 and
finishing later is not a callback completion protocol.
Remote MCP
spec.mcp_servers accepts public HTTPS streamable-HTTP servers. Each descriptor
includes a name, URL, allowed tools, optional encrypted headers, and discovery
and call timeouts.
Probe the projected names before admission:
nvoken mcp list-tools \
--name support \
--url https://mcp.example.com/rpcThe allowlist is enforced. Secret headers are encrypted for the Invocation and never returned in specs, streams, transcripts, errors, or logs. nvoken also checks public-address resolution, redirects, and response bounds.
Recovery repeats an unfinished MCP call only when it was admitted as safe to repeat. A possibly mutating call with an unknown outcome is reported as unknown instead of being issued twice.
Builtin and provider tools
The only general builtin is:
{ "name": "nvoken_fetch", "mode": "builtin" }It fetches public HTTPS text, follows guarded redirects, and converts HTML to Markdown within fixed time and size limits. It does not use caller credentials or ambient HTTP proxies.
spec.provider_tools currently exposes web_search for qualified Anthropic
models. The provider runs and bills that search inside generation. Search spend
is not included in max_estimated_cost_usd; bound it with max_uses.
What nvoken does not decide
Host and callback tool declarations do not carry read-only, destructive, permission, preview, or approval policy. Keep that metadata and enforcement in your application.
A stable ToolCall ID makes result submission idempotent. It does not turn an arbitrary external side effect into exactly-once work.