Tools
Your agent needs to look up an order. The LLM doesn’t know your order database — it knows what you tell it via the tool’s JSON schema. Tools are the contract between LLM intent (“call this with these args”) and your code (“here’s what happens”). Get the schema right and the LLM uses the tool well; get it wrong and you debug schema-vs-args mismatches at 2 AM.
What a tool is
Section titled “What a tool is”A Tool has two parts:
{ schema: { name, description, inputSchema }, // what the LLM sees execute: async (args) => result, // what runs when LLM calls it}The inputSchema is a JSON Schema describing the args the LLM should produce. It’s the LLM’s contract — it generates well-formed args based on that shape.
defineTool — the flat builder
Section titled “defineTool — the flat builder”defineTool is a flatter helper that puts name + description + inputSchema at the top level instead of nested under schema:
import { defineTool } from 'agentfootprint';
const lookup = defineTool<{ orderId: string }, string>({ name: 'lookup_order', description: 'Look up an order by ID', inputSchema: { type: 'object', properties: { orderId: { type: 'string' } }, required: ['orderId'], }, execute: async ({ orderId }) => `Order ${orderId}: shipped, $299`,});
const agent = Agent.create({ provider }).tool(lookup).build();Type parameters <TArgs, TResult> flow into execute so your handler is fully typed.
Example — agent + tool registered inline
Section titled “Example — agent + tool registered inline”const agent = Agent.create({ provider: provider ?? exampleProvider('feature', { respond: weatherRespond }), model: 'mock', maxIterations: 5,}) .system('You answer weather questions using the `weather` tool.') .tool({ schema: { name: 'weather', description: 'Get current weather for a city.', inputSchema: { type: 'object', properties: { city: { type: 'string' } }, required: ['city'], }, }, execute: async (args) => `${(args as { city: string }).city}: sunny, 72°F`, }) .build();.tool(...) accepts both shapes (flat {schema, execute} or defineTool output) — the agent doesn’t care which.
Bulk-register from MCP
Section titled “Bulk-register from MCP”Pull a whole MCP server’s tool surface into the agent in one call:
// Connect once at startup. In production: use a real transport.const fileServer = await mcpClient({ name: 'file-server', transport: { transport: 'stdio', command: 'npx', args: ['fake-mcp'] }, _client: fakeServer, // ← test injection; remove for real MCP});
// Agent picks up all the server's tools at once.const agent = Agent.create({ provider: provider ?? mock({ reply: '/tmp/notes.md and /tmp/todo.txt are present.' }), model: 'mock', maxIterations: 1,}) .system('You answer file-system questions using the MCP tools provided.') .tools(await fileServer.tools()) .build();agent.tools(arr) is the bulk-register companion to agent.tool(t). Tool-name uniqueness is validated at .build() time — if an MCP-imported tool collides with a manually-defined tool, the build throws with a clear error.
For mock-first development of MCP integrations without spawning a subprocess, use mockMcpClient({ tools }) — same McpClient interface, in-memory implementation. See MCP integration for the full surface.
Tool errors
Section titled “Tool errors”When execute throws, the framework catches it, reports it to the LLM as a tool error, and continues the loop. The LLM sees the error message and can decide to retry with different args, try a different tool, or surface to the user. See Error handling for the typed error contract + retry decorators.
Anti-patterns
Section titled “Anti-patterns”- Don’t make
executesynchronous — it must return a Promise. The agent loop is async end-to-end. - Don’t put validation logic in
executefor things JSON Schema can express (type,required,enum). The LLM honors well-formed schemas; redundant runtime checks are noise. - Don’t make tool descriptions ambiguous. “Get data” is bad. “Look up an order by ID; returns status + amount” is good. The LLM picks tools by description.
Next steps
Section titled “Next steps”- MCP integration — connect to any MCP server with
mcpClient({ transport }) - Skills, explained — LLM-activated tool bundles
- Permission gating — restrict which tools an agent can call