Tools
Tools give agents the ability to act: look things up, call APIs, run code, read files. The model decides which tools to call and when.
@Tool Macro
The @Tool macro is the primary way to define a tool. Attach it to any Swift function and the compiler generates the JSON schema, tool name, and AgentTool conformance automatically.
/// Count the number of words in a block of text.
@Tool
func wordCount(text: String) -> Int {
text.split(whereSeparator: \.isWhitespace).count
}
/// Evaluate a mathematical expression and return the result.
@Tool
func calculator(expression: String) -> String {
guard let result = NSExpression(format: expression)
.expressionValue(with: nil, context: nil) else {
return "Error: invalid expression"
}
return "\(result)"
}
let agent = Agent(model: provider, tools: [wordCount, calculator])
What the macro generates
For each annotated function, the macro creates two things:
- A struct conforming to
AgentToolwith the JSON schema derived from the parameter types - A
letbinding with the same name as the function that you pass toAgent(tools:)
The function itself is unchanged. You can still call it directly as a regular Swift function:
// Still callable as a plain Swift function
let count = wordCount(text: "hello world") // 2
Type mapping
| Swift Type | JSON Schema |
|---|---|
String | "type": "string" |
Int, Int64, … | "type": "integer" |
Double, Float | "type": "number" |
Bool | "type": "boolean" |
T? or default value | Marked optional in schema (omitted from required) |
Optional parameters and defaults
Parameters with a default value or ? type are treated as optional: included in the schema but excluded from required:
/// Look up current weather for a city.
@Tool
func getWeather(
city: String, // required
unit: String = "celsius", // optional (has a default)
detailed: Bool = false // optional (has a default)
) async throws -> String {
// fetch from weather API
return "22°C, partly cloudy in \(city)"
}
Async and throwing tools
Tools can be async, throws, or both. The generated wrapper handles await and catches errors automatically:
/// Search the web and return a summary of the top results.
@Tool
func searchWeb(query: String, maxResults: Int = 5) async throws -> String {
let url = URL(string: "https://api.search.example.com?q=\(query)")!
let (data, _) = try await URLSession.shared.data(from: url)
return String(data: data, encoding: .utf8) ?? "No results"
}
Concurrent Tool Execution
When a model requests multiple tools in a single response, the agent runs them all concurrently using Swift structured concurrency. This means a model can call searchWeb, calculator, and getWeather simultaneously and wait for all results before continuing.
Make your tools async whenever they perform I/O (network, disk) so concurrent execution is truly parallel rather than serialized.
Manual Conformance
For complex input decoding, dynamic schemas, or reuse across targets, you can conform to AgentTool directly:
struct WordCount: AgentTool {
let name = "word_count"
var toolSpec: ToolSpec {
ToolSpec(
name: name,
description: "Count the number of words in text.",
inputSchema: [
"type": "object",
"properties": [
"text": [
"type": "string",
"description": "The text to count words in"
]
],
"required": ["text"],
]
)
}
func call(toolUse: ToolUseBlock, context: ToolContext) async throws -> ToolResultBlock {
let text = toolUse.input["text"]?.foundationValue as? String ?? ""
let count = text.split(whereSeparator: \.isWhitespace).count
return ToolResultBlock(
toolUseId: toolUse.toolUseId,
status: .success,
content: [.text("\(count)")]
)
}
}
let agent = Agent(model: provider, tools: [WordCount()])
Tool Context
The ToolContext passed to call(toolUse:context:) provides access to the agent's state, session, and observability span. Useful for tools that need to call the agent recursively or log structured data:
func call(toolUse: ToolUseBlock, context: ToolContext) async throws -> ToolResultBlock {
context.span?.setAttribute("query", toolUse.input["query"]?.foundationValue as? String ?? "")
// use context.agentState to read/write shared state
return ToolResultBlock(
toolUseId: toolUse.toolUseId,
status: .success,
content: [.text("done")]
)
}
MCP Tool Providers
The SDK supports the Model Context Protocol (MCP). Connect any MCP server as a tool provider and all its tools become available to the agent automatically:
let mcpProvider = MCPToolProvider(serverURL: URL(string: "http://localhost:3000")!)
let tools = try await mcpProvider.tools()
let agent = Agent(model: provider, tools: tools)