Tool composer
Chain N tools into a single macro tool — a fixed recipe the model can invoke with one schema.
Some agent capabilities are always the same multi-step recipe: fetch → parse → rerank → summarize. Letting the model pick each step adds latency and unreliability; baking the recipe into a single tool gives the model one lever and you predictable behavior.
composeTool takes N sub-tools, a mapper per step, and an optional
finalizer — and returns one ToolDefinition the model sees as a
single tool.
Install
Ships in @agentskit/core under a subpath:
import { composeTool } from '@agentskit/core/compose-tool'Chain three tools into one
import { composeTool } from '@agentskit/core/compose-tool'
import { defineTool } from '@agentskit/core'
import { fetchUrl, webSearch } from '@agentskit/tools'
const summarize = defineTool({
name: 'summarize',
schema: { type: 'object', properties: { text: { type: 'string' } }, required: ['text'] } as const,
execute: async ({ text }) => `summary: ${text.slice(0, 80)}...`,
})
const research = composeTool<{ query: string }>({
name: 'research',
description: 'Search the web, fetch the top result, summarize it.',
schema: { type: 'object', properties: { query: { type: 'string' } }, required: ['query'] },
steps: [
{
tool: webSearch(),
mapArgs: ({ args }) => ({ query: args.query, limit: 1 }),
},
{
tool: fetchUrl(),
mapArgs: ({ state }) => ({ url: (state as { results: { url: string }[] }).results[0]!.url }),
},
{
tool: summarize,
mapArgs: ({ state }) => ({ text: String(state) }),
},
],
})Step contract
Each steps[i]:
{
tool,
mapArgs({ args, state, prior }) => Record<string, unknown>,
mapResult?(result, { args, state, prior }) => newState,
stopWhen?(state, { args, prior }) => boolean, // short-circuit the chain
}args— the macro tool's original input.state— output of the previous step (aftermapResult).prior— every intermediate output in declaration order.
Return a finalize({ args, prior, state }) to produce a different
return value than the last step's state.
Stop when done
A step can short-circuit the rest of the chain if its stopWhen
predicate returns true — useful for early termination in cache-hit
scenarios.
{
tool: cacheCheck,
mapArgs: ({ args }) => ({ key: args.query }),
stopWhen: state => state !== null,
}Observability
composeTool({
...,
onStep: e => logger.debug('[compose]', e),
})Events: start / end / skip, with step index + tool name.
See also
- MCP bridge — expose composed tools to MCP hosts
- Mandatory sandbox
- Custom adapter
MCP bridge (bidirectional)
Consume any MCP server as AgentsKit tools, or expose your AgentsKit tools to MCP hosts — over stdio or any transport.
Vue / Svelte / Solid / React Native / Angular
One package per framework. Same ChatReturn contract as @agentskit/react — pick the binding that matches your stack.