n4nAI

Node.js function calling with Claude's Messages API

Hands-on tutorial for nodejs claude messages api function calling: build a TypeScript agent that invokes local tools via Anthropic's Messages API step by step.

n4n Team1 min read264 words

Audio narration

Coming soon — every post will get a voice note here.

Wiring up nodejs claude messages api function calling requires more than a single SDK call—you have to loop tool results back into the conversation until Claude stops emitting tool_use blocks. This tutorial builds a minimal TypeScript agent that calls local functions for weather and arithmetic using the Anthropic Messages API and the official SDK.

Prerequisites

  • Node.js 18 or newer (fetch is global)
  • An Anthropic API key (ANTHROPIC_API_KEY in env)
  • Familiarity with TypeScript and async/await

Initialize a project and install deps:

mkdir claude-tools && cd claude-tools
npm init -y
npm install @anthropic-ai/sdk
npm install -D typescript tsx

Add a tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["*.ts"]
}

Set your key:

export ANTHROPIC_API_KEY="sk-ant-..."

Define the tool schemas

Claude needs JSON Schema for each tool. We declare two: get_weather and calculate.

const tools = [
  {
    name: "get_weather",
    input_schema: {
      type: "object",
      properties: {
        city: { type: "string", description: "City name, e.g. 'Berlin'" }
      },
      required: ["city"]
    }
  },
  {
    name: "calculate",
    input_schema: {
      type: "object",
      properties: {
        expression: { type: "string", description: "e.g. '3 * (4 + 5)'" }
      },
      required: ["expression"]
    }
  }
] as const;

Implement local handlers

The SDK does not execute code. You map tool_use blocks to real functions:

function getWeather(city: string): string {
  // stub: real impl would call an API
  const fake: Record<string, number> = { berlin: 12, paris: 18, tokyo: 24 };
  const t = fake[city.toLowerCase()] ?? 20;
  return `${city}: ${t}°C`;
}

function calculate(expr: string): string {
  // safe-ish eval for demo only
  const sanitized = expr.replace(/[^0-9+\-*/().\s]/g, "");
  try {
    const result = Function(`"use strict"; return (${sanitized})`)();
    return String(result);
  } catch {
    return "error";
  }
}

Send the first request

Build the conversation and call client.messages.create. Use claude-3-5-sonnet-20241022 (a real model id).

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

let messages: Anthropic.MessageCreateParams["messages"] = [
  { role: "user", content: "What's the weather in Berlin and what is 3 * (4 + 5)?" }
];

const first = await client.messages.create({
  model: "claude-3-5-sonnet-20241022",
  max_tokens: 1024,
  tools: tools as unknown as Anthropic.Tool[],
  messages
});

console.log(JSON.stringify(first.content, null, 2));

Expected checkpoint output (abridged):

[
  {
    "type": "text",
    "text": "I'll check both for you."
  },
  {
    "type": "tool_use",
    "id": "toolu_01A",
    "name": "get_weather",
    "input": { "city": "Berlin" }
  },
  {
    "type": "tool_use",
    "id": "toolu_01B",
    "name": "calculate",
    "input": { "expression": "3 * (4 + 5)" }
  }
]

Run the tool loop

Append the assistant message, execute each tool, and return results as a user message with tool_result blocks.

messages.push({ role: "assistant", content: first.content });

const toolResults = first.content
  .filter((b): b is Anthropic.ToolUseBlock => b.type === "tool_use")
  .map((block) => {
    let content = "";
    if (block.name === "get_weather") {
      content = getWeather((block.input as any).city);
    } else if (block.name === "calculate") {
      content = calculate((block.input as any).expression);
    }
    return {
      type: "tool_result" as const,
      tool_use_id: block.id,
      content
    };
  });

messages.push({ role: "user", content: toolResults });

const second = await client.messages.create({
  model: "claude-3-5-sonnet-20241022",
  max_tokens: 1024,
  tools: tools as unknown as Anthropic.Tool[],
  messages
});

console.log(second.content.find(b => b.type === "text")?.text);

Expected final text:

The weather in Berlin is 12°C, and 3 * (4 + 5) equals 27.

Full agent loop

Wrap it in a while to handle multi-step chains:

import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();
const model = "claude-3-5-sonnet-20241022";

let messages: Anthropic.MessageCreateParams["messages"] = [
  { role: "user", content: "Weather in Paris then add 10 to that temperature." }
];

for (let i = 0; i < 5; i++) {
  const res = await client.messages.create({
    model,
    max_tokens: 1024,
    tools: tools as unknown as Anthropic.Tool[],
    messages
  });

  messages.push({ role: "assistant", content: res.content });

  const uses = res.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use"
  );
  if (uses.length === 0) {
    console.log(res.content.find(b => b.type === "text")?.text);
    break;
  }

  const results = uses.map((u) => {
    const input = u.input as any;
    const content =
      u.name === "get_weather" ? getWeather(input.city)
      : u.name === "calculate" ? calculate(input.expression)
      : "unknown tool";
    return { type: "tool_result" as const, tool_use_id: u.id, content };
  });

  messages.push({ role: "user", content: results });
}

This loop terminates when Claude returns no tool_use blocks. In production, add timeout and error handling.

Notes on routing and fallback

If you front the Messages API with an OpenRouter-class gateway such as n4n.ai, you get one OpenAI-compatible endpoint for 240+ models and automatic fallback when a provider is degraded, but the nodejs claude messages api function calling loop above stays identical—only the client base URL changes.

Keep tool schemas strict. Claude will obey required fields; missing inputs fail fast. For latency, stream responses and parse tool_use deltas incrementally.

That’s the whole pattern: define tools, send, loop on tool_use, return tool_result.

Tagsnodejsclaudemessages-apifunction-calling

Written by

n4n Team

The team building n4n — a single OpenAI-compatible API in front of 240+ models, with automatic fallback, load balancing and pay-per-token metering.

More from n4n Team →

All function calling in typescript/node.js posts →