Files
awesome-copilot/skills/dotnet-mcp-builder/references/client.md
T
Adrien Clerbois 2c275f2ef9 feat(skills): add dotnet-mcp-builder, deprecate csharp-mcp-server-gen… (#1645)
* feat(skills): add dotnet-mcp-builder, deprecate csharp-mcp-server-generator

Adds a comprehensive skill for building MCP (Model Context Protocol)
servers in C#/.NET against the official ModelContextProtocol 1.x NuGet
packages. Covers both transports (STDIO, Streamable HTTP — SSE is
deprecated) and every primitive in the current MCP spec (2025-11-25):
tools, prompts, resources, elicitation (form + URL mode), sampling,
roots, completions, logging, and MCP Apps. Includes a thin .NET MCP
client reference and testing guidance (MCP Inspector + in-memory
transport for unit tests).

Steers the model toward the current stable 1.x packages instead of the
0.x previews it tends to pin by default, and enforces the STDIO
stdout/stderr trap.

Also deprecates the existing csharp-mcp-server-generator skill, which
predates ModelContextProtocol 1.0 and only covered a subset of the
current spec. Its SKILL.md now redirects users to dotnet-mcp-builder so
existing install URLs keep working without surprises.

* fix: address PR review from aaronpowell

- Delete csharp-mcp-server-generator skill (rather than deprecating it)
- Update mcp-apps.md pitfalls section to reference .NET Tool.Meta type
  instead of the serialized _meta JSON property names
- Rebuild docs/README.skills.md

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* chore: remove C# MCP development plugin files

* chore: remove csharp-mcp-development plugin entry from marketplace

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
2026-05-11 09:35:26 +10:00

192 lines
5.8 KiB
Markdown

# Building an MCP client in .NET
A short reference for *consuming* an MCP server from .NET — useful for testing your server, building agent harnesses, or wiring MCP into a Semantic Kernel / Microsoft.Extensions.AI pipeline.
For just *running* a server, ignore this file.
## Packages
```bash
dotnet add package ModelContextProtocol.Core # minimal: just client + transports
# or
dotnet add package ModelContextProtocol # adds DI/hosting helpers
```
## Connecting via STDIO (launching a server process)
```csharp
using ModelContextProtocol.Client;
var transport = new StdioClientTransport(new StdioClientTransportOptions
{
Command = "dotnet",
Arguments = ["run", "--project", "../MyMcpServer"],
EnvironmentVariables = new() { ["MY_API_KEY"] = "..." },
ShutdownTimeout = TimeSpan.FromSeconds(10),
StandardErrorLines = line => Console.Error.WriteLine($"[server] {line}")
});
await using var client = await McpClient.CreateAsync(transport);
```
`StandardErrorLines` is a great debugging aid — you'll see your server's logs as they happen.
## Connecting via HTTP (Streamable)
```csharp
using ModelContextProtocol.Client;
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://my-server.example.com/mcp"),
TransportMode = HttpTransportMode.StreamableHttp,
ConnectionTimeout = TimeSpan.FromSeconds(30),
AdditionalHeaders = new Dictionary<string, string>
{
["Authorization"] = "Bearer ..."
}
});
await using var client = await McpClient.CreateAsync(transport);
```
`TransportMode = AutoDetect` (the default) tries Streamable HTTP first and falls back to SSE — useful for older servers, but pin to `StreamableHttp` for new code so failures are loud.
## Listing and calling tools
```csharp
IList<McpClientTool> tools = await client.ListToolsAsync();
foreach (var t in tools)
Console.WriteLine($"- {t.Name}: {t.Description}");
var echo = tools.First(t => t.Name == "Echo");
CallToolResult result = await echo.CallAsync(new Dictionary<string, object?>
{
["message"] = "hello"
});
if (result.IsError == true)
{
var msg = result.Content.OfType<TextContentBlock>().FirstOrDefault()?.Text;
Console.Error.WriteLine($"Tool failed: {msg}");
return;
}
foreach (var block in result.Content)
{
switch (block)
{
case TextContentBlock text:
Console.WriteLine(text.Text);
break;
case ImageContentBlock image:
File.WriteAllBytes("out.png", image.DecodedData.ToArray());
break;
}
}
```
## Listing prompts and resources
```csharp
IList<McpClientPrompt> prompts = await client.ListPromptsAsync();
GetPromptResult pr = await client.GetPromptAsync("code_review",
new Dictionary<string, object?> { ["language"] = "csharp", ["code"] = "..." });
IList<McpClientResource> resources = await client.ListResourcesAsync();
ReadResourceResult rr = await client.ReadResourceAsync("config://app/settings");
```
## Subscribing to server notifications
```csharp
client.RegisterNotificationHandler(
NotificationMethods.ToolListChangedNotification,
async (notification, ct) =>
{
var updated = await client.ListToolsAsync(cancellationToken: ct);
Console.WriteLine($"Tool list changed; now {updated.Count} tools.");
});
```
## Handling server-to-client requests (sampling, elicitation, roots)
If your server uses these features, your client must handle them. Configure handlers when creating the client:
```csharp
await using var client = await McpClient.CreateAsync(transport, new McpClientOptions
{
Capabilities = new()
{
Sampling = new()
{
SamplingHandler = async (req, progress, ct) =>
{
// Route req.Messages to your IChatClient and return a CreateMessageResult.
var response = await myChatClient.GetResponseAsync(/* convert */, ct);
return new CreateMessageResult { /* fill in */ };
}
},
Elicitation = new()
{
ElicitationHandler = async (req, ct) =>
{
// Show req.Message + req.RequestedSchema to the user; collect input.
return new ElicitResult { Action = "accept", Content = collectedValues };
}
},
Roots = new()
{
RootsHandler = async (req, ct) =>
{
return new ListRootsResult
{
Roots = new[] { new Root { Uri = "file:///workspace", Name = "Workspace" } }
};
}
}
}
});
```
If you don't supply a handler and the server calls the feature, the call fails with a "method not supported" error.
## Using MCP tools as `IChatClient` function tools
If you're plugging MCP into a `Microsoft.Extensions.AI` pipeline, expose tools as `AIFunction`:
```csharp
using Microsoft.Extensions.AI;
IList<McpClientTool> mcpTools = await client.ListToolsAsync();
var chatOptions = new ChatOptions
{
Tools = mcpTools.Cast<AITool>().ToList()
};
var chatClient = new MyChatClient(...); // any IChatClient
var response = await chatClient.GetResponseAsync(messages, chatOptions);
```
`McpClientTool` implements `AIFunction` — function-calling middleware will invoke the right tool and feed the result back to the LLM automatically.
## Resuming a session (HTTP, stateful)
```csharp
var transport = new HttpClientTransport(new HttpClientTransportOptions
{
Endpoint = new Uri("https://my-server.example.com/mcp"),
KnownSessionId = previousSessionId
});
await using var client = await McpClient.ResumeSessionAsync(transport, new ResumeClientSessionOptions
{
ServerCapabilities = previousServerCapabilities,
ServerInfo = previousServerInfo
});
```
Useful for long-lived agent processes that survive transient network drops.