GeminiDotnet is a lightweight yet fully-featured library for interacting with Google's Gemini API in modern .NET. GeminiDotnet is performant and Native AOT compatible, using System.Text.Json source-generation for JSON serialization, and has minimal dependencies.
This respository contains two packages which users can choose from. The recommended entry-point is GeminiDotnet.Extensions.AI which provides implementations of the Microsoft.Extensions.AI.Abstractions APIs. These provide common abstractions over generative AI models, allowing users to swap out their model provider without rewriting their code. Alternatively, if you'd like a direct, lightweight mapping to the Google Gemini API, you can use GeminiDotnet directly.
GeminiDotnetfor direct interaction with Gemini APIGeminiDotnet.Extensions.AIfor use withMicrosoft.Extensions.AI(recommended).
Note
Since writing this library, Google have released first-party support for C#, which you may prefer. As of Google.GenAI v0.11.0, this now includes Microsoft.Extensions.AI support too.
| Package | Latest | Downloads |
|---|---|---|
| GeminiDotnet | ||
| GeminiDotnet.Extensions.AI |
The following examples use the GeminiDotnet.Extensions.AI package.
- Streaming Text Generation
- Function Execution
- Code Execution
- Remote MCP Servers
- Requiring a Tool Call
To get incremental updates while the model continues to output its response, you can use the streaming overloads.
var options = new GeminiClientOptions { ApiKey = _apiKey, ModelId = "gemini-3.7-flash" };
IChatClient client = new GeminiChatClient(options);
await foreach (var update in client.GetStreamingResponseAsync("What is AI?"))
{
Console.Write(update);
}Using Microsoft.Extensions.AI.FunctionInvokingChatClient to handle automatic function invocation, it is simple to wire up function calling to arbitrary .NET functions.
var geminiClient = new GeminiChatClient(new GeminiClientOptions
{
ApiKey = _apiKey, ModelId = "gemini-3.7-flash"
});
[Description("Gets the current weather")]
static string GetCurrentWeather(string location, DateOnly date)
{
return $"It's raining in {location} on {date}.";
}
IChatClient client = new ChatClientBuilder(geminiClient)
.UseFunctionInvocation()
.Build();
List<ChatMessage> messages =
[
new(ChatRole.User, "Should I wear a rain coat in London tomorrow (1st Oct, 2000)? Get the current weather if needed.")
];
var options = new ChatOptions
{
Tools = [AIFunctionFactory.Create(GetCurrentWeather, nameof(GetCurrentWeather))]
};
var response = await client.GetResponseAsync(messages, options, cancellationToken);The Gemini API provides a code execution feature that enables the model to generate and run Python code and learn iteratively from the results until it arrives at a final output. You can enable and use this as follows.
var options = new GeminiClientOptions
{
ApiKey = _apiKey, ModelId = "gemini-3.7-flash"
};
IChatClient geminiClient = new GeminiChatClient(options);
var chatOptions = new ChatOptions { Tools = [new HostedCodeInterpreterTool()] };
var response = await geminiClient.GetResponseAsync(
[new(ChatRole.User, "What is the sum of the first 42 fibonacci numbers? Generate and run code to do the calculation.")],
chatOptions,
cancellationToken);Gemini can connect to a remote MCP server itself, discover its tools and call them server-side. Map a
HostedMcpServerTool onto that with the server's name and its streamable HTTP endpoint.
var options = new GeminiClientOptions
{
ApiKey = _apiKey, ModelId = "gemini-3.7-flash"
};
IChatClient geminiClient = new GeminiChatClient(options);
var chatOptions = new ChatOptions
{
Tools =
[
new HostedMcpServerTool("weather", "https://example.com/mcp")
{
ApprovalMode = HostedMcpServerToolApprovalMode.NeverRequire,
},
]
};
var response = await geminiClient.GetResponseAsync(
[new(ChatRole.User, "Will it rain in London tomorrow?")],
chatOptions,
cancellationToken);Gemini rejects a request whose MCP servers break its own naming and addressing rules:
ServerNamemust be lowercase snake_case (weather,rain_forecast), and must be unique across the servers in one request.ServerAddressmust be an absolute URL.
The rest of HostedMcpServerTool is what Gemini cannot honour. Rather than dropping a restriction the
caller asked for, the mapping throws a GeminiMappingException:
AllowedToolsmust benull. Gemini accepts an allow-list and then ignores it, so the model would still be offered every tool the server exposes. Restrict the tools on the server instead.ApprovalModemust be set toHostedMcpServerToolApprovalMode.NeverRequire. Gemini runs the tools server-side with no approval hook, so that is the only mode it can honour. The defaultnullis rejected too: M.E.AI documents it as a value some providers treat asAlwaysRequire, and the OpenAI client does exactly that, so reading it asNeverRequirehere would quietly turn "unspecified" into consent.- An MCP server cannot be combined with
HostedWebSearchTool,HostedCodeInterpreterToolorHostedFileSearchToolin the same request, because Gemini rejects that combination.AIFunctiontools can be used alongside it.
ServerDescription has no Gemini counterpart and is ignored.
ChatOptions.ToolMode = ChatToolMode.RequireAny (or RequireSpecific) makes Gemini require a function
call, so the request must also declare at least one AIFunction. Asked to require one when only hosted
tools are present, the model loops until it hits the tool-call cap and returns an empty response with
finishReason: TOO_MANY_TOOL_CALLS, having billed every round-trip it made along the way. An MCP server is
no exception, because Gemini runs its tools server-side and no client-visible call ever satisfies the mode.
This library therefore throws a GeminiMappingException instead of sending such a request.