Skip to content

Repository files navigation

Claude Code SDK for C#

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.

Status & Version

NuGet Version (with prereleases) NuGet Version (with prereleases) CI/CD

Package versions use the format x.y.z and may include a -preview suffix for prerelease builds.

  • x follows the target .NET version.
  • y is incremented when the library introduces breaking changes or updates its Microsoft Agent Framework dependency.
  • z is incremented for library patch releases.

Features

  • 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 AskUserQuestion through 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 IAsyncDisposable support with proper resource management

Installation

Core SDK

Install via NuGet:

dotnet add package ClaudeCodeSdk

Microsoft Agent Framework Integration (Optional)

For Microsoft Agent Framework support:

dotnet add package ClaudeCodeSdk.MAF

Prerequisites

  • .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_TOKEN environment variable with your Anthropic API key

Quick Start

One-shot Query

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);
        }
    }
}

Interactive Client

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}");
        }
    }
}

Microsoft Agent Framework Integration

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.

Architecture

The SDK implements a simplified dual-pattern architecture for Claude Code interactions:

Core Components

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 ClaudeQuery and ClaudeSdkClient

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

Message Types

All messages implement IMessage:

  • AssistantMessage - Claude's responses with content blocks
  • UserMessage - User input
  • SystemMessage - System notifications and metadata
  • ResultMessage - End-of-conversation marker with cost/usage data
  • StreamEvent - Raw partial-message event emitted when IncludePartialMessages is enabled

Content Blocks

All content blocks implement IContentBlock:

  • TextBlock - Plain text content
  • ThinkingBlock - Claude's reasoning (when extended thinking is enabled)
  • ToolUseBlock - Tool invocations
  • ToolResultBlock - Tool execution results
  • ErrorContentBlock - Error information

Exception Hierarchy

Custom exceptions inherit from ClaudeSDKException:

  • CLINotFoundException - Claude Code CLI not found
  • CLIConnectionException - Transport connection issues
  • ProcessException - Subprocess execution failures
  • CLIJsonDecodeException - Message parsing errors
  • MessageParseException - Type conversion failures

Microsoft Agent Framework Integration

The MAF integration (ClaudeCodeSdk.MAF) provides:

ClaudeCodeAIAgent

  • Full AIAgent implementation from Microsoft.Agents.AI
  • Streaming and non-streaming execution modes
  • Session-based conversation management
  • Automatic session persistence via ClaudeSdkClientManager
  • System prompt extraction and configuration

ClaudeCodeAgentSession

  • Session serialization/deserialization for persistence
  • Session ID management for conversation continuity
  • Compatible with MAF's AIConversationState

ClaudeSdkClientManager

  • Automatic client lifecycle management
  • Disposes old clients when switching sessions
  • Session-safe with proper async resource management
  • Optimizes resource usage across multiple sessions

Configuration

Environment Variables

The SDK automatically configures these environment variables for the Claude Code CLI:

  • ANTHROPIC_AUTH_TOKEN - API authentication (from ClaudeCodeOptions.ApiKey or environment)
  • ANTHROPIC_BASE_URL - Custom API endpoint (from ClaudeCodeOptions.BaseUrl)
  • CLAUDE_CODE_ENTRYPOINT - SDK identifier (always "sdk-csharp")

ClaudeCodeOptions

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" }
    }
};

Streaming partial messages

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.

Handling tool permissions and questions

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.

Development

Building

# Build the entire solution
dotnet build

# Build specific project
dotnet build src/ClaudeCodeSdk/ClaudeCodeSdk.csproj

Testing

# Run all tests
dotnet test

# Run with verbose output
dotnet test --verbosity normal

# Run specific test
dotnet test --filter "FullyQualifiedName~ExceptionsTests"

Running Examples

# Run all examples
dotnet run --project examples/ClaudeCodeSdk.Examples.csproj

# Run specific example class
dotnet run --project examples/ClaudeCodeSdk.Examples.csproj -- --example QuickStart

Packaging NuGet Packages

# 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=true

Examples

The 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

Running Examples

Make sure you have:

  1. Installed Claude Code CLI: npm install -g @anthropic-ai/claude-code
  2. Set ANTHROPIC_AUTH_TOKEN environment variable

Then run:

dotnet run --project examples/ClaudeCodeSdk.Examples.csproj

Key Behaviors

Message Streaming and Termination

  • ClaudeProcess.ReceiveAsync() automatically terminates when receiving a ResultMessage
  • Both ClaudeQuery and ClaudeSdkClient rely on this automatic termination
  • ClaudeSdkClient.ReceiveResponseAsync() provides convenience method that yields until ResultMessage

JSON Serialization

  • Uses snake_case_lower naming policy via JsonUtil for Claude Code CLI compatibility
  • Consistent serialization across all message exchanges

Resource Management

  • All process-managing classes implement IAsyncDisposable
  • ClaudeProcess handles subprocess lifecycle (start, kill, cleanup)
  • Use await using for automatic cleanup

MAF Session Management

  • ClaudeSdkClientManager automatically handles client creation/disposal when switching sessions
  • Agent session IDs are sent as session_id on user messages for conversation continuity
  • Session state persists via the session's SessionId

Troubleshooting

"Claude Code CLI not found"

Ensure Claude Code CLI is installed globally:

npm install -g @anthropic-ai/claude-code

Authentication Errors

Set 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-key

Or pass it via options:

var options = new ClaudeCodeOptions { ApiKey = "your-api-key" };

Process Lifecycle Issues

Always dispose of SDK objects properly:

await using var client = new ClaudeSdkClient();
await client.ConnectAsync();
// ... use client
// Automatic disposal on scope exit

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

License

MIT License - see LICENSE.txt for details

Links

About

Claude Code SDK for C#

Resources

Stars

3 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages