AI agents and MCP: TypeScript

Sefism.

Chapter 02

Tool calling from scratch

How a model asks your code to act: tool definitions, the call and result messages, tool choice, parallel calls and errors, shown with one provider API and no framework.

Tool definition

Also called function schema, tool schema, input_schema, parameters, function calling

What
A name, a description and a JSON Schema for the arguments, sent with every request. The model sees only these three things when deciding whether and how to call the tool.
Why
The definition is the whole interface between the model and your code, so its wording decides how often the tool is chosen and filled in correctly.
JSON
{
  "name": "search_orders",
  "description": "Find a customer's orders by email.",
  "input_schema": {
    "type": "object",
    "properties": {
      "email": { "type": "string", "format": "email" },
      "status": { "enum": ["open", "shipped", "refunded"] }
    },
    "required": ["email"]
  }
}

Watch out

One vague tool such as query(text) that does five jobs, instead of a few tools with one clear job each.

Tool call and tool result

Also called tool_use, tool_result, function_call, function_call_output, call id

What
The model replies with a tool call carrying an id, a tool name and JSON arguments. Your code runs the tool and sends back a tool result that quotes the same id, then calls the model again.
Why
The id is how the model matches each result to its request, especially when several calls happen in one turn.
TypeScript
while (true) {
  const r = await client.messages.create(
    { model: MODEL, max_tokens: 4096, tools, messages });
  messages.push({ role: "assistant", content: r.content });
  if (r.stop_reason !== "tool_use") break;
  const calls = r.content.filter(
    (b): b is Anthropic.ToolUseBlock => b.type === "tool_use");
  const results = calls.map((b) => ({
    type: "tool_result" as const, tool_use_id: b.id,
    content: runTool(b.name, b.input) }));
  messages.push({ role: "user", content: results });
}

Watch out

Dropping the assistant message that holds the tool calls before sending results, which the API rejects or the model misreads.

Provider differences in tool calling

Also called Anthropic vs OpenAI tools, Responses API, Messages API, Gemini function calling

What
Every major provider has the same loop with different field names. Anthropic uses input_schema, tool_use and tool_result; the OpenAI Responses API uses parameters, function_call and function_call_output joined by call_id.
Why
Knowing the loop in one provider lets you read any other in minutes, and it is why thin adapters and gateways can switch models without rewriting agents.
Example
Stop signals differ too: Anthropic sets stop_reason to tool_use, while in the Responses API you look for function_call items in the output list.

Watch out

Hard-coding one provider's message shapes throughout your code instead of behind one small adapter.

Tool choice

Also called tool_choice, forced tool use, auto, required, none

What
A request setting that lets the model decide freely (auto), forces a tool call (any or required, or one named tool), or blocks tools (none).
Why
Auto is right for agents. Forcing a tool is a blunt instrument some newer models no longer accept, so prefer a clear instruction plus strict schemas.

Watch out

Forcing a tool on every turn, so the loop can never end with a plain answer.

Parallel tool calls

Also called parallel_tool_calls, disable_parallel_tool_use, multiple tool calls

What
One model turn can contain several tool calls. Your code runs them, concurrently when they are independent, and returns all the results together in the next message.
Why
It cuts latency when an agent needs several lookups at once, such as three order statuses or two searches.

Watch out

Splitting the results of one turn across several messages, which confuses the model and discourages parallel calls later.

Tool errors the model can act on

Also called is_error, isError, tool failure, error result

What
When a tool fails, return a result marked as an error with a short message saying what went wrong and what to try instead, rather than throwing and crashing the loop.
Why
A clear error lets the model retry with fixed arguments, pick another tool or tell the user, which is most of what makes an agent feel robust.
Example
"No order found for that email. Ask the customer for the order number, which starts with ORD-." is far more useful than a stack trace.

Watch out

Returning an empty result on failure, so the model concludes the customer has no orders.

Writing tool descriptions

Also called tool docs, agent-computer interface, tool naming

What
Describe each tool as you would for a new teammate: what it does, when to use it, when not to, what it returns and any limits. Name tools by the action, such as search_orders.
Why
Most tool-calling failures are wrong tool choice or wrong arguments, and both are usually fixed in the description rather than the prompt.

Watch out

Leaving parameter meanings implicit, such as a date with no stated format or time zone.

Keeping tool results small

Also called tool output truncation, pagination for agents, response format

What
Return only what the model needs to decide the next step: a few relevant fields, a page of results with a way to get more, and readable ids instead of raw internal dumps.
Why
Every tool result stays in the context for the rest of the run, so bloated results raise cost, slow every call and bury the facts that matter.

Watch out

Returning a 5,000-row query result and hoping the model finds the one row it needs.

02 · Tool calling from scratch2 / 11