Go SDK for building AI agents with Claude Code. See the Claude Agent SDK documentation for more information.
go get github.com/Facets-cloud/claude-agent-sdk-goPrerequisites:
- Go 1.21+
- Node.js
- Claude Code CLI (recommended version: 2.0.55):
Or use the automatic installer:
npm install -g @anthropic-ai/claude-code@2.0.55
curl -fsSL https://claude.ai/install.sh | bash -s 2.0.55
package main
import (
"context"
"fmt"
"log"
claude "github.com/Facets-cloud/claude-agent-sdk-go"
)
func main() {
ctx := context.Background()
// Simple query
msgCh, errCh, err := claude.Query(ctx, "What is 2 + 2?", nil, nil)
if err != nil {
log.Fatal(err)
}
for msg := range msgCh {
if assistantMsg, ok := msg.(*claude.AssistantMessage); ok {
for _, block := range assistantMsg.Content {
if textBlock, ok := block.(claude.TextBlock); ok {
fmt.Println(textBlock.Text)
}
}
}
}
if err := <-errCh; err != nil {
log.Fatal(err)
}
}The Query() function is ideal for simple, stateless queries:
ctx := context.Background()
// With options
maxTurns := 1
options := &claude.ClaudeAgentOptions{
SystemPrompt: "You are a helpful assistant",
MaxTurns: &maxTurns,
AllowedTools: []string{"Read", "Write"},
}
msgCh, errCh, err := claude.Query(ctx, "Create a hello.go file", options, nil)
if err != nil {
log.Fatal(err)
}
// Process messages
for msg := range msgCh {
switch m := msg.(type) {
case *claude.AssistantMessage:
// Handle assistant response
case *claude.ResultMessage:
fmt.Printf("Cost: $%.4f\n", *m.TotalCostUSD)
}
}For bidirectional, stateful conversations, use ClaudeSDKClient:
client := claude.NewClaudeSDKClient(nil)
if err := client.Connect(ctx, nil); err != nil {
log.Fatal(err)
}
defer client.Disconnect()
// Send query
if err := client.Query(ctx, "Hello Claude", "default"); err != nil {
log.Fatal(err)
}
// Receive response
for msg := range client.ReceiveResponse(ctx) {
if assistantMsg, ok := msg.(*claude.AssistantMessage); ok {
for _, block := range assistantMsg.Content {
if textBlock, ok := block.(claude.TextBlock); ok {
fmt.Println(textBlock.Text)
}
}
}
}Create in-process tools that Claude can invoke:
import "github.com/Facets-cloud/claude-agent-sdk-go/mcp"
// Define a tool
addTool := mcp.Tool(
"add",
"Add two numbers",
map[string]string{"a": "number", "b": "number"},
func(ctx context.Context, args map[string]interface{}) (map[string]interface{}, error) {
a := args["a"].(float64)
b := args["b"].(float64)
return mcp.TextContent(fmt.Sprintf("Sum: %.2f", a+b)), nil
},
)
// Create server
calculator := mcp.CreateSdkMcpServer("calculator", "1.0.0", []*mcp.SdkMcpTool{addTool})
// Use with Claude
options := &claude.ClaudeAgentOptions{
McpServers: map[string]claude.McpServerConfig{
"calc": calculator.ToConfig(),
},
AllowedTools: []string{"mcp__calc__add"},
}Benefits:
- No subprocess overhead
- Direct access to Go application state
- Simpler deployment
- Type-safe with Go
Hooks allow you to intercept and control Claude's behavior:
// Block dangerous bash commands
bashHook := func(ctx context.Context, input map[string]interface{}, toolUseID *string, hookCtx claude.HookContext) (claude.HookJSONOutput, error) {
toolName := input["tool_name"].(string)
if toolName != "Bash" {
return claude.HookJSONOutput{}, nil
}
toolInput := input["tool_input"].(map[string]interface{})
command := toolInput["command"].(string)
if strings.Contains(command, "rm -rf") {
decision := "block"
return claude.HookJSONOutput{
Decision: &decision,
HookSpecificOutput: map[string]interface{}{
"hookEventName": "PreToolUse",
"permissionDecision": "deny",
"permissionDecisionReason": "Dangerous command blocked",
},
}, nil
}
return claude.HookJSONOutput{}, nil
}
// Hook timeout in seconds (optional)
timeout := 5.0
options := &claude.ClaudeAgentOptions{
Hooks: map[claude.HookEvent][]claude.HookMatcher{
claude.HookEventPreToolUse: {
{
Matcher: "Bash",
Hooks: []claude.HookCallback{bashHook},
Timeout: &timeout, // Optional: timeout for hook execution
},
},
},
}Control tool execution programmatically:
canUseTool := func(ctx context.Context, toolName string, input map[string]interface{}, permCtx claude.ToolPermissionContext) (claude.PermissionResult, error) {
// Allow read-only operations
if toolName == "Read" || toolName == "Grep" {
return claude.PermissionResultAllow{Behavior: "allow"}, nil
}
// Block dangerous commands
if toolName == "Bash" {
command := input["command"].(string)
if strings.Contains(command, "sudo") {
return claude.PermissionResultDeny{
Behavior: "deny",
Message: "sudo commands not allowed",
}, nil
}
}
return claude.PermissionResultAllow{Behavior: "allow"}, nil
}
options := &claude.ClaudeAgentOptions{
CanUseTool: canUseTool,
}Get responses in a specific JSON schema format:
// Define a JSON schema
schema := map[string]interface{}{
"type": "object",
"properties": map[string]interface{}{
"file_count": map[string]interface{}{
"type": "number",
},
"has_tests": map[string]interface{}{
"type": "boolean",
},
},
"required": []string{"file_count", "has_tests"},
}
options := &claude.ClaudeAgentOptions{
OutputFormat: map[string]interface{}{
"type": "json_schema",
"schema": schema,
},
PermissionMode: &claude.PermissionModeAcceptEdits,
}
client := claude.NewClaudeSDKClient(options)
// ... connect and query ...
// Access structured output from result
for msg := range msgCh {
if result, ok := msg.(*claude.ResultMessage); ok {
if result.StructuredOutput != nil {
output := result.StructuredOutput.(map[string]interface{})
fmt.Printf("File count: %.0f\n", output["file_count"])
fmt.Printf("Has tests: %v\n", output["has_tests"])
}
}
}See examples/structured_outputs for more examples.
options := &claude.ClaudeAgentOptions{
// Tool restrictions
AllowedTools: []string{"Read", "Write"},
DisallowedTools: []string{"Bash"},
// System prompt
SystemPrompt: "You are a helpful Go assistant",
// Or use preset:
// SystemPrompt: claude.SystemPromptPreset{
// Type: "preset",
// Preset: "claude_code",
// Append: stringPtr("Additional instructions"),
// },
// Conversation settings
MaxTurns: &maxTurns,
ContinueConversation: true,
Resume: &sessionID,
ForkSession: true,
// Model selection
Model: stringPtr("claude-sonnet-4-5"),
FallbackModel: stringPtr("claude-sonnet-3-5"), // Fallback if primary unavailable
// Structured outputs
OutputFormat: map[string]interface{}{
"type": "json_schema",
"schema": mySchema, // JSON schema for response format
},
// Budget and token control
MaxBudgetUSD: floatPtr(1.0), // Maximum spending limit in USD
MaxThinkingTokens: intPtr(10000), // Maximum extended thinking tokens
// Permission mode
PermissionMode: &permissionMode, // "default", "acceptEdits", "bypassPermissions"
// Working directory
Cwd: stringPtr("/path/to/project"),
// Environment variables
Env: map[string]string{"KEY": "value"},
// Additional directories
AddDirs: []string{"/dir1", "/dir2"},
// Settings
Settings: stringPtr("/path/to/settings.json"),
SettingSources: []claude.SettingSource{claude.SettingSourceUser, claude.SettingSourceProject},
// Plugins
Plugins: []claude.SdkPluginConfig{
{
Type: "local",
Path: "/path/to/plugin",
},
},
// Custom agents
Agents: map[string]claude.AgentDefinition{
"code-reviewer": {
Description: "Reviews code",
Prompt: "You are a code reviewer",
Tools: []string{"Read", "Grep"},
},
},
// Streaming
IncludePartialMessages: true,
// Callbacks
CanUseTool: canUseToolFunc,
Hooks: hooksMap,
Stderr: stderrCallback,
}The SDK uses typed messages for type-safe handling:
// Iterate through messages with type assertions
for msg := range msgCh {
switch m := msg.(type) {
case *claude.UserMessage:
// User input
case *claude.AssistantMessage:
// Claude's response with content blocks
for _, block := range m.Content {
switch b := block.(type) {
case claude.TextBlock:
fmt.Println(b.Text)
case claude.ToolUseBlock:
fmt.Printf("Tool: %s\n", b.Name)
case claude.ThinkingBlock:
fmt.Printf("Thinking: %s\n", b.Thinking)
}
}
case *claude.SystemMessage:
// System messages
case *claude.ResultMessage:
// Final result with metrics
fmt.Printf("Duration: %dms, Cost: $%.4f\n", m.DurationMS, *m.TotalCostUSD)
case *claude.StreamEvent:
// Partial updates (when IncludePartialMessages is true)
}
}import "errors"
msgCh, errCh, err := claude.Query(ctx, "Hello", nil, nil)
if err != nil {
var cliNotFound *claude.CLINotFoundError
if errors.As(err, &cliNotFound) {
fmt.Println("Please install Claude Code")
}
var processErr *claude.ProcessError
if errors.As(err, &processErr) {
fmt.Printf("Exit code: %d\n", processErr.ExitCode)
}
log.Fatal(err)
}
// Process messages...
// Check for runtime errors
if err := <-errCh; err != nil {
log.Fatal(err)
}Error types:
ClaudeSDKError- Base errorCLINotFoundError- Claude Code not installedCLIConnectionError- Connection issuesProcessError- Process failuresCLIJSONDecodeError- JSON parsing errorsMessageParseError- Message parsing errors
See the examples directory for complete working examples:
- quickstart - Basic query usage
- streaming - Multi-turn conversations with ClaudeSDKClient
- mcp_tools - Custom in-process tools
- hooks - Hook system for control and interception
- tool_permission_callback - Control tool permissions
- agents - Define and use custom agents
- max_budget_usd - Set maximum spending limits
- plugin_example - Load custom plugins
- setting_sources - Control which settings are loaded
- system_prompt - Different system prompt configurations
- include_partial_messages - Stream partial updates
- stderr_callback - Capture debug output
Run examples:
cd examples/quickstart
go run main.goThe SDK is designed for Go's concurrency model:
// Use context for cancellation
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
// Channels for message passing
msgCh, errCh, err := claude.Query(ctx, "Hello", nil, nil)
// Use select for concurrent operations
for {
select {
case msg, ok := <-msgCh:
if !ok {
return
}
// Process message
case err := <-errCh:
if err != nil {
log.Fatal(err)
}
return
case <-ctx.Done():
log.Println("Timeout or cancellation")
return
}
}Run tests:
# Unit tests
go test ./tests/unit/...
# All tests
go test ./...
# With coverage
go test -cover ./...
# With race detection
go test -race ./...| Feature | Python SDK | Go SDK |
|---|---|---|
| Simple queries | query() |
Query() |
| Streaming client | ClaudeSDKClient |
ClaudeSDKClient |
| Async iteration | async for |
channels (<-chan) |
| Callbacks | async functions | functions with context.Context |
| Error handling | exceptions | error returns + channels |
| Type safety | runtime type checking | compile-time type safety |
| Concurrency | asyncio/trio | goroutines + channels |
claude-agent-sdk-go/
├── types.go # Core types and interfaces
├── errors.go # Error types
├── query.go # Simple Query API
├── client.go # ClaudeSDKClient
├── transport/ # Transport layer
│ ├── transport.go # Interface
│ └── subprocess.go # CLI subprocess transport
├── internal/
│ ├── parser/ # Message parser
│ ├── query/ # Control protocol handler
│ └── client/ # Internal client
├── mcp/ # SDK MCP server support
│ └── sdk_server.go
├── examples/ # Example applications
└── tests/ # Unit and integration tests
Contributions are welcome! Please:
- Write tests for new features
- Follow Go conventions (
go fmt,go vet) - Update documentation
- Add examples for significant features
MIT