MCP clients connect to servers, discover capabilities, and invoke tools, prompts, and resources. The C# SDK provides a high-level McpClient class that handles protocol details, session management, and transport abstraction.
Clients are created using the McpClient.CreateAsync factory method:
using ModelContextProtocol.Client;using ModelContextProtocol.Protocol;var transport = new StdioClientTransport(new StdioClientTransportOptions{ Command = "npx", Arguments = ["-y", "@modelcontextprotocol/server-everything"],});await using var client = await McpClient.CreateAsync(transport);
var options = new McpClientOptions{ ClientInfo = new Implementation { Name = "MyClient", Version = "1.0.0", Description = "My custom MCP client" }, Capabilities = new ClientCapabilities { Sampling = new SamplingCapability(), Roots = new RootsCapability { ListChanged = true }, Elicitation = new ElicitationCapability { Form = new FormElicitationCapability(), Url = new UrlElicitationCapability() } }, ProtocolVersion = "2025-11-25", InitializationTimeout = TimeSpan.FromSeconds(30)};await using var client = await McpClient.CreateAsync(transport, options);
If ClientInfo is not specified, the SDK automatically populates it with information from the current process.
McpClientTool inherits from AIFunction, enabling seamless integration with IChatClient:
using Microsoft.Extensions.AI;// Get tools from MCP serverIList<McpClientTool> mcpTools = await client.ListToolsAsync();// Use with any IChatClientIChatClient chatClient = new OpenAIClient(apiKey).AsChatClient("gpt-4");var response = await chatClient.GetResponseAsync( "What's the weather like in Tokyo?", new ChatOptions { Tools = [.. mcpTools] });Console.WriteLine(response.Message.Text);
The chat client automatically:
Provides tool definitions to the LLM
Executes tool calls via MCP when requested
Returns results to the LLM for response generation
var result = await client.ReadResourceAsync("file:///path/to/file.txt");foreach (var content in result.Contents){ if (content is TextResourceContents textContent) { Console.WriteLine(textContent.Text); } else if (content is BlobResourceContents blobContent) { byte[] data = Convert.FromBase64String(blobContent.Blob); // Process binary data }}
if (client.ServerCapabilities.Prompts is not null){ IList<McpClientPrompt> prompts = await client.ListPromptsAsync(); foreach (var prompt in prompts) { Console.WriteLine($"{prompt.Name}: {prompt.Description}"); if (prompt.Arguments is not null) { foreach (var arg in prompt.Arguments) { Console.WriteLine($" Argument: {arg.Name} (required: {arg.Required})"); } } }}
Handlers = new McpClientHandlers{ ElicitationHandler = async (request, context, ct) => { if (request.Form is not null) { // Display form to user and collect input var userInput = await ShowFormToUserAsync(request.Form); return new ElicitResult { Form = new FormElicitationResult { Values = userInput } }; } else if (request.Url is not null) { // Open URL in browser await OpenUrlAsync(request.Url.Url); return new ElicitResult { Url = new UrlElicitationResult() }; } throw new InvalidOperationException("Unknown elicitation type"); }}
// Goodif (client.ServerCapabilities.Resources is { Subscribe: true }){ await client.SubscribeToResourceAsync(uri);}// Bad - may throw if server doesn't support subscriptionsawait client.SubscribeToResourceAsync(uri);
Use async disposal for proper cleanup
// Goodawait using var client = await McpClient.CreateAsync(transport);// Also goodvar client = await McpClient.CreateAsync(transport);try{ // Use client}finally{ await client.DisposeAsync();}
Implement timeout policies for long-running operations
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));var result = await client.CallToolAsync("slow_tool", args, cancellationToken: cts.Token);
Monitor server instructions for usage guidance
if (client.ServerInstructions is not null){ // Include in system prompt for LLM var systemPrompt = $"You have access to MCP tools. {client.ServerInstructions}";}