A .NET SDK for interacting with Claude through the Claude Code CLI, providing both one-shot queries and interactive client sessions with full Microsoft Agent Framework (MAF) integration.
Package versions use the format x.y.z and may include a -preview suffix for prerelease builds.
xfollows the target .NET version.yis incremented when the library introduces breaking changes or updates its Microsoft Agent Framework dependency.zis incremented for library patch releases.
- ✅ One-shot Queries - Simple request/response pattern via
ClaudeQuery.QueryAsync - ✅ Interactive Client - Bidirectional communication with
ClaudeSdkClient - ✅ Streaming Support - Real-time response streaming via
IAsyncEnumerable<T> - ✅ Partial Messages - Optional token-level text, thinking, and Tool Call streaming
- ✅ Microsoft Agent Framework Integration - Use Claude as an
AIAgent(ClaudeCodeSdk.MAF) - ✅ Session Management - Multi-turn conversations with session support and automatic session lifecycle management
- ✅ Session Persistence - Serialize/deserialize conversation sessions for storage
- ✅ Tool Integration - Full support for Claude Code tools and MCP servers
- ✅ Human Input Callbacks - Handle permissions and
AskUserQuestionthrough the stdio control protocol - ✅ Thinking Blocks - Extended reasoning with configurable thinking tokens
- ✅ Usage Tracking - Token usage and cost monitoring
- ✅ .NET 10.0 - Targets
net10.0 - ✅ Modern Async/Await - Full
IAsyncDisposablesupport with proper resource management
Install via NuGet:
dotnet add package ClaudeCodeSdkFor Microsoft Agent Framework support:
dotnet add package ClaudeCodeSdk.MAF- .NET 10.0 SDK
- Claude Code CLI available on
PATH. For example, install it via npm:npm install -g @anthropic-ai/claude-code
- API Key: Set
ANTHROPIC_AUTH_TOKENenvironment variable with your Anthropic API key
using ClaudeCodeSdk;
using ClaudeCodeSdk.Types;
await foreach (var message in ClaudeQuery.QueryAsync("What is the capital of France?"))
{
if (message is AssistantMessage assistantMessage)
{
foreach (var text in assistantMessage.Content.OfType<TextBlock>())
{
Console.WriteLine(text.Text);
}
}
}using ClaudeCodeSdk;
using ClaudeCodeSdk.Types;
await using var client = new ClaudeSdkClient();
await client.ConnectAsync();
await client.QueryAsync("Hello Claude!");
await foreach (var message in client.ReceiveResponseAsync())
{
if (message is AssistantMessage assistantMsg)
{
foreach (var text in assistantMsg.Content.OfType<TextBlock>())
{
Console.WriteLine($"Claude: {text.Text}");
}
}
}using ClaudeCodeSdk.MAF;
using Microsoft.Extensions.AI;
// Create Claude as an AIAgent
await using var agent = new ClaudeCodeAIAgent();
// Simple query
var response = await agent.RunAsync("Explain async/await in C#");
Console.WriteLine(response.Text);
// Multi-turn conversation with session
var session = await agent.CreateSessionAsync();
var response1 = await agent.RunAsync(
[new ChatMessage(ChatRole.User, "What is dependency injection?")],
session: session
);
// Context is automatically preserved across turns
var response2 = await agent.RunAsync(
[new ChatMessage(ChatRole.User, "Show me an example in C#")],
session: session
);
// Streaming with real-time updates
await foreach (var update in agent.RunStreamingAsync("Tell me a story", session: session))
{
if (update.Contents != null)
{
foreach (var content in update.Contents)
{
if (content is TextContent text)
{
Console.Write(text.Text);
}
}
}
}See ClaudeCodeSdk.MAF README for complete MAF integration documentation.
Important
MAF forwards text and image DataContent from the first user message. If you need a global instruction, configure ClaudeCodeAIAgentOptions.SystemPrompt (or AppendSystemPrompt) instead of relying on per-request ChatRole.System messages.
Tip
For long-running conversations backed by your own storage, configure ClaudeCodeAIAgentOptions.ChatHistoryProvider to observe each request and save new messages after each response. Claude Code resumes model history through the supplied AgentSession ID rather than resending stored messages as prompt input.
The SDK implements a simplified dual-pattern architecture for Claude Code interactions:
ClaudeProcess - Unified subprocess manager
- Direct subprocess communication with Claude Code CLI
- JSON-based message protocol with strongly-typed parsing
- Automatic CLI discovery and process lifecycle management
- Shared by both
ClaudeQueryandClaudeSdkClient
ClaudeQuery - One-shot query API
- Fire-and-forget pattern for simple queries
- Streams responses as
IAsyncEnumerable<IMessage> - Automatically handles connection lifecycle
- Ideal for single-request scenarios
ClaudeSdkClient - Interactive client API
- Long-lived bidirectional communication
- Manual connection control via
ConnectAsync/DisconnectAsync - Session management with multi-turn conversations
- Interrupt support and resource cleanup
All messages implement IMessage:
AssistantMessage- Claude's responses with content blocksUserMessage- User inputSystemMessage- System notifications and metadataResultMessage- End-of-conversation marker with cost/usage dataStreamEvent- Raw partial-message event emitted whenIncludePartialMessagesis enabled
All content blocks implement IContentBlock:
TextBlock- Plain text contentThinkingBlock- Claude's reasoning (when extended thinking is enabled)ToolUseBlock- Tool invocationsToolResultBlock- Tool execution resultsErrorContentBlock- Error information
Custom exceptions inherit from ClaudeSDKException:
CLINotFoundException- Claude Code CLI not foundCLIConnectionException- Transport connection issuesProcessException- Subprocess execution failuresCLIJsonDecodeException- Message parsing errorsMessageParseException- Type conversion failures
The MAF integration (ClaudeCodeSdk.MAF) provides:
- Full
AIAgentimplementation from Microsoft.Agents.AI - Streaming and non-streaming execution modes
- Session-based conversation management
- Automatic session persistence via
ClaudeSdkClientManager - System prompt extraction and configuration
- Session serialization/deserialization for persistence
- Session ID management for conversation continuity
- Compatible with MAF's
AIConversationState
- Automatic client lifecycle management
- Disposes old clients when switching sessions
- Session-safe with proper async resource management
- Optimizes resource usage across multiple sessions
The SDK automatically configures these environment variables for the Claude Code CLI:
ANTHROPIC_AUTH_TOKEN- API authentication (fromClaudeCodeOptions.ApiKeyor environment)ANTHROPIC_BASE_URL- Custom API endpoint (fromClaudeCodeOptions.BaseUrl)CLAUDE_CODE_ENTRYPOINT- SDK identifier (always "sdk-csharp")
Key configuration options:
var options = new ClaudeCodeOptions
{
ApiKey = "sk-ant-...", // Anthropic API key
BaseUrl = "https://api.anthropic.com", // Custom API endpoint
MaxThinkingTokens = 10000, // Extended thinking budget
SystemPrompt = "You are a helpful assistant",
Model = "sonnet", // Stable alias for the latest Sonnet model
IncludePartialMessages = true, // Emit raw StreamEvent messages
PermissionMode = PermissionMode.acceptEdits, // Tool approval mode
WorkingDirectory = "/path/to/project",
MaxTurns = 10,
EnvironmentVariables = new Dictionary<string, string?>
{
{ "HTTP_PROXY", "http://proxy:1080" }
}
};Set IncludePartialMessages to receive raw Claude Code StreamEvent messages before each complete
AssistantMessage. Each event preserves the CLI's original JSON payload so callers can handle new event types without
waiting for an SDK update.
ClaudeCodeSdk.MAF translates supported partial events into standard AgentResponseUpdate chunks. Text and thinking
deltas are emitted immediately with a stable ResponseId and MessageId; Tool Use JSON is accumulated internally and
emitted as a complete FunctionCallContent when its content block ends. The later complete AssistantMessage is used
only as a fallback for content that was not already streamed.
When a MAF ChatHistoryProvider is configured together with partial messages, deltas are emitted as they arrive. Each
logical Assistant message is persisted after both its message_stop event and complete AssistantMessage arrive, before
the corresponding processed message is yielded.
The SDK combines that message's updates with ToAgentResponse(), so a run containing multiple Tool Use rounds can persist
multiple completed history batches. Tool results are carried into the next completed Assistant batch. On normal completion,
failure, cancellation, source exception, or early consumer disposal, the SDK also persists any remaining buffered updates in
their original order, including incomplete Assistant text and tool fragments. Final persistence ignores the run cancellation
token; if both the stream and persistence fail, the stream failure remains primary.
Without partial events, the SDK retains the compatible end-of-run aggregation fallback.
Set CanUseTool to handle Claude Code permission requests without a terminal. The SDK automatically configures
--permission-prompt-tool stdio; the callback can remain pending while your application collects user input.
var options = new ClaudeCodeOptions
{
CanUseTool = async (toolName, input, context, cancellationToken) =>
{
if (toolName != "AskUserQuestion")
{
return new PermissionResultDeny($"Unsupported request: {toolName}");
}
var updatedInput = await CollectAnswersAsync(input, cancellationToken);
return new PermissionResultAllow(updatedInput);
}
};PermissionResultAllow preserves the original input when UpdatedInput is omitted. For AskUserQuestion, return the
original questions plus an answers object. Cancelling the query also cancels the pending callback.
# Build the entire solution
dotnet build
# Build specific project
dotnet build src/ClaudeCodeSdk/ClaudeCodeSdk.csproj# Run all tests
dotnet test
# Run with verbose output
dotnet test --verbosity normal
# Run specific test
dotnet test --filter "FullyQualifiedName~ExceptionsTests"# Run all examples
dotnet run --project examples/ClaudeCodeSdk.Examples.csproj
# Run specific example class
dotnet run --project examples/ClaudeCodeSdk.Examples.csproj -- --example QuickStart# Pack both SDK and MAF packages
dotnet pack src/ClaudeCodeSdk/ClaudeCodeSdk.csproj -c Release
dotnet pack src/ClaudeCodeSdk.MAF/ClaudeCodeSdk.MAF.csproj -c Release
# Pack with symbols
dotnet pack src/ClaudeCodeSdk/ClaudeCodeSdk.csproj -c Release -p:IncludeSymbols=trueThe examples/ folder contains complete working examples:
- QuickStartExamples - Basic one-shot queries and interactive client usage
- StreamingExamples - Real-time response streaming patterns
- MafExample - Microsoft Agent Framework integration examples
Make sure you have:
- Installed Claude Code CLI:
npm install -g @anthropic-ai/claude-code - Set
ANTHROPIC_AUTH_TOKENenvironment variable
Then run:
dotnet run --project examples/ClaudeCodeSdk.Examples.csprojClaudeProcess.ReceiveAsync()automatically terminates when receiving aResultMessage- Both
ClaudeQueryandClaudeSdkClientrely on this automatic termination ClaudeSdkClient.ReceiveResponseAsync()provides convenience method that yields until ResultMessage
- Uses
snake_case_lowernaming policy viaJsonUtilfor Claude Code CLI compatibility - Consistent serialization across all message exchanges
- All process-managing classes implement
IAsyncDisposable ClaudeProcesshandles subprocess lifecycle (start, kill, cleanup)- Use
await usingfor automatic cleanup
ClaudeSdkClientManagerautomatically handles client creation/disposal when switching sessions- Agent session IDs are sent as
session_idon user messages for conversation continuity - Session state persists via the session's
SessionId
Ensure Claude Code CLI is installed globally:
npm install -g @anthropic-ai/claude-codeSet your API key:
# macOS/Linux
export ANTHROPIC_AUTH_TOKEN="your-api-key"
# Windows PowerShell
$env:ANTHROPIC_AUTH_TOKEN="your-api-key"
# Windows Command Prompt
set ANTHROPIC_AUTH_TOKEN=your-api-keyOr pass it via options:
var options = new ClaudeCodeOptions { ApiKey = "your-api-key" };Always dispose of SDK objects properly:
await using var client = new ClaudeSdkClient();
await client.ConnectAsync();
// ... use client
// Automatic disposal on scope exitContributions are welcome! Please feel free to submit a Pull Request.
MIT License - see LICENSE.txt for details