toAnthropic
JavaScript · package toolnexus · SPEC §4 · js/src/adapters.ts
function toAnthropic(tools: Tool[]): { name: string description: string input_schema: JSONSchema}[]Turns a Tool[] into the tools array an Anthropic Messages request expects. Anthropic’s shape is
the flattest of the three providers — no wrapper object, no nesting; each tool is one plain record
whose only rename is inputSchema → input_schema.
When to use it
Section titled “When to use it”When you are calling the Anthropic Messages API yourself — with @anthropic-ai/sdk, a raw
fetch, or through Bedrock / Vertex — and need schema to put in the request body. Anything that
speaks the Messages API takes this array verbatim.
Why this and not the alternative
Section titled “Why this and not the alternative”tk.toAnthropic() on a Toolkit is this same function applied
to that toolkit’s tools — use the method when you have a toolkit, the free function when you have a
bare array (a filtered subset, a hand-picked pair, one tool for a narrow turn).
Examples
Section titled “Examples”1. One tool to Anthropic schema
Section titled “1. One tool to Anthropic schema”import assert from "node:assert"import { toAnthropic, defineTool } from "toolnexus"
const weather = defineTool({ name: "get_weather", description: "Current weather for a city", inputSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"], }, run: async ({ city }) => `sunny in ${city}`,})
const schema = toAnthropic([weather])
assert.equal(schema.length, 1)assert.equal(schema[0].name, "get_weather")assert.equal(schema[0].description, "Current weather for a city")// The ONLY rename: inputSchema -> input_schema. No { type: "function" } wrapper.assert.deepEqual(schema[0].input_schema.required, ["city"])assert.equal((schema[0] as any).function, undefined)
console.log("ok:", schema[0].name)Compare with toOpenAI, which nests the same three fields
under { type: "function", function: {...} }. Same information, different envelope — that is the
entire job of an adapter.
2. Feeding it straight into a Messages request body
Section titled “2. Feeding it straight into a Messages request body”The output drops into tools untouched. Nothing else in the body needs transforming.
import assert from "node:assert"import { toAnthropic, defineTool } from "toolnexus"
const tools = [ defineTool({ name: "search", description: "Search the docs", inputSchema: { type: "object", properties: { q: { type: "string" } }, required: ["q"] }, run: async ({ q }) => `results for ${q}`, }), defineTool({ name: "ping", description: "Health check", inputSchema: { type: "object", properties: {} }, run: async () => "pong", }),]
const body = { model: "claude-sonnet-4-5", max_tokens: 1024, messages: [{ role: "user", content: "search for adapters" }], tools: toAnthropic(tools),}
// Order is preserved, one entry per tool.assert.equal(body.tools.length, 2)assert.deepEqual(body.tools.map((t) => t.name), ["search", "ping"])// Plain JSON — no classes, no cycles, safe to serialise.assert.ok(JSON.stringify(body).length > 0)
console.log("ok:", body.tools.map((t) => t.name).join(", "))3. Round-tripping a tool_use block back to the tool
Section titled “3. Round-tripping a tool_use block back to the tool”Schema out, tool call in. Anthropic returns a tool_use content block whose name is the same
name you advertised and whose input is already a parsed object — no JSON.parse step.
import assert from "node:assert"import { toAnthropic, defineTool } from "toolnexus"
const weather = defineTool({ name: "get_weather", description: "Current weather for a city", inputSchema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] }, run: async ({ city }) => `sunny in ${city}`,})
const tools = [weather]const schema = toAnthropic(tools)
// What a model would send back for that schema.const block = { type: "tool_use", id: "toolu_1", name: "get_weather", input: { city: "Chennai" } }
const called = tools.find((t) => t.name === block.name)assert.ok(called, "the advertised name resolves back to the tool")
const res = await called.execute(block.input)assert.equal(res.output, "sunny in Chennai")assert.equal(res.isError, false)
// The block you send on the next turn.const toolResult = { type: "tool_result", tool_use_id: block.id, content: res.output, is_error: res.isError,}assert.equal(toolResult.tool_use_id, "toolu_1")
// An empty tool list is valid — it just means "no tools this turn".assert.deepEqual(toAnthropic([]), [])
console.log("ok:", schema[0].name, "->", res.output)| Path | From | Notes |
|---|---|---|
[].name |
Tool.name |
What the model calls back with in a tool_use block. |
[].description |
Tool.description |
What the model reads to decide whether to call it. |
[].input_schema |
Tool.inputSchema |
Renamed — inputSchema → input_schema. |
There is no envelope key and no type discriminator: an Anthropic tool entry has exactly these
three fields, and the input array’s order is preserved one-for-one.
See also
Section titled “See also”toOpenAI·toGeminicreateToolkit—tk.toAnthropic()is this, applied to the toolkitcreateClient— calls the adapter for youTool— the input shape every adapter reads