Idiomatic Tool Use Across Go, Java, C#, PHP, and Ruby
Tool_use is the same protocol everywhere Claude runs, but defining a tool schema and reading back the result looks different in every language. This page compares that syntax across all five official SDKs beyond Python and TypeScript.
Go describes a tool's input schema as a typed struct plus an explicit ToolInputSchemaParam, and reads results by type-switching on content blocks.
type WeatherInput struct { City string `json:"city"`}weatherTool := anthropic.ToolParam{ Name: anthropic.String("get_weather"), Description: anthropic.String("Get the current weather for a city."), InputSchema: anthropic.ToolInputSchemaParam{ Type: "object", Properties: map[string]interface{}{ "city": map[string]string{"type": "string"}, }, Required: []string{"city"}, },}for _, block := range message.Content { if toolUse, ok := block.AsAny().(anthropic.ToolUseBlock); ok { var input WeatherInput json.Unmarshal(toolUse.Input, &input) }}
The schema is built from a Go struct's json tags plus an explicit Properties map; Go has no built-in JSON Schema reflection, so you write both by hand.
Reading a result means type-switching on block.AsAny() to find a ToolUseBlock, then json.Unmarshal-ing its raw Input bytes into your own typed struct.
This is the most verbose of the five, in exchange for compile-time field checking on the unmarshal target.
Java expresses the same schema through its builder pattern, and reads results by inspecting typed content block objects.
Tool weatherTool = Tool.builder() .name("get_weather") .description("Get the current weather for a city.") .inputSchema(Tool.InputSchema.builder() .properties(JsonValue.from(Map.of( "city", Map.of("type", "string") ))) .putAdditionalProperty("required", JsonValue.from(List.of("city"))) .build()) .build();for (ContentBlock block : message.content()) { if (block.isToolUse()) { ToolUseBlock toolUse = block.asToolUse(); String city = toolUse.input().asObject().get("city").asString().get(); }}
Tool.builder() mirrors the same fluent-builder convention Java uses everywhere else in the SDK, including for the message request itself.
Input schema properties are built as a Map wrapped in JsonValue, since Java has no native JSON literal syntax.
Reading a result means checking block.isToolUse() before calling block.asToolUse(), then navigating the input as a generic JSON tree rather than unmarshaling into a POJO automatically.
C# defines the schema as a typed object and reads results through pattern matching on content block types.
var weatherTool = new Tool{ Name = "get_weather", Description = "Get the current weather for a city.", InputSchema = new { type = "object", properties = new { city = new { type = "string" } }, required = new[] { "city" } }};foreach (var block in response.Content){ if (block is ToolUseContent toolUse) { var city = toolUse.Input["city"].ToString(); }}
InputSchema is often built as an anonymous object serialized to JSON, since C# has no first-class JSON Schema type in the SDK.
Result handling uses C#'s pattern-matching is operator to check the block's runtime type, the C# equivalent of Go's type switch or Java's isToolUse() check.
toolUse.Input is typically a dictionary-like structure you index by key, closer to PHP and Ruby's approach than Go's strict unmarshal-to-struct pattern.
PHP has no compile-time typing for this, so both the schema and the result are plain associative arrays.
$weatherTool = [ 'name' => 'get_weather', 'description' => 'Get the current weather for a city.', 'input_schema' => [ 'type' => 'object', 'properties' => [ 'city' => ['type' => 'string'], ], 'required' => ['city'], ],];foreach ($response->content as $block) { if ($block->type === 'tool_use') { $city = $block->input['city']; }}
The tool schema is written as a nested associative array that mirrors the JSON Schema shape directly, key-for-key.
Result handling checks $block->type === 'tool_use' and then indexes $block->input like a normal PHP array, since PHP doesn't unmarshal into typed classes by default.
This is the least ceremonious of the five: no builders, no structs, just arrays matching the wire format almost verbatim.
Ruby's approach mirrors PHP's, using hashes with symbol keys instead of associative arrays.
weather_tool = { name: "get_weather", description: "Get the current weather for a city.", input_schema: { type: "object", properties: { city: { type: "string" } }, required: ["city"] }}response.content.each do |block| if block.type == "tool_use" city = block.input["city"] endend
The schema hash uses symbol keys (city:) rather than string keys, which is idiomatic Ruby but still serializes to the same JSON Schema the Messages API expects.
Result handling checks block.type == "tool_use" and reads block.input as a hash, the same shape PHP produces from its associative array.
Ruby and PHP are the closest pair here: both skip static typing entirely and let the hash/array shape do the work.
Regardless of language, every SDK expects the same next step once you've read a tool_use block: run the tool, then send a tool_result message back to Claude referencing the tool_use's ID.
Is the tool_use protocol actually different across these five SDKs?
No. The wire protocol, what Claude sends as a tool_use block and expects back as a tool_result, is identical across all five. Only the host-language syntax for building the schema and reading the result differs.
Which SDK has the least boilerplate for defining a tool?
PHP and Ruby, since both use associative arrays or hashes that map almost directly onto the JSON Schema the API expects, with no builder or struct ceremony.
Which SDK gives the most compile-time safety for tool inputs?
Go, since json.Unmarshal targets a real Go struct with typed fields, catching shape mismatches at compile time on the consuming side (though the schema definition itself is still hand-written).
Do I have to use a builder in Java, or can I construct a Tool directly?
The builder pattern is the SDK's idiomatic entry point and the one most examples use, matching how Java constructs message requests generally.
How does C# know which content block type it received?
Through C#'s pattern-matching is operator, checking whether a content block is ToolUseContent before accessing tool-specific fields.
Do PHP and Ruby validate the input_schema before sending it?
No, neither language does client-side JSON Schema validation. Both send the array or hash as-is; a malformed schema is only caught when the Messages API rejects the request.
What does every SDK need to send a tool_result back to Claude?
The originating tool_use block's ID
The tool's output, serialized as the result content
A flag indicating whether the tool call was an error, in SDKs that expose one
Can I mix tool definitions written in different languages in the same conversation?
Not directly. Each service that talks to Claude defines and executes its own tools locally; if two services in different languages both expose tools, that's typically two separate integrations, not a shared definition.
Does Go's verbosity here mean it's the wrong choice for tool-heavy services?
Not necessarily. The extra verbosity buys compile-time checking on the unmarshal target, which many teams prefer for services with many tools and strict input contracts.
Where should I look if I want the full request/response loop, not just the schema syntax?
See the general tool_use loop article in the tool-use-function-calling section, and the Go-specific walkthrough in this section for a complete, runnable example.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5 - and the current official SDKs for Go, Java, C#, PHP, and Ruby. Model names, SDK versions, and pricing move quickly - verify current specifics at platform.claude.com/docs before relying on them.
Reviewed by Chris St. John·Last updated Jul 19, 2026