Create a Client Tool
Scaffold a new AtlasTool named $ARGUMENTS and wire it into the app.
Background
Client tools let the Atlas Agent invoke logic that runs in the browser — rendering charts, querying local state, showing UI panels, triggering navigation, etc. The agent decides when to call the tool; the app executes it and returns a result.
The flow is:
- Agent responds with a
clientToolaction - The library validates the arguments against the TypeBox schema
execute()runs in the browser and returns{ output, details }output(string) is sent back to the agent as the tool resultdetails(any shape) is available onmessage.toolCallsfor the UI to render
Step 1 — Understand the codebase
Before writing anything, read:
- The file where
useAtlasChatis called (likelysrc/App.tsx) to find wheretoolsis passed - Any existing tool definitions to match the file/naming conventions
Step 2 — Define the tool
Create the tool as a typed constant. Use Type from @cognite/dune-industrial-components/atlas-agent to
define the parameters schema — this gives both compile-time types and runtime validation.
import { Type } from "@cognite/dune-industrial-components/atlas-agent";
import type { AtlasTool } from "@cognite/dune-industrial-components/atlas-agent";
export const myTool: AtlasTool = {
name: "my_tool", // snake_case — this is what the agent uses to invoke it
description:
"One sentence describing what this tool does and when the agent should call it.",
parameters: Type.Object({
exampleParam: Type.String({ description: "What this param is for" }),
optionalNum: Type.Optional(Type.Number({ description: "..." })),
}),
execute: async (args) => {
// args is fully typed from the schema above
// Do the work here — call APIs, update state, render UI, etc.
return {
output: "Plain text summary sent back to the agent",
details: {
// Any structured data you want available in the UI via message.toolCalls
},
};
},
};
TypeBox quick reference
| Schema | Usage |
|---|---|
Type.String() | string |
Type.Number() | number |
Type.Boolean() | boolean |
Type.Literal("foo") | exact value |
Type.Union([Type.Literal("a"), Type.Literal("b")]) | enum |
Type.Array(Type.String()) | string[] |
Type.Object({ ... }) | object |
Type.Optional(...) | mark any field optional |
Always add a description to each field — the agent uses these to understand what to pass.
Step 3 — Wire into useAtlasChat
Find the useAtlasChat call and add the tool to the tools array:
const { messages, send, ... } = useAtlasChat({
client: isLoading ? null : sdk,
agentExternalId: AGENT_EXTERNAL_ID,
tools: [myTool], // add here
});
Step 4 — Render tool results (if needed)
If the tool returns structured details, render them in the message list.
message.toolCalls is a ToolCall[] — one entry per tool call (client-side and server-side) in call order.
{msg.toolCalls?.map((tc, i) => (
// tc.name — tool name
// tc.output — the string sent back to the agent
// tc.details — your structured data (cast to your known shape)
<MyToolOutput key={i} data={tc.details as MyToolDetails} />
))}
Done
The agent can now invoke $ARGUMENTS. Describe what it does clearly in the description
field — the agent relies on that string to decide when and how to call the tool.