Skip to content
MCP Five

The HostProject #214 / 51

The three rules

Append the whole reply. All results in one message. Cap the loop, hard.

Every one of these cost real time to learn. None of them is difficult once somebody has said it out loud.

Rule 1 β€” Append the whole reply, not just the text

ts
// ❌ throws away the tool_use block
const text = response.content.find(b => b.type === "text").text;
messages.push({ role: "assistant", content: text });
 
// βœ…
messages.push({ role: "assistant", content: response.content });

response.content is a list of blocks: text, thinking, and tool_use. Extract just the text and the tool_use block vanishes β€” so your tool_result refers to an id the API has never heard of, and it rejects the whole request with a message about a mismatched tool_use_id.

Rule 2 β€” All tool results go back in ONE user message

If the model asks for three tools at once, send one user message containing three tool_result blocks.

One message per result technically works, and quietly teaches the model to stop asking for tools in parallel β€” which is a performance regression with no error attached to it.

The failure mode here is the nasty kind: nothing breaks. Your agent still works. It is just slower and more expensive than it should be, forever, and no test will ever tell you.

Rule 3 β€” Cap the loop, hard

ts
const MAX_ITERATIONS = 10;

An unbounded tool-calling loop is not a hang. It's a bill.

That is the whole rule, and it is worth being precise about why it is worse than an ordinary infinite loop. A normal runaway loop burns CPU you have already paid for. This one makes a paid API call every time round, with a conversation that is larger every time.

Two more things belong in the same seatbelt:

Handle every stop_reason explicitly. end_turn, max_tokens, refusal, pause_turn each get their own branch. A loop that only asks "is it tool_use?" is a loop that can spin on a case you didn't think about.

Put the token count in the UI, per iteration. Partly a feature. Mostly a seatbelt. You cannot manage a cost you cannot see.

Where the secret lives

Not a rule so much as a fact with no workaround.

A browser cannot keep a secret. If ANTHROPIC_API_KEY appears anywhere in a client component it is compiled into the JavaScript bundle, served to every visitor, and scraped within hours.

The browser sends plain text and receives a stream of loop events. It never sees the API key, the MCP shared token, or even which servers exist. This is why an AI app always needs a server in the middle.

There is no clever client-side workaround. The same rule covers MCP_SHARED_TOKEN and, in project #3, DATABASE_URL β€” a connection string that reaches the browser bundle is a public database.