Chapter 4: Tools - The Hands of the Model
While Resources (data) give the model "knowledge", Tools are the instruments that let the model act. In this chapter we learn how to define, implement, and test tools - based on the MCP specification from 2025-11-25 (including SEP-1303 and SEP-973).
What is an MCP Tool?
A tool is an executable function that the server makes available to the client. Every tool consists of:
- Name: A unique ID (e.g.,
calculate_sum). - Description: Text that explains to the LLM when and why it should use this tool.
- Input Schema: A JSON schema (standard: JSON Schema 2020-12) that defines the arguments.
- Icons (New in 2025-11): Optional visual metadata for clients.
Metadata and Icons (SEP-973)
Since November 2025, tools (as well as resources and prompts) can carry visual metadata. This allows clients to build an appealing user interface with icons.
An icon consists of:
- src: A URL (HTTP/HTTPS) or a Data URI (Base64 - a way to embed images directly as a text string).
- mimeType: Optional media type (e.g.,
image/png). - sizes: Optional dimensions (e.g.,
48x48).
Implementation in Go
mcp.AddTool(s, &mcp.Tool{
Name: "add",
Description: "Adds two integers",
InputSchema: map[string]any{
"type": "object",
"properties": map[string]any{
"a": map[string]any{"type": "integer"},
"b": map[string]any{"type": "integer"},
},
"required": []string{"a", "b"},
},
// Icons can now be attached as metadata (SEP-973)
}, func(ctx context.Context, request *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
a, _ := args["a"].(float64)
b, _ := args["b"].(float64)
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: fmt.Sprintf("Result: %d", int(a)+int(b))},
},
}, nil, nil
})Design Pattern: "Compute Instead of Guessing" (e.g. `wollmilchsau`)
The add example above illustrates a fundamental principle for custom MCP tools: LLMs are probabilistic text predictors, not calculation engines.
When you ask an LLM to multiply 17-digit numbers, calculate date differences, or aggregate complex tables, even state-of-the-art models are prone to hallucination. By using a tool, we offload the work to the CPU:
- The model identifies the intent: "I need to add two numbers."
- It calls the
addtool. - The CPU returns a 100% mathematically deterministic result in microseconds.
This pattern can be taken much further: our open-source server wollmilchsau (github.com/hmsoft0815/wollmilchsau · mlcgo.eu/products/wollmilchsau) equips the model with a full V8 JavaScript/TypeScript sandbox as a tool. Instead of struggling with complex filters, regex patterns, or mathematical formulas via expensive "chain-of-thought" prompt reasoning, the model writes a small snippet of code, executes it in the sandbox, and instantly receives the exact result.
Design Pattern: The Secure Database Gatekeeper
Another prime use case for custom tools is mediated access to internal databases:
Never grant an LLM an unrestricted, generic tool like execute_sql_query("...") on a production database. The risk of SQL injection, accidental data loss, or leaking internal schema information is immense.
Instead, a well-architected MCP server defines tightly scoped domain tools:
get_customer_balance(customer_id)list_open_invoices(limit)
The MCP server executes the prepared SQL statement internally, strips sensitive columns (such as passwords, tokens, or internal IDs), and returns only sanitized data. Your database remains protected behind a robust security perimeter while the AI remains fully productive.
Strategic Error Handling (SEP-1303)
An important change in the November 2025 specification concerns validation. If a model sends invalid arguments, the server should not return a JSON-RPC error (protocol level).
Instead, the error should be sent as a regular tool result with IsError: true.
Why? Only when the error lands in the content array can the LLM "read" the error message, understand it, and start a corrected call (self-correction).
if b == 0 {
return &mcp.CallToolResult{
Content: []mcp.Content{&mcp.TextContent{Text: "Error: Division by zero is not allowed."}},
IsError: true,
}, nil, nil
}Tool Annotations: Intent-Aware Tools (readOnlyHint & Co.)
Autonomous agents and clients often need to make critical decisions: Can this tool execute automatically in the background, or must the human user explicitly confirm the action?
Previously, clients had to rely on brittle heuristics matching tool names (delete_*, remove_*). Since the 2025-03-26 specification, MCP defines official Tool Annotations directly inside the tool object:
{
"name": "delete_customer",
"description": "Permanently deletes a customer from the database",
"annotations": {
"readOnlyHint": false,
"destructiveHint": true,
"idempotentHint": false
},
"inputSchema": {
"type": "object",
"properties": {
"customer_id": { "type": "string" }
},
"required": ["customer_id"]
}
}The three core annotations are:
readOnlyHint(boolean): Guarantees that invoking the tool causes no side effects or state mutations (pure read/computation). Clients can safely auto-execute such tools without user prompting.destructiveHint(boolean): Flags tools that delete, overwrite, or cause irreversible changes to state. Clients should display a confirmation modal with the arguments ("Are you sure you want to delete customer 4711?").idempotentHint(boolean): Indicates that multiple calls with identical arguments yield the same system state. Essential for safe retry strategies during network interruptions.
In Go, declare annotations directly during tool registration:
s.AddTool(&mcp.Tool{
Name: "get_customer_balance",
Description: "Retrieves the current balance for a customer.",
Annotations: &mcp.ToolAnnotations{
ReadOnlyHint: true,
DestructiveHint: false,
IdempotentHint: true,
},
// ...
}, handler)[!TIP] The coupling of tool annotations with security scopes (OAuth 2.1) is discussed in detail in Chapter 17.
Testing Tools with `mcp-tester`
The mcp-tester supports the latest standards and lets you simulate tool calls precisely:
./bin/mcp-tester tools call add --args '{"a": 10, "b": 5}' --profile localCheck the IsError flag in particular in the output to make sure your server implements the new error strategy (SEP-1303) correctly.
Outlook: Organizing Tools into Agent Skills
If your server offers a very large number of tools, you risk overwhelming the model. A modern solution to this problem is grouping tools into Agent Skills. There, tools are not all loaded at once, but on demand. You will learn more about this in Chapter 23.
← Chapter 3: Minimal MCP | Table of Contents | Next Chapter: Resources →
Copyright Michael Lechner - 2026-04-26