[Beta] A Swift SDK for seamlessly integrating Claude Code into your iOS and macOS applications. Interact with Anthropic's Claude Code programmatically for AI-powered coding assistance.
- Native Session Storage - Direct access to Claude CLI's session storage (
~/.claude/projects/
) - Enhanced Error Handling - Detailed error types with retry hints and classification
- Built-in Retry Logic - Automatic retry with exponential backoff for transient failures
- Rate Limiting - Token bucket rate limiter to respect API limits
- Timeout Support - Configurable timeouts for all operations
- Cancellation - AbortController support for canceling long-running operations
- New Configuration Options - Model selection, permission modes, executable configuration, and more
ClaudeCodeSDK allows you to integrate Claude Code's capabilities directly into your Swift applications. The SDK provides a simple interface to run Claude Code as a subprocess, enabling multi-turn conversations, custom system prompts, and various output formats.
- Platforms: iOS 15+ or macOS 13+
- Swift Version: Swift 6.0+
- Dependencies: Claude Code CLI installed (
npm install -g @anthropic/claude-code
)
Add the package dependency to your Package.swift
file:
dependencies: [
.package(url: "https://github.com/jamesrochabrun/ClaudeCodeSDK", from: "1.0.0")
]
Or add it directly in Xcode:
- File > Add Package Dependencies...
- Enter:
https://github.com/jamesrochabrun/ClaudeCodeSDK
Import the SDK and create a client:
import ClaudeCodeSDK
// Initialize the client
let client = ClaudeCodeClient(debug: true)
// Run a simple prompt
Task {
do {
let result = try await client.runSinglePrompt(
prompt: "Write a function to calculate Fibonacci numbers",
outputFormat: .text,
options: nil
)
switch result {
case .text(let content):
print("Response: \(content)")
default:
break
}
} catch {
print("Error: \(error)")
}
}
The SDK supports adding a suffix after the command, which is useful when the command requires specific argument ordering:
// Configure with a command suffix
var config = ClaudeCodeConfiguration.default
config.commandSuffix = "--" // Adds "--" after "claude"
let client = ClaudeCodeClient(configuration: config)
// This generates commands like: "claude -- -p --verbose --max-turns 50"
let result = try await client.runSinglePrompt(
prompt: "Write a sorting algorithm",
outputFormat: .text,
options: ClaudeCodeOptions()
)
This is particularly useful when your command executable requires specific argument positioning or when using command wrappers that need arguments separated with --
.
Choose from three output formats depending on your needs:
// Get plain text
let textResult = try await client.runSinglePrompt(
prompt: "Write a sorting algorithm",
outputFormat: .text,
options: nil
)
// Get JSON with metadata
let jsonResult = try await client.runSinglePrompt(
prompt: "Explain big O notation",
outputFormat: .json,
options: nil
)
// Stream responses as they arrive
let streamResult = try await client.runSinglePrompt(
prompt: "Create a React component",
outputFormat: .streamJson,
options: nil
)
if case .stream(let publisher) = streamResult {
publisher.sink(
receiveCompletion: { completion in
// Handle completion
},
receiveValue: { chunk in
// Process each chunk as it arrives
}
)
.store(in: &cancellables)
}
Maintain context across multiple interactions:
// Continue the most recent conversation
let continuationResult = try await client.continueConversation(
prompt: "Now refactor this for better performance",
outputFormat: .text,
options: nil
)
// Resume a specific session
let resumeResult = try await client.resumeConversation(
sessionId: "550e8400-e29b-41d4-a716-446655440000",
prompt: "Add error handling",
outputFormat: .text,
options: nil
)
Configure the client's runtime behavior:
// Create a custom configuration
var configuration = ClaudeCodeConfiguration(
command: "claude", // Command to execute (default: "claude")
workingDirectory: "/path/to/project", // Set working directory
environment: ["API_KEY": "value"], // Additional environment variables
enableDebugLogging: true, // Enable debug logs
additionalPaths: ["/custom/bin"], // Additional PATH directories
commandSuffix: "--" // Optional suffix after command (e.g., "claude --")
)
// Initialize client with custom configuration
let client = ClaudeCodeClient(configuration: configuration)
// Or modify configuration at runtime
client.configuration.enableDebugLogging = false
client.configuration.workingDirectory = "/new/path"
client.configuration.commandSuffix = "--" // Add suffix for commands like "claude -- -p --verbose"
Fine-tune Claude Code's behavior with comprehensive options:
var options = ClaudeCodeOptions()
options.verbose = true
options.maxTurns = 5
options.systemPrompt = "You are a senior backend engineer specializing in Swift."
options.appendSystemPrompt = "After writing code, add comprehensive comments."
options.timeout = 300 // 5 minute timeout
options.model = "claude-3-sonnet-20240229"
options.permissionMode = .acceptEdits
options.maxThinkingTokens = 10000
// Tool configuration
options.allowedTools = ["Read", "Write", "Bash"]
options.disallowedTools = ["Delete"]
let result = try await client.runSinglePrompt(
prompt: "Create a REST API in Swift",
outputFormat: .text,
options: options
)
The Model Context Protocol (MCP) allows you to extend Claude Code with additional tools and resources from external servers. ClaudeCodeSDK provides full support for MCP integration.
Create a JSON configuration file with your MCP servers:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": [
"-y",
"@modelcontextprotocol/server-filesystem",
"/path/to/allowed/files"
]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "your-github-token"
}
}
}
}
Use the configuration in your Swift code:
var options = ClaudeCodeOptions()
options.mcpConfigPath = "/path/to/mcp-config.json"
// MCP tools are automatically added with the format: mcp__serverName__toolName
// The SDK will automatically allow tools like:
// - mcp__filesystem__read_file
// - mcp__filesystem__list_directory
// - mcp__github__*
let result = try await client.runSinglePrompt(
prompt: "List all files in the project",
outputFormat: .text,
options: options
)
You can also configure MCP servers programmatically:
var options = ClaudeCodeOptions()
// Define MCP servers in code
options.mcpServers = [
"XcodeBuildMCP": .stdio(McpStdioServerConfig(
command: "npx",
args: ["-y", "xcodebuildmcp@latest"]
)),
"filesystem": .stdio(McpStdioServerConfig(
command: "npx",
args: ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
))
]
// The SDK creates a temporary configuration file automatically
let result = try await client.runSinglePrompt(
prompt: "Build the iOS app",
outputFormat: .streamJson,
options: options
)
MCP tools follow a specific naming pattern: mcp__<serverName>__<toolName>
// Explicitly allow specific MCP tools
options.allowedTools = [
"mcp__filesystem__read_file",
"mcp__filesystem__write_file",
"mcp__github__search_repositories"
]
// Or use wildcards to allow all tools from a server
options.allowedTools = ["mcp__filesystem__*", "mcp__github__*"]
For non-interactive mode with MCP servers that require permissions:
var options = ClaudeCodeOptions()
options.mcpConfigPath = "/path/to/mcp-config.json"
options.permissionMode = .auto
options.permissionPromptToolName = "mcp__permissions__approve"
The SDK provides robust error handling with detailed error types and recovery options:
// Enhanced error handling
do {
let result = try await client.runSinglePrompt(
prompt: "Complex task",
outputFormat: .json,
options: options
)
} catch let error as ClaudeCodeError {
if error.isRetryable {
// Error can be retried
if let delay = error.suggestedRetryDelay {
// Wait and retry
}
} else if error.isRateLimitError {
print("Rate limited")
} else if error.isTimeoutError {
print("Request timed out")
} else if error.isPermissionError {
print("Permission denied")
}
}
Built-in retry support with exponential backoff:
// Simple retry with default policy
let result = try await client.runSinglePromptWithRetry(
prompt: "Generate code",
outputFormat: .json,
retryPolicy: .default // 3 attempts with exponential backoff
)
// Custom retry policy
let conservativePolicy = RetryPolicy(
maxAttempts: 5,
initialDelay: 5.0,
maxDelay: 300.0,
backoffMultiplier: 2.0,
useJitter: true
)
let result = try await client.runSinglePromptWithRetry(
prompt: "Complex analysis",
outputFormat: .json,
retryPolicy: conservativePolicy
)
Protect against API rate limits with built-in rate limiting:
// Create a rate-limited client
let rateLimitedClient = RateLimitedClaudeCode(
wrapped: client,
requestsPerMinute: 10,
burstCapacity: 3 // Allow 3 requests in burst
)
// All requests are automatically rate-limited
let result = try await rateLimitedClient.runSinglePrompt(
prompt: "Task",
outputFormat: .json,
options: nil
)
Cancel long-running operations with AbortController:
var options = ClaudeCodeOptions()
let abortController = AbortController()
options.abortController = abortController
// Start operation
Task {
let result = try await client.runSinglePrompt(
prompt: "Long running task",
outputFormat: .streamJson,
options: options
)
}
// Cancel when needed
abortController.abort()
Access Claude CLI's native session storage directly to read conversation history:
// Initialize the native storage
let storage = ClaudeNativeSessionStorage()
// List all projects with sessions
let projects = try await storage.listProjects()
print("Projects with sessions: \(projects)")
// Get sessions for a specific project
let sessions = try await storage.getSessions(for: "/Users/me/projects/myapp")
for session in sessions {
print("Session: \(session.id)")
print(" Created: \(session.createdAt)")
print(" Summary: \(session.summary ?? "No summary")")
print(" Messages: \(session.messages.count)")
}
// Get the most recent session for a project
if let recentSession = try await storage.getMostRecentSession(for: "/Users/me/projects/myapp") {
print("Most recent session: \(recentSession.id)")
// Access the conversation messages
for message in recentSession.messages {
print("\(message.role): \(message.content)")
}
}
// Get all sessions across all projects
let allSessions = try await storage.getAllSessions()
print("Total sessions: \(allSessions.count)")
- Complete Sync: Access sessions created from both CLI and your app
- No Duplication: Single source of truth for all Claude conversations
- Rich Metadata: Access to git branch, working directory, timestamps, and more
- Conversation History: Full access to all messages in each session
- Project Organization: Sessions automatically organized by project path
// Session information
public struct ClaudeStoredSession {
let id: String // Session UUID
let projectPath: String // Project this session belongs to
let createdAt: Date // When session was created
let lastAccessedAt: Date // Last activity
var summary: String? // Session summary if available
var gitBranch: String? // Git branch at time of creation
var messages: [ClaudeStoredMessage] // All messages in session
}
// Message information
public struct ClaudeStoredMessage {
let id: String // Message UUID
let parentId: String? // Parent message for threading
let sessionId: String // Session this belongs to
let role: MessageRole // user/assistant/system
let content: String // Message content
let timestamp: Date // When message was sent
let cwd: String? // Working directory
let version: String? // Claude CLI version
}
You can use the native storage to resume conversations or analyze past sessions:
let storage = ClaudeNativeSessionStorage()
let client = ClaudeCodeClient()
// Find a session to resume
if let session = try await storage.getMostRecentSession(for: projectPath) {
// Resume that specific session
let result = try await client.resumeConversation(
sessionId: session.id,
prompt: "Continue where we left off",
outputFormat: .text,
options: nil
)
}
The repository includes a complete example project demonstrating how to integrate and use the SDK in a real application. You can find it in the Example/ClaudeCodeSDKExample
directory.
The example showcases:
- Creating a chat interface with Claude
- Handling streaming responses
- Managing conversation sessions
- Displaying loading states
- Error handling
- Clone the repository
- Open
Example/ClaudeCodeSDKExample/ClaudeCodeSDKExample.xcodeproj
- Build and run
The SDK is built with a protocol-based architecture for maximum flexibility:
ClaudeCode
: Protocol defining the interfaceClaudeCodeClient
: Concrete implementation that runs Claude Code CLI as a subprocessClaudeCodeOptions
: Configuration options for Claude Code executionClaudeCodeOutputFormat
: Output format options (text, JSON, streaming JSON)ClaudeCodeResult
: Result types returned by the SDKResponseChunk
: Individual chunks in streaming responses
ClaudeNativeSessionStorage
: Direct access to Claude CLI's native session storageClaudeSessionStorageProtocol
: Protocol for session storage implementationsClaudeStoredSession
: Model representing a stored Claude sessionClaudeStoredMessage
: Model representing messages within sessions
ApiKeySource
: Source of API key (user/project/org/temporary)ConfigScope
: Configuration scope levels (local/user/project)PermissionMode
: Permission handling modes (default/acceptEdits/bypassPermissions/plan)McpServerConfig
: MCP server configurations (stdio/sse)
ClaudeCodeError
: Comprehensive error types with retry hintsRetryPolicy
: Configurable retry strategiesRetryHandler
: Automatic retry with exponential backoff
RateLimiter
: Token bucket rate limitingAbortController
: Cancellation supportRateLimitedClaudeCode
: Rate-limited wrapper
Problem: When running ClaudeCodeSDK from an app, you get errors like "npm is not installed" even though npm works fine in your terminal.
Cause: When ClaudeCodeSDK launches subprocesses, it uses a shell environment that doesn't automatically source your shell configuration files. This means nvm's PATH modifications aren't loaded.
Solution: Add nvm paths to your configuration:
// Find your nvm version
// Run in terminal: ls ~/.nvm/versions/node/
var config = ClaudeCodeConfiguration.default
config.additionalPaths = [
"/usr/local/bin",
"/opt/homebrew/bin",
"/usr/bin",
"\(NSHomeDirectory())/.nvm/versions/node/v22.11.0/bin", // Replace with your version
]
Better Solution: Use dynamic nvm detection (see NvmPathDetector utility below).
Add the tool's directory to additionalPaths
:
// For Homebrew on Apple Silicon
config.additionalPaths.append("/opt/homebrew/bin")
// For Homebrew on Intel Macs
config.additionalPaths.append("/usr/local/bin")
// For custom tools
config.additionalPaths.append("/path/to/your/tools/bin")
Pass required environment variables explicitly:
var config = ClaudeCodeConfiguration.default
config.environment = [
"API_KEY": "your-key",
"DATABASE_URL": "your-url",
"NODE_ENV": "production"
]
Set the working directory explicitly:
var config = ClaudeCodeConfiguration.default
config.workingDirectory = "/path/to/your/project"
Enable debug logging to see what's happening:
var config = ClaudeCodeConfiguration.default
config.enableDebugLogging = true
This will show:
- The exact command being executed
- Environment variables being used
- PATH configuration
- Error messages from the subprocess
Validate your setup before running Claude Code:
// Use the validateCommand method
let isValid = try await client.validateCommand("npm")
if !isValid {
print("npm not found in PATH")
}
The SDK includes a utility to automatically detect nvm paths:
// Automatic nvm detection
var config = ClaudeCodeConfiguration.default
if let nvmPath = NvmPathDetector.detectNvmPath() {
config.additionalPaths.append(nvmPath)
}
// Or use the convenience initializer
let config = ClaudeCodeConfiguration.withNvmSupport()
ClaudeCodeSDK is available under the MIT license. See the LICENSE
file for more info.
This is not an offical Anthropic SDK, For more information about Claude Code and its capabilities, visit the Anthropic Documentation.