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.

Defining tools with @ToolSwift
/// 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 AgentTool with the JSON schema derived from the parameter types
  • A let binding with the same name as the function that you pass to Agent(tools:)

The function itself is unchanged. You can still call it directly as a regular Swift function:

Swift
// Still callable as a plain Swift function
let count = wordCount(text: "hello world")  // 2

Type mapping

Swift TypeJSON Schema
String"type": "string"
Int, Int64, …"type": "integer"
Double, Float"type": "number"
Bool"type": "boolean"
T? or default valueMarked 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:

Swift
/// 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:

Swift
/// 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:

Manual AgentTool conformanceSwift
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:

Swift
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:

Swift
let mcpProvider = MCPToolProvider(serverURL: URL(string: "http://localhost:3000")!)
let tools = try await mcpProvider.tools()
let agent = Agent(model: provider, tools: tools)