Skip to main content
← Journal·AI AutomationsOctober 7, 2026·11 min read

Claude API Function Calling: Tools, Streaming and MCP

Claude API function calling in TypeScript: define tools, run the tool_use loop with retries, control tool choice, stream input and when to use an MCP server.

A

Founder of IImagined.ai

Quick answer

Claude API function calling, which Anthropic calls tool use, works by sending tool definitions in the tools parameter, running your function when the response stops with tool_use, and returning a tool_result. This tutorial builds that loop once in TypeScript with parallel calls, error results and a turn limit, explains tool_choice on current models, and shows when an MCP server beats hand-written tools.

Claude API function calling works by passing tool definitions (a name, description and JSON Schema) in the Messages API's tools parameter; when Claude wants one, the response stops with stop_reason "tool_use" and a tool_use block holding the arguments. Your code runs the function and sends the output back as a tool_result in the next user message, and the loop repeats until Claude answers in plain text.

API shapes checked October 2026 against Anthropic's tool use documentation and the official TypeScript SDK, @anthropic-ai/sdk. Model IDs and prices change; confirm them on Anthropic's models page before shipping.

Anthropic's docs cover each piece separately. This tutorial builds the whole loop once, in production shape: typed tools, parallel calls, error results, a turn limit, and the tool-choice rules that changed with the current model generation. It ends with the question most teams hit next: when to stop hand-writing tools and put them behind an MCP server. For setup basics first, see our Claude API tutorial.

What you will have at the end

  • A reusable ask() function that runs Claude's tool loop to completion.
  • Tools with strict schemas, so arguments always validate.
  • Correct handling of parallel calls and tool errors.
  • A decision rule for function calling versus an MCP server.
The Claude tool-use loop
  1. 01
    Request with tools

    messages + tools

  2. 02
    stop_reason: tool_use

    One or more tool_use blocks

  3. 03
    Your code runs them

    In parallel, errors caught

  4. 04
    tool_result message

    All results in one user turn

  5. 05
    Repeat until text

    stop_reason: end_turn

Prerequisites

Before you start
  • Node.js and a TypeScript project
  • npm install @anthropic-ai/sdk
  • An Anthropic API key in ANTHROPIC_API_KEY (never in source code)
  • One real function you want Claude to call, with clear inputs
  • A spending limit set in the Anthropic Console while you test

If you do not have a key yet, our guide on getting a Claude API key walks through it. For what each call costs, see our Claude API pricing breakdown.

Claude API function calling: the tool-use loop in TypeScript

The code below is the whole pattern. It defines one tool, calls the API, runs every tool Claude asks for, returns results, and stops when Claude answers in text or after ten turns.

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

const client = new Anthropic(); // reads ANTHROPIC_API_KEY

const tools: Anthropic.Tool[] = [
  {
    name: "get_order_status",
    description: "Look up the status of a customer order by order ID.",
    input_schema: {
      type: "object",
      properties: { order_id: { type: "string", description: "e.g. ORD-1042" } },
      required: ["order_id"],
      additionalProperties: false,
    },
    strict: true,
  },
];

async function runTool(name: string, input: unknown): Promise<string> {
  if (name === "get_order_status") {
    const { order_id } = input as { order_id: string };
    return JSON.stringify(await lookupOrder(order_id)); // your code
  }
  throw new Error("Unknown tool: " + name);
}

export async function ask(question: string): Promise<string> {
  const messages: Anthropic.MessageParam[] = [{ role: "user", content: question }];

  for (let turn = 0; turn < 10; turn++) {            // hard stop on runaway loops
    const response = await client.messages.create({
      model: "claude-opus-5-5",
      max_tokens: 16000,
      tools,
      messages,
    });

    if (response.stop_reason !== "tool_use") {
      return response.content
        .filter((b): b is Anthropic.TextBlock => b.type === "text")
        .map((b) => b.text)
        .join("\n");
    }

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

    const calls = response.content.filter(
      (b): b is Anthropic.ToolUseBlock => b.type === "tool_use",
    );
    const results = await Promise.all(
      calls.map(async (call): Promise<Anthropic.ToolResultBlockParam> => {
        try {
          return { type: "tool_result", tool_use_id: call.id, content: await runTool(call.name, call.input) };
        } catch (err) {
          return { type: "tool_result", tool_use_id: call.id, content: String(err), is_error: true };
        }
      }),
    );
    messages.push({ role: "user", content: results }); // all results, one message
  }
  throw new Error("Tool loop did not finish in 10 turns");
}
What each part does
  1. 1
    Define the tool

    name, a description that says when to use it, and input_schema. strict: true with additionalProperties: false makes arguments match the schema exactly.

  2. 2
    Call messages.create with tools

    Expected result: either a text answer, or stop_reason "tool_use" with tool_use blocks.

  3. 3
    Append the assistant turn unchanged

    Push response.content as-is, so the tool_use blocks (and any thinking blocks) stay in history.

  4. 4
    Run every call

    Promise.all runs parallel calls together. A thrown error becomes a tool_result with is_error: true.

  5. 5
    Return all results in one user message

    Each tool_result carries the matching tool_use_id. Expected result: Claude continues with the data.

  6. 6
    Stop safely

    Return the text when stop_reason is not tool_use; throw after the turn limit so a bug cannot loop forever.

The official SDK also ships a beta tool runner that drives this loop for you, with hooks for approvals and logging. Write the manual loop once to understand it, then consider the runner for production; Anthropic's docs cover both.

Controlling tool choice on current models

The tool_choice parameter decides whether Claude may, must or must not call tools. On the current generation the rules changed: forcing a call with any or a named tool returns a 400 error on Claude Opus 5.5, Claude Sonnet 5.5 and Claude Fable 5.1. Older models still accept it.

tool_choiceBehaviorUse it for
auto (default)Claude decides whether to call a toolNormal agent and assistant flows
noneClaude answers without calling toolsA turn where tools must not run
auto + disable_parallel_tool_useAt most one tool call per turnTools with side effects that must run in order
any / tool (forced)Must call a tool, or a named toolOlder models only; returns a 400 on Opus 5.5, Sonnet 5.5, Fable 5.1

To get a reliable call on current models, keep auto, say in the user or system message which tool to use, and keep strict: true on the tool. Then check whether a tool_use block came back and re-prompt if not. If the only reason for forcing a tool was to get JSON, use structured outputs instead; it is the cleaner tool for that job.

Streaming tool input

For chat interfaces, stream the response with client.messages.stream(...) and take the complete message from finalMessage(). Tool arguments arrive as partial JSON while streaming. Setting eager_input_streaming: true on a tool definition streams large arguments as they are generated rather than in one burst at the end; the trade-off is that the API no longer validates those streamed inputs for you, so parse them defensively and validate against your schema before running the tool. Leave it off for non-streaming requests.

Anthropic tool use API: details that matter in production

  • Descriptions do the routing. Claude chooses tools from their descriptions. Say what the tool does, when to use it and when not to.
  • Parse inputs, do not string-match them. Tool inputs arrive as objects; treat them as data and validate them.
  • Keep results small. Return the fields Claude needs, not a whole database row. Large results cost input tokens on every later turn.
  • Gate side effects. For tools that send email, charge cards or delete data, require a confirmation step in your code before running them.
  • Log every call. Name, input, output and duration make debugging an agent possible.

If you are building a product on top of this loop, our AI SaaS Builder program covers turning it into a shipped app with auth, billing and deployment.

What tool use costs

Tool use is billed like any other Messages API call: input and output tokens at the model's rates. Three things make tool-heavy apps more expensive than they look. First, tool definitions are sent with every request, so long descriptions and many tools add input tokens to each turn. Second, every turn resends the whole conversation, including earlier tool results, so a ten-turn loop pays for the early results ten times. Third, the model may think before calling tools, and thinking is billed as output.

The fixes are simple. Keep descriptions precise rather than long, return only the fields Claude needs, and cap the loop. Put the stable parts of the request, the tools and system prompt, first and use prompt caching so repeated turns read them from cache at a fraction of the price. Then measure: log the usage object from each response and look at cost per completed task, not per request, because a cheaper request that needs more turns is not cheaper.

Claude MCP API: when to move tools to an MCP server

Function calling keeps tools inside one app. The Model Context Protocol packages tools on a server so any MCP client can use them. With Anthropic's MCP connector (beta), the Messages API connects to a remote MCP server for you: you list the server in mcp_servers and reference it with an mcp_toolset entry in tools. Both halves are required.

const response = await client.beta.messages.create({
  model: "claude-opus-5-5",
  max_tokens: 16000,
  betas: ["mcp-client-2025-11-20"],
  mcp_servers: [
    { type: "url", url: "https://mcp.example.com/sse", name: "orders" },
  ],
  tools: [{ type: "mcp_toolset", mcp_server_name: "orders" }],
  messages: [{ role: "user", content: "Where is order ORD-1042?" }],
});
Keep function calling if
  • One app uses the tools
  • Tools touch local state or private networks
  • You need full control of execution and approvals
  • There are only a few tools
Move to an MCP server if
  • Several apps or agents share the same tools
  • You want to use tools in Claude apps and other MCP clients too
  • A vendor already offers an MCP server
  • Tool code should deploy separately from your app

A practical path: start with function calling inside your app, and extract tools into an MCP server once a second consumer needs them. For workflow tools such as n8n, our guide to Claude with n8n covers a no-code route to the same idea.

Troubleshooting

  • 400 on tool_choice. You are forcing a tool on a current model. Switch to auto and instruct in the prompt.
  • Error about missing tool_result. A tool_use block went unanswered. Return a result, even an error result, for every call.
  • Claude never calls the tool. The description does not say when to use it, or the question does not need it. Rewrite the description and test again.
  • Arguments fail your validation. The schema is too loose or the description is vague. Add strict: true with additionalProperties: false, tighten enums and required fields, and describe each property with an example.
  • Loop never ends. A tool keeps returning something Claude retries on. Keep the turn limit, and return clear error messages.

Claude API function calling: FAQ

Does the Claude API support function calling?

Yes. Anthropic calls it tool use. You pass tool definitions with a name, description and JSON Schema in the tools parameter of the Messages API. When Claude decides to call one, the response has stop_reason "tool_use" and a tool_use block with the arguments. Your code runs the function and sends the output back as a tool_result block in the next user message.

How do I force Claude to call a specific tool?

On the current models (Claude Opus 5.5, Claude Sonnet 5.5 and Claude Fable 5.1), forcing a call with tool_choice "any" or a named tool returns a 400 error. Use tool_choice "auto", name the tool in your instruction, and set strict: true on the tool so arguments match the schema. If you only wanted JSON back, use structured outputs instead of a forced tool. Older models still accept forced tool choice.

What is the difference between Claude tool use and MCP?

Tool use is the API mechanism: you define tools in your request and execute them in your own code. MCP, the Model Context Protocol, is a standard for packaging tools on a server so many clients can use them. With the MCP connector, the Messages API connects to a remote MCP server for you, so its tools work without you writing the execution loop for them.

Can Claude call several tools at once?

Yes. Parallel tool use is on by default, so one response can contain several tool_use blocks. Run them, ideally concurrently, and return every tool_result in a single user message. Splitting results across several messages works but teaches Claude to stop calling tools in parallel. Set disable_parallel_tool_use on tool_choice if you need at most one call per turn.

How should I handle a tool error?

Still return a tool_result for that tool_use_id, with is_error set to true and a short message explaining what went wrong. Claude can then retry with different arguments, try another tool or explain the failure to the user. Never drop the result, because every tool_use block in the assistant turn needs a matching tool_result in the next user message.

Which model should I use for tool calling?

Anthropic's current default recommendation is Claude Opus 5.5 (model ID claude-opus-5-5) for agentic and tool-heavy work, with Claude Sonnet 5.5 as a faster, cheaper option and Claude Haiku 5.5 for simple, high-volume routes. All support tool use. Check current model IDs and prices on Anthropic's pricing and models pages before shipping, because they change.

All Access · all four programs · $99/mo

Turn the loop into a product.

The AI SaaS Builder program, included in All Access, covers building AI features with the Claude API, auth, billing and deployment, with the other three programs, live coaching and the private community in one subscription.

Start All Access — $99/mo →30-day money-back guarantee
Free · no signup

Ask builders in the free Discord

Get help with tool use, agents and AI app stacks from other builders.

About the author

Written by Anyro, Founder of IImagined.ai. IImagined.ai is a founder-led education platform teaching Instagram growth, AI influencers, digital products, and AI automation.

Results vary; no income is guaranteed.

All-Access subscription

Every program. Member benefits.
One subscription.

Use all four premium programs with weekly live coaching, a private community, and the resource vault.

Confirm current lessons, downloadable resources and member-benefit arrangements before purchasing.

  • All 4 premium programs plus free Futures Trading
  • Weekly live coaching calls
  • Private community access
  • Resource vault and templates
  • 30-day money-back guarantee, cancel anytime
$99/ month
$99 for the first month · $702 to buy all four standalone
Start All-AccessOr browse standalone programs
30-day money-back guarantee · $99/month · cancel anytime