As of 2026-10-09, the Claude platform release notes place this on 2026-09-22: a tool can be defined inside a mid-conversation system message. The beta header is inline-tools-2026-09-15. The docs retrieved that day list models through Claude Haiku 5.5, which launched on 2026-10-07. Claude Sonnet 5 is absent. Known tools stay in tools. A tool you learn about later, or an input_schema that must change, goes in tool_addition with the full definition. The MCP URL and token stay on mcp_servers. Pin the list with mcp_tool_listing. Why the contract is an object: why AI models like JSON. How agents hand objects to each other: how Claude agents exchange JSON.
In a long chat, the analytics database shows up halfway through. Inserting db_query at the front of tools changes the cached prefix from the first byte. The contract the model sees on the next turn should be the input_schema you just appended, and it should stay that object if the server later publishes a different list.
What the docs actually say
As of 2026-10-09, follow the platform release notes and the mid-conversation system messages page. The beta that defines a tool is separate from system messages that need no header:
- System messages: append
{"role":"system"}mid-conversation on the Claude API, Amazon Bedrock, and Google Cloud. No beta header. The documented models are Claude Fable 5.1, Mythos 5.1, Fable 5, Mythos 5, Opus 5.5, Opus 4.8, Opus 5, Sonnet 5.5, and Haiku 5.5. Claude Sonnet 5 uses the top-levelsystemfield. - Define a tool by value: still beta. On the Claude API send
anthropic-beta: inline-tools-2026-09-15. Atool_additionblock can carry the full definition, for a tool unknown at the start or for a Schema that changes later. The same header covers add and remove by reference, so you do not also sendmid-conversation-tool-changes-2026-07-01. - Reference by name only: the older header
mid-conversation-tool-changes-2026-07-01still works on the Claude API, Bedrock, and Google Cloud. It does not carry a full definition inside the block. - Add MCP mid-conversation: also send
mcp-client-2026-09-15. The definition can be anmcp_toolset. The page documents that header on the Claude API. It includes whatmcp-client-2025-11-20did, so send it in place of the older header.
One line. The tools array in the cached prefix stays byte-identical. The new contract appears in a later system message, and the field is still input_schema.
Why the tools array stays put
The docs say the tools array sits earlier in the hashed request prefix than the top-level system field. Editing the array invalidates the prompt cache for the whole conversation. Declare the tools you already know in tools, then use tool_addition and tool_removal to offer or withdraw a tool from a given message onward. The array itself does not change.
By reference, {"type":"tool_reference","name":"..."} names a tool already in tools. An MCP connector tool can be one mcp_tool_reference (server_name plus name) or a whole mcp_toolset_reference. A name that is not declared returns 400 on the Claude API, with error.details.error_code set to tool_reference_unresolved.
A tool with defer_loading: true stays hidden until a tool_addition surfaces it. tool_addition also re-offers a tool an earlier tool_removal withdrew. A tool known on the first request belongs in tools, with defer_loading if the model should not see it yet. Define by value only what is unknown at the start, or what changes later.
Keep at least one non-deferred tool in tools. A request whose tools are all deferred is still accepted, but the first definition by value changes the start of the rendered prompt and costs one full cache miss. A tool search tool counts as non-deferred.
Carry input_schema in tool_definition
Wrap the definition in tool.type tool_definition. definition is an ordinary tools entry: a custom tool, a client tool, or a server tool, including cache_control and defer_loading. During the beta some types cannot be defined in a message yet. The docs name computer use, which returns a 400 that says so. Declare those in tools and add them by reference.
The documented db_query example:
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {
"type": "tool_definition",
"definition": {
"name": "db_query",
"description": "Run a read-only SQL query against the analytics database.",
"input_schema": {
"type": "object",
"properties": { "sql": { "type": "string" } },
"required": ["sql"]
}
}
}
}
]
}
From that message on, the model calls it the same way it calls a tool in tools. Sending the identical definition again changes nothing, so a retry can resend it. The headers stay anthropic-version: 2023-06-01 and anthropic-beta: inline-tools-2026-09-15. The documented request keeps get_weather in tools and places db_query after the first user turn.
The model returns an ordinary tool_use. The documented input is:
{
"type": "tool_use",
"id": "toolu_01A09q90qw90lq917835lq9",
"name": "db_query",
"input": {
"sql": "SELECT COUNT(*) FROM orders WHERE shipped_at::date = CURRENT_DATE - 1"
}
}
That official input_schema does not set additionalProperties: false. Extra keys can still be valid JSON Schema. To reject them locally, add the keyword, then check the real input. The beta release note does not fold tool-level strict into this feature. strict remains a separate switch on the tool definition.
Send a new Schema under the same name
To change a Schema, or to move a server tool to a newer version, send a different definition under the same name. The new definition replaces the earlier one from that position onward. Reusing the name of a different tool type returns 400 with tool_name_conflict. A newer version of the same tool does not count as a different type. tool_removal still takes a reference. A removed tool can be defined or offered again later.
The limit field below is a local sample of “same name, different definition.” It is not a second official snippet. The documented example has only sql.
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {
"type": "tool_definition",
"definition": {
"name": "db_query",
"description": "Run a read-only SQL query. Reject extra keys locally before execution.",
"input_schema": {
"type": "object",
"properties": {
"sql": { "type": "string" },
"limit": { "type": "integer", "minimum": 1, "maximum": 100 }
},
"required": ["sql"],
"additionalProperties": false
}
}
}
}
]
}
Do not edit or delete a system message you already sent. That invalidates the cache from that point, the same as any earlier edit. The docs say that on Claude Fable 5.1, Opus 5.5, Sonnet 5.5, and Haiku 5.5 it also fails the conversation check for later thinking blocks. If the instruction must change, append another system message.
How to pin an MCP tool list
An MCP server can change its tools at any time. With mcp-client-2026-09-15, when the API asks a server for its tools, the response starts with an mcp_tool_listing block, one per server it asked. The documented example:
{
"type": "mcp_tool_listing",
"mcp_server_name": "example-mcp",
"tools": [
{
"name": "echo",
"description": "Returns the text it receives.",
"input_schema": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}
}
]
}
If your code reads content[0] as the answer, skip these blocks. Send the assistant message back unchanged, listing included, and keep sending mcp-client-2026-09-15 on every later request that carries the block. Later requests use the recorded list and do not ask that server again.
To pin it yourself, copy the block’s tools into that server’s mcp_toolset tools field. The API then does not ask the server. The toolset is exactly those entries, with default_config and configs applied. Each entry holds the server’s name (without the server name), description, and input_schema:
{
"type": "mcp_toolset",
"mcp_server_name": "example-mcp",
"tools": [
{
"name": "echo",
"description": "Returns the text it receives.",
"input_schema": {
"type": "object",
"properties": { "text": { "type": "string" } },
"required": ["text"]
}
}
]
}
A follow-up request with the pin has no mcp_tool_listing, because the API did not ask the server.
A server that becomes available halfway through still lists its connection on mcp_servers. The appended block only names it. Send both headers: inline-tools-2026-09-15,mcp-client-2026-09-15.
{
"role": "system",
"content": [
{
"type": "tool_addition",
"tool": {
"type": "tool_definition",
"definition": { "type": "mcp_toolset", "mcp_server_name": "calendar" }
}
}
]
}
The mcp_toolset object is the same one you would put in tools, including default_config and configs. A tool_addition block never holds a server URL or a token. The documented request puts the calendar server on mcp_servers with type: "url", name calendar, and authorization_token beside them.
Placement, size, and error codes
A system message that carries content (text, tool_addition, or tool_removal) must immediately follow a user turn, including a user turn that carries tool_result blocks, or an assistant turn that ends in a server tool result. It must precede the next assistant turn or end the array. It cannot sit between a tool_use block and its tool_result. The wrong place returns 400. Immediately after a paused assistant turn (one ending in a server tool result), tool_addition and tool_removal are not accepted; text blocks are. Resume the paused turn first, then send the tool change.
Put cache_control on the block or in the definition, not both, and it counts toward the request’s breakpoint limit. A deferred definition cannot carry cache_control. If a server tool you define by value needs its own beta header, send that header on every later request in the conversation.
Any of these limits returns 400 with available_tools_limit_exceeded:
- More than 10,000 deferred tools are available after any message.
- More than 10,000 tools defined after the first user message are still available after any message.
- Tool definitions sent after the first user message and still available after any message total more than 4 MB (4,194,304 bytes).
- The rendered tool text is larger than 4 MB (4,194,304 bytes).
How to check it in JSON Toolbox
Tighten the official input_schema into a local contract first. The official sample omits additionalProperties: false. This one is for the check:
{
"type": "object",
"properties": {
"sql": { "type": "string" }
},
"required": ["sql"],
"additionalProperties": false
}
Three input objects:
{
"ok": {
"sql": "SELECT COUNT(*) FROM orders WHERE shipped_at::date = CURRENT_DATE - 1"
},
"extra_limit": {
"sql": "SELECT 1",
"limit": 10
},
"missing_sql": {}
}
- Paste
tool_addition,mcp_tool_listing, and the three inputs into JSON format and confirm they parse. - Use JSON → Schema on
ok, then addrequiredandadditionalProperties: false. The official schema, as written, does not already reject extra keys. - Run
extra_limitandmissing_sqlthrough Schema validator. Against the tightened contract both should fail.okshould pass. - Put
mcp_toolsetandinput_schemathrough MCP Tool. Keep the URL and token out of the object that only names the tool. - Check
tool_use.inputagain with Function Calling before any SQL runs.
The data stays in the browser. It is not uploaded. Get an object that rejects a missing sql and an extra limit before you change the live system message.
FAQ
What is the difference between editing the tools array and appending tool_addition?
tools sits earlier in the cached prefix. Editing it drops the whole cache. Appending tool_addition leaves the array and earlier messages as sent. Only the new message is new input.
Do I send inline-tools together with the older mid-conversation-tool-changes header?
No. inline-tools-2026-09-15 already covers add and remove by reference. The older header only names a tool. It cannot carry a full definition, and it does work on the Claude API, Bedrock, and Google Cloud.
Can an MCP server URL and token go inside tool_addition?
No. The block only carries mcp_server_name. The URL and authorization_token stay on mcp_servers. Adding a server mid-conversation also needs mcp-client-2026-09-15, documented for the Claude API.
What do I do with mcp_tool_listing?
Skip it when you read the answer text. Send the assistant message back unchanged and keep the beta header. To pin the list, copy tools onto the mcp_toolset. The API then stops asking the server, and the next response has no listing block.
Is this the same note as ChatGPT MCP Events?
No. That note is the subscription filter and webhook data. This one is Claude changing input_schema mid-conversation and pinning an MCP tool list. Delivery is in ChatGPT MCP Events.
Summary
As of 2026-10-09, Claude writes a mid-conversation tool change as one appendable JSON object: tool_addition wraps tool_definition, and the contract is still input_schema. The release note is dated 2026-09-22. The beta header is inline-tools-2026-09-15. The model list retrieved that day includes Haiku 5.5 and leaves out Sonnet 5. Editing the tools array drops the whole cache. A second definition under the same name replaces the Schema from that point; a name taken by another tool type is tool_name_conflict. The MCP address and token stay on mcp_servers. Send mcp_tool_listing back unchanged, or copy it into tools and pin it. The official sample does not close extra keys. The next step is to reject a missing sql and an extra limit, before anyone puts computer use inside the message.