An unofficial C# port of the Claude Agent SDK for Python.
This library provides a .NET interface for interacting with Claude Code CLI, mirroring the API design of the official Python SDK.
Note: This is a community project, not an official Anthropic product. For authoritative documentation, refer to the official Python SDK.
dotnet add package ih0.Claude.Agent.SDKPrerequisites:
- .NET 8.0+
- Claude Code CLI installed:
curl -fsSL https://claude.ai/install.sh | bash
using ih0.Claude.Agent.SDK;
using ih0.Claude.Agent.SDK.Types;
await foreach (var message in ClaudeAgent.QueryAsync("What is 2 + 2?"))
{
if (message is AssistantMessage assistant)
{
foreach (var block in assistant.Content)
{
if (block is TextBlock text)
{
Console.WriteLine($"Claude: {text.Text}");
}
}
}
}ClaudeAgent.QueryAsync() is a static async method for querying Claude Code. It returns an IAsyncEnumerable<Message> of response messages.
// Simple query
await foreach (var message in ClaudeAgent.QueryAsync("Hello Claude"))
{
if (message is AssistantMessage assistant)
{
foreach (var block in assistant.Content)
{
if (block is TextBlock text)
Console.WriteLine(text.Text);
}
}
}
// With options
var options = new ClaudeAgentOptionsBuilder()
.WithSystemPrompt("You are a helpful assistant")
.WithMaxTurns(1)
.Build();
await foreach (var message in ClaudeAgent.QueryAsync("Tell me a joke", options))
{
Console.WriteLine(message);
}var options = new ClaudeAgentOptionsBuilder()
.AddAllowedTools("Read", "Write", "Bash")
.WithPermissionMode(PermissionMode.AcceptEdits) // auto-accept file edits
.Build();
await foreach (var message in ClaudeAgent.QueryAsync("Create a hello.cs file", options))
{
// Process tool use and results
}var options = new ClaudeAgentOptionsBuilder()
.WithCwd("/path/to/project")
.Build();ClaudeAgentClient supports bidirectional, interactive conversations with Claude Code.
Unlike ClaudeAgent.QueryAsync(), ClaudeAgentClient additionally enables custom tools and hooks, both of which can be defined as C# methods.
var options = new ClaudeAgentOptionsBuilder()
.WithModel("claude-sonnet-4-20250514")
.WithMaxTurns(5)
.Build();
await using var client = new ClaudeAgentClient(options);
await client.ConnectAsync();
// Send a query
await client.QueryAsync("Hello, Claude!");
// Receive streaming response
await foreach (var message in client.ReceiveMessagesAsync())
{
Console.WriteLine(message);
}A custom tool is a C# method that you can offer to Claude, for Claude to invoke as needed.
Custom tools are implemented as in-process MCP servers that run directly within your application, eliminating the need for separate processes.
For an end-to-end example, see McpCalculator.
// Define tools by implementing ISdkMcpServer
public class CalculatorServer : ISdkMcpServer
{
public string Name => "calculator";
public string Version => "1.0.0";
public Task<IReadOnlyList<SdkMcpToolDefinition>> ListToolsAsync(CancellationToken cancellationToken = default)
{
var tools = new List<SdkMcpToolDefinition>
{
new SdkMcpToolDefinition
{
Name = "add",
Description = "Add two numbers",
InputSchema = JsonDocument.Parse("""
{ "type": "object", "properties": { "a": {"type": "number"}, "b": {"type": "number"} }, "required": ["a", "b"] }
""").RootElement
},
new SdkMcpToolDefinition
{
Name = "multiply",
Description = "Multiply two numbers",
InputSchema = JsonDocument.Parse("""
{ "type": "object", "properties": { "a": {"type": "number"}, "b": {"type": "number"} }, "required": ["a", "b"] }
""").RootElement
}
};
return Task.FromResult<IReadOnlyList<SdkMcpToolDefinition>>(tools);
}
public Task<SdkMcpToolResult> CallToolAsync(string name, JsonElement arguments, CancellationToken cancellationToken = default)
{
var a = arguments.GetProperty("a").GetDouble();
var b = arguments.GetProperty("b").GetDouble();
var result = name switch { "add" => a + b, "multiply" => a * b, _ => throw new ArgumentException($"Unknown: {name}") };
return Task.FromResult(new SdkMcpToolResult { Content = new[] { new SdkMcpTextContent { Text = result.ToString() } } });
}
}// Use it with Claude
var calculator = new CalculatorServer();
var options = new ClaudeAgentOptionsBuilder()
.AddMcpServer("calculator", new McpSdkServerConfig { Name = "calculator", Instance = calculator })
.AddAllowedTools("mcp__calculator__add", "mcp__calculator__multiply")
.Build();
await using var client = new ClaudeAgentClient(options);
await client.ConnectAsync();
await client.QueryAsync("What is 5 + 3?");
await foreach (var message in client.ReceiveMessagesAsync())
{
Console.WriteLine(message);
}- No subprocess management - Runs in the same process as your application
- Better performance - No IPC overhead for tool calls
- Simpler deployment - Single process instead of multiple
- Easier debugging - All code runs in the same process
- Type safety - Direct C# method calls with full type checking
// BEFORE: External MCP server (separate process)
var optionsBefore = new ClaudeAgentOptionsBuilder()
.AddMcpServer("calculator", new McpStdioServerConfig
{
Command = "dotnet",
Args = new[] { "run", "--project", "CalculatorServer" }
})
.Build();
// AFTER: SDK MCP server (in-process)
var calculator = new CalculatorServer();
var optionsAfter = new ClaudeAgentOptionsBuilder()
.AddMcpServer("calculator", new McpSdkServerConfig { Name = "calculator", Instance = calculator })
.Build();You can use both SDK and external MCP servers together:
var options = new ClaudeAgentOptionsBuilder()
.AddMcpServer("internal", new McpSdkServerConfig { Name = "internal", Instance = sdkServer })
.AddMcpServer("external", new McpStdioServerConfig
{
Command = "external-server"
})
.Build();A hook is a C# callback that the Claude Code application (not Claude) invokes at specific points of the agent loop. Hooks can provide deterministic processing and automated feedback for Claude. Read more in Claude Code Hooks Reference.
For more examples, see Hooks example.
// Define a hook to validate Bash commands
HookCallback CheckBashCommand = (HookInput input, string? toolUseId, HookContext context) =>
{
if (input is not PreToolUseHookInput preToolUse || preToolUse.ToolName != "Bash")
return Task.FromResult(new HookOutput());
var command = preToolUse.ToolInput.GetProperty("command").GetString() ?? "";
if (command.Contains("rm -rf"))
{
return Task.FromResult(new HookOutput
{
Decision = "deny",
Reason = "Dangerous command blocked"
});
}
return Task.FromResult(new HookOutput());
};
var options = new ClaudeAgentOptionsBuilder()
.AddAllowedTool("Bash")
.AddHook(HookEvent.PreToolUse, CheckBashCommand, matcher: "Bash")
.Build();
await using var client = new ClaudeAgentClient(options);
// ...The ClaudeAgentOptionsBuilder provides a fluent API for configuring options:
var options = new ClaudeAgentOptionsBuilder()
.WithModel("claude-sonnet-4-20250514")
.WithMaxTurns(3)
.WithMaxBudgetUsd(0.50)
.AddAllowedTools("Read", "Glob", "Grep")
.AddDisallowedTool("Bash")
.WithSystemPrompt("You are a helpful code reviewer. Be concise.")
.Build();Use ToBuilder() to create a builder from existing options:
var baseOptions = new ClaudeAgentOptions
{
Model = "claude-sonnet-4-20250514",
MaxTurns = 5
};
var modifiedOptions = baseOptions.ToBuilder()
.WithMaxTurns(10)
.AddAllowedTool("Bash")
.Build();Load options from IConfiguration:
{
"Claude": {
"Model": "claude-sonnet-4-20250514",
"MaxTurns": 10,
"MaxBudgetUsd": 5.0,
"PermissionMode": "AcceptEdits",
"AllowedTools": ["Read", "Write", "Bash"],
"Cwd": "/project/root",
"Env": {
"DEBUG": "true"
}
}
}var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build();
// Load directly
var options = configuration.GetClaudeAgentOptions();
// Or load as builder for further customization
var options2 = configuration.GetClaudeAgentOptionsBuilder()
.WithMaxTurns(20) // Override config value
.Build();Register IClaudeAgentService with the DI container:
// With builder configuration
services.AddClaudeAgent(builder => builder
.WithModel("claude-sonnet-4-20250514")
.WithMaxTurns(10));
// From IConfiguration
services.AddClaudeAgent(configuration);
// From configuration with overrides
services.AddClaudeAgent(configuration, builder => builder
.WithMaxTurns(20));Then inject and use:
public class MyService
{
private readonly IClaudeAgentService _claude;
public MyService(IClaudeAgentService claude)
{
_claude = claude;
}
public async Task<string> ProcessAsync(string prompt)
{
await foreach (var message in _claude.QueryAsync(prompt))
{
if (message is ResultMessage result && result.Result != null)
{
return result.Result;
}
}
return string.Empty;
}
}See the ih0.Claude.Agent.SDK.Types namespace for complete type definitions:
ClaudeAgentOptions- Configuration optionsAssistantMessage,UserMessage,SystemMessage,ResultMessage- Message typesTextBlock,ToolUseBlock,ToolResultBlock,ThinkingBlock- Content blocksHookEvent,HookMatcher,HookCallback- Hook typesPermissionMode,PermissionResult- Permission types
try
{
await foreach (var message in ClaudeAgent.QueryAsync("Hello"))
{
// Process messages
}
}
catch (CliNotFoundException)
{
Console.WriteLine("Please install Claude Code CLI");
}
catch (ProcessException ex)
{
Console.WriteLine($"Process failed with exit code: {ex.ExitCode}");
}
catch (ClaudeAgentException ex)
{
Console.WriteLine($"Error: {ex.Message}");
}See the Claude Code documentation for a complete list of available tools.
The SDK includes many example projects in the examples/ directory:
| Example | Description |
|---|---|
QuickStart |
Basic usage patterns |
StreamingMode |
Interactive client usage |
OptionsBuilder |
Fluent builder pattern |
ConfigurationExample |
Loading from appsettings.json |
DependencyInjection |
DI container integration |
Hooks |
Custom hook implementations |
McpCalculator |
In-process MCP server |
ToolPermissions |
Permission callbacks |
Agents |
Subagent definitions |
SystemPrompt |
System prompt configuration |
ToolsOption |
Tool configuration |
Run an example:
cd examples/QuickStart
dotnet run# Build the solution
dotnet build
# Run tests
dotnet test
# Build in Release mode
dotnet build -c Releasesrc/ih0.Claude.Agent.SDK/ # Main library
Types/ # Type definitions
Extensions/ # IConfiguration, DI extensions
Mcp/ # MCP server support
Internal/ # Internal implementation
tests/ih0.Claude.Agent.SDK.Tests/ # Unit tests
examples/ # Example projects
Use of this SDK is governed by Anthropic's Commercial Terms of Service, including when you use it to power products and services that you make available to your own customers and end users, except to the extent a specific component or dependency is covered by a different license as indicated in that component's LICENSE file.