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.
How to Use This List
- Read the entry for your language first, then skim the others to recognize the pattern when you read cross-language examples.
- Every example defines the same tool (a
get_weatherfunction taking acitystring) so the comparison is apples-to-apples. - The wire protocol - what Claude actually sends and expects back - is identical across all five; only the host-language syntax changes.
- Pair this with the general tool_use loop (request,
tool_useblock, run the tool, sendtool_result) if you haven't seen that flow before.
Go: Struct-Based Schemas
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
jsontags plus an explicitPropertiesmap; 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 aToolUseBlock, thenjson.Unmarshal-ing its rawInputbytes 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: Builder-Pattern Schemas
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
Mapwrapped inJsonValue, since Java has no native JSON literal syntax. - Reading a result means checking
block.isToolUse()before callingblock.asToolUse(), then navigating the input as a generic JSON tree rather than unmarshaling into a POJO automatically.
C#: Typed Request Objects
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();
}
}InputSchemais 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
isoperator to check the block's runtime type, the C# equivalent of Go's type switch or Java'sisToolUse()check. toolUse.Inputis 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: Associative Arrays
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->inputlike 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: Hashes
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"]
end
end- 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 readsblock.inputas 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.
Comparing the Five at a Glance
| Language | Schema shape | Result access | Typing |
|---|---|---|---|
| Go | Struct + explicit ToolInputSchemaParam | Type-switch to ToolUseBlock, then json.Unmarshal | Static, most verbose |
| Java | Builder pattern (Tool.builder()) | block.isToolUse() then block.asToolUse() | Static, builder-heavy |
| C# | Typed object / anonymous object | Pattern match with is ToolUseContent | Static, lighter than Go/Java |
| PHP | Associative array | Check type === 'tool_use', index array | Dynamic |
| Ruby | Hash with symbol keys | Check type == "tool_use", index hash | Dynamic |
Handling the Result: The Shared Shape
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.
resultMessage := anthropic.NewUserMessage(
anthropic.NewToolResultBlock(toolUse.ID, weatherJSON, false),
)- The
tool_resultblock always carries the originatingtool_useblock's ID, so Claude can match the result to the call it made. - This ID-matching requirement is identical across all five SDKs; only the syntax for constructing the result block differs.
- Skipping this step, or sending a
tool_resultwith a mismatched ID, produces a request the Messages API rejects the same way in every language.
FAQs
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_useblock'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.
Related
- Calling Claude from Go with the Official SDK - a full, runnable Go tool_use example with the follow-up request included
- Other Official SDKs Basics - first calls in each of the five languages before adding tools
- Beyond Python and TypeScript: The Other Official Claude SDKs - why these SDKs diverge idiomatically despite sharing one protocol
- Building a Multi-Turn Tool Use Loop - the language-agnostic request/tool_result/follow-up flow
- Defining Tool Schemas with Name, Description, and Input Schema - the JSON Schema shape every language's syntax ultimately produces
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.