Repository navigation
MCP (Model Context Protocol) Integration
The Model Context Protocol (MCP) is a standard way to connect AI models to external tools, data sources and services. An MCP server can expose tools that, for example:
- call external APIs and services
- read and change files
- query databases
- run custom business logic
- connect to third-party products
The SDK's McpTool connects to any MCP server, discovers its tools, and exposes them to Gemini as ordinary function tools. When the model calls one of them, McpTool forwards the call to the MCP server and returns the result to the model.
dotnet add package Google_GenerativeAI.ToolsGoogle_GenerativeAI.Tools 4.x is built on the official MCP C# SDK, ModelContextProtocol 2.x (currently 2.4.0). The MCP types live in the GenerativeAI.Tools.Mcp namespace.
using GenerativeAI;
using GenerativeAI.Tools.Mcp;
var transport = McpTransportFactory.CreateStdioTransport(
name: "everything",
command: "npx",
arguments: new[] { "-y", "@modelcontextprotocol/server-everything" });
await using var mcpTool = await McpTool.CreateAsync(transport);
var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
model.AddFunctionTool(mcpTool);
var response = await model.GenerateContentAsync("Use the echo tool to say hello");
Console.WriteLine(response.Text);The model sees every tool the server provides, calls them when needed, and uses the results in its answer.
McpTransportFactory creates an IClientTransport (from the MCP SDK) for the common cases. You can also pass any other IClientTransport straight to McpTool.CreateAsync.
Launches the MCP server as a child process and talks to it over standard input/output.
var transport = McpTransportFactory.CreateStdioTransport(
name: "filesystem",
command: "npx",
arguments: new[] { "-y", "@modelcontextprotocol/server-filesystem", "/path/to/files" },
environmentVariables: new Dictionary<string, string> { ["LOG_LEVEL"] = "info" }, // optional
workingDirectory: null); // optionalConnects to a remote MCP server. The factory uses the MCP SDK's HttpClientTransport with its default mode, which tries Streamable HTTP first and falls back to Server-Sent Events (SSE) if the server doesn't support it.
var transport = McpTransportFactory.CreateHttpTransport("http://localhost:8080");
// Overloads accept a Uri, an optional HttpClient and optional additional headers.Sends Authorization: Bearer {token} with every request.
var transport = McpTransportFactory.CreateHttpTransportWithAuth(
baseUrl: "https://api.example.com/mcp",
authToken: "your-api-key");var headers = new Dictionary<string, string>
{
{ "X-API-Key", "your-api-key" },
{ "X-Custom-Header", "custom-value" }
};
var transport = McpTransportFactory.CreateHttpTransportWithHeaders(
baseUrl: "https://api.example.com/mcp",
headers: headers);- Stdio (local servers): faster and more contained; good for file operations and local tools.
- HTTP (remote servers): good for shared services, APIs and cloud deployments.
var options = new McpToolOptions
{
ConnectionTimeoutMs = 30000, // connection timeout (default 30000)
AutoReconnect = true, // reconnect when the connection is lost (default true)
MaxReconnectAttempts = 3, // default 3
ThrowOnToolCallFailure = false, // false: failures are returned to the model as error responses (default false)
IncludeDetailedErrors = true // include error details in those responses (default true)
};
await using var mcpTool = await McpTool.CreateAsync(transport, options);A transport instance can't be reused after it closes. Pass a factory so McpTool can create a fresh transport when it reconnects:
await using var mcpTool = await McpTool.CreateAsync(
transportFactory: () => McpTransportFactory.CreateStdioTransport(
"everything", "npx", new[] { "-y", "@modelcontextprotocol/server-everything" }),
options: new McpToolOptions { AutoReconnect = true });-
@modelcontextprotocol/server-filesystem: file system operations -
@modelcontextprotocol/server-github: GitHub integration -
@modelcontextprotocol/server-google-drive: Google Drive access -
@modelcontextprotocol/server-slack: Slack integration -
@modelcontextprotocol/server-postgres: PostgreSQL database -
@modelcontextprotocol/server-everything: demo server with many tools
Some of these reference servers have moved to community maintenance; check the MCP servers repository for the current list.
var transport = McpTransportFactory.CreateStdioTransport(
name: "filesystem",
command: "npx",
arguments: new[] { "-y", "@modelcontextprotocol/server-filesystem", "/Users/username/documents" });
await using var mcpTool = await McpTool.CreateAsync(transport);
var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
model.AddFunctionTool(mcpTool);
// The model can now list, read, write and search files under the root directory.
var response = await model.GenerateContentAsync(
"Find all Python files in the current directory and summarize their contents");foreach (var name in mcpTool.GetAvailableFunctions())
{
var info = mcpTool.GetFunctionInfo(name); // FunctionDeclaration?
Console.WriteLine($"Tool: {name}");
Console.WriteLine($"Description: {info?.Description}");
}Each MCP tool's JSON schema is converted to a Gemini Schema (FunctionDeclaration.Parameters). If the schema uses features Gemini's schema subset doesn't support (for example dictionaries with additionalProperties), it is sent as FunctionDeclaration.ParametersJsonSchema instead. That fallback works with generateContent but not with the Live API.
Other members: IsConnected, Client (the underlying MCP SDK McpClient), and RefreshToolsAsync() to reload the tool list after the server's tools change:
await mcpTool.RefreshToolsAsync();using System.Text.Json.Nodes;
using GenerativeAI.Types;
var functionCall = new FunctionCall("echo")
{
Args = new JsonObject { ["message"] = "Hello, MCP!" }
};
FunctionResponse? result = await mcpTool.CallAsync(functionCall);
Console.WriteLine(result?.Response?.ToJsonString());var transports = new List<IClientTransport> // IClientTransport is in ModelContextProtocol.Client
{
McpTransportFactory.CreateStdioTransport("filesystem", "npx",
new[] { "-y", "@modelcontextprotocol/server-filesystem", "/path" }),
McpTransportFactory.CreateStdioTransport("github", "npx",
new[] { "-y", "@modelcontextprotocol/server-github" })
};
var mcpTools = await McpTool.CreateMultipleAsync(transports);
var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
foreach (var tool in mcpTools)
model.AddFunctionTool(tool);
var response = await model.GenerateContentAsync(
"Read the README.md file and create a GitHub issue summarizing it");
foreach (var tool in mcpTools)
await tool.DisposeAsync();var model = new GenerativeModel(apiKey, GoogleAIModels.Gemini38Flash);
model.AddFunctionTool(mcpTool);
var chat = model.StartChat();
var response1 = await chat.GenerateContentAsync("List the files in the current directory");
Console.WriteLine(response1.Text);
var response2 = await chat.GenerateContentAsync("Read the contents of the largest file you found");
Console.WriteLine(response2.Text);
// The chat keeps context across turns and tool calls.McpTool targets GenerativeModel. For an IChatClient (see Microsoft.Extensions.AI IChatClient), use the MCP SDK directly: its McpClientTool is an AIFunction.
var mcpClientTools = await mcpTool.Client!.ListToolsAsync();
var chatOptions = new ChatOptions { Tools = mcpClientTools.Cast<AITool>().ToList() };McpTool is a normal function tool, so GenerativeModel.FunctionCallingBehaviour applies (see Function Calling).
Automatic (default):
model.AddFunctionTool(mcpTool);
model.FunctionCallingBehaviour.AutoCallFunction = true;
model.FunctionCallingBehaviour.AutoReplyFunction = true;
var response = await model.GenerateContentAsync("What's the weather in San Francisco?");
// The model calls the MCP tool and uses the result in its answer.Manual:
model.FunctionCallingBehaviour.AutoCallFunction = false;
var userTurn = new Content { Role = Roles.User };
userTurn.AddText("What's the weather in San Francisco?");
var request = new GenerateContentRequest { Contents = new List<Content> { userTurn } };
var response = await model.GenerateContentAsync(request);
var functionCall = response.GetFunction();
if (functionCall != null)
{
var result = await mcpTool.CallAsync(functionCall);
request.Contents.Add(response.Candidates![0].Content!); // model's function-call turn
var resultTurn = new Content { Role = Roles.User }; // function results use the "user" role in v4
resultTurn.AddPart(new Part { FunctionResponse = result });
request.Contents.Add(resultTurn);
var finalResponse = await model.GenerateContentAsync(request);
Console.WriteLine(finalResponse.Text);
}Since v4.0.0 function results go in a user turn because Gemini 3.x rejects the legacy function role. If older code still sends Role = "function", the SDK converts it to user automatically.
var options = new McpToolOptions
{
ThrowOnToolCallFailure = false, // return failures to the model as error responses
IncludeDetailedErrors = true
};
await using var mcpTool = await McpTool.CreateAsync(transport, options);
try
{
var response = await mcpTool.CallAsync(new FunctionCall("echo") { Args = new JsonObject() });
// If the tool fails while running, the response contains an "error" entry instead of throwing.
}
catch (ArgumentException ex)
{
// A tool name the server doesn't provide always throws ArgumentException.
Console.WriteLine($"Error: {ex.Message}");
}If the connection is lost and AutoReconnect is on, CallAsync tries to reconnect before calling the tool.
var options = new McpToolOptions
{
AutoReconnect = true,
MaxReconnectAttempts = 5,
ConnectionTimeoutMs = 60000
};
try
{
await using var mcpTool = await McpTool.CreateAsync(transport, options);
}
catch (OperationCanceledException)
{
Console.WriteLine("Connection timed out - is the server running?");
}
catch (Exception ex)
{
Console.WriteLine($"Failed to connect: {ex.Message}");
}-
Dispose the tool.
McpToolimplementsIAsyncDisposableandIDisposable; useawait using, or callDisposeAsync()in afinallyblock. - Set timeouts for the server type, for example 30 seconds for network servers and 5 seconds for local ones.
-
Use detailed errors during development (
IncludeDetailedErrors = true) and consider turning them off in production. - Let the model discover tools. Prompts like "help me with this using whatever tools you have" are more flexible than naming a specific tool.
using GenerativeAI;
using GenerativeAI.Tools.Mcp;
var transport = McpTransportFactory.CreateStdioTransport(
name: "filesystem",
command: "npx",
arguments: new[]
{
"-y", "@modelcontextprotocol/server-filesystem",
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments)
});
var options = new McpToolOptions { ConnectionTimeoutMs = 30000, AutoReconnect = true, IncludeDetailedErrors = true };
await using var mcpTool = await McpTool.CreateAsync(transport, options);
Console.WriteLine("Available tools:");
foreach (var toolName in mcpTool.GetAvailableFunctions())
Console.WriteLine($" - {toolName}: {mcpTool.GetFunctionInfo(toolName)?.Description}");
var model = new GenerativeModel(
apiKey: Environment.GetEnvironmentVariable("GOOGLE_API_KEY")!,
model: GoogleAIModels.Gemini38Flash);
model.AddFunctionTool(mcpTool);
var chat = model.StartChat();
Console.WriteLine((await chat.GenerateContentAsync("What files are in my Documents folder? Focus on text files.")).Text);
Console.WriteLine((await chat.GenerateContentAsync("Summarize README.md if it exists.")).Text);
Console.WriteLine((await chat.GenerateContentAsync("Create a new file called 'summary.txt' with your summary.")).Text);A runnable sample is in samples/McpIntegrationDemo.
Server does not start / cannot connect
- Check the command, for example
npx -y @modelcontextprotocol/server-name. - Check that Node.js is installed (
node --version) for npm-based servers. - Run the server manually first.
- Increase
ConnectionTimeoutMs.
GetAvailableFunctions() returns an empty list
- Check the server's logs for errors.
- Check that the server supports a compatible protocol version.
- Call
RefreshToolsAsync().
Tool calls fail
- Turn on
IncludeDetailedErrors. - Inspect parameter schemas with
GetFunctionInfo(). - Check that parameter names and types match.
- Call the tool yourself with
CallAsync().
Connection drops
- Turn on
AutoReconnectand increaseMaxReconnectAttempts. - Use the transport-factory overload of
CreateAsyncso a new transport can be created on reconnect. - Check the server's stability.
Getting Started
- Initialization
- Authentication
- Configuration & Error Handling
- Supported Models
- Migration Guide (v4)
- Release Notes (v4)
Core Features
- Generate Content
- Chat Session
- Streaming
- Multimodal Features
- JSON Mode
- Function Calling
- Embeddings
- Context Caching
Gemini APIs
- Interactions API
- Multimodal Live API
- File Search
- Batch API
- Model Tuning
- Webhooks
- Agent APIs
- Image Generation
- Video Generation
- Speech Generation
- Files Client
- Model Client
Vertex AI
Integrations
Guides
Legacy