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>
This commit is contained in:
Adrien Clerbois
2026-05-11 01:35:26 +02:00
committed by GitHub
parent d944a73b58
commit 2c275f2ef9
20 changed files with 2272 additions and 122 deletions
@@ -0,0 +1,200 @@
# Other server features (completions, logging, progress, filters)
A quick reference for the smaller MCP server features beyond the core primitives. Each section is short — load this file when one of these comes up.
## Argument completions
Completions let the host autocomplete prompt arguments and resource template parameters. The user starts typing; the client asks the server "what are valid values?".
Implement via the low-level handler (no high-level attribute exists yet):
```csharp
builder.Services.Configure<McpServerOptions>(options =>
{
options.Capabilities ??= new();
options.Capabilities.Completions ??= new();
options.Capabilities.Completions.CompleteHandler = async (ctx, ct) =>
{
// ctx.Params.Ref tells us what they're completing (a prompt or resource).
// ctx.Params.Argument has the partial value typed so far.
var partial = ctx.Params.Argument.Value ?? "";
var matches = MyDataSource
.Where(x => x.StartsWith(partial, StringComparison.OrdinalIgnoreCase))
.Take(100)
.ToArray();
return new CompleteResult
{
Completion = new()
{
Values = matches,
HasMore = false,
Total = matches.Length
}
};
};
});
```
Useful for: project IDs, file names, enum values that depend on dynamic data.
## Logging
Servers can emit log messages that hosts surface in their UI (and the LLM can sometimes see). Use the standard `ILogger<T>` injected via DI — the SDK plumbs it through.
```csharp
public class WeatherTools
{
private readonly ILogger<WeatherTools> _log;
public WeatherTools(ILogger<WeatherTools> log) => _log = log;
[McpServerTool, Description("…")]
public string GetWeather(string city)
{
_log.LogInformation("Looking up weather for {City}", city);
return "...";
}
}
```
For STDIO servers, remember: console logging **must** go to stderr (`LogToStandardErrorThreshold = LogLevel.Trace`) — otherwise it corrupts the JSON-RPC stream. See [`transport-stdio.md`](./transport-stdio.md).
To send a log specifically over the MCP channel (so the *host UI* sees it, not just your container logs):
```csharp
await server.SendNotificationAsync(
NotificationMethods.LoggingMessageNotification,
new LoggingMessageNotificationParams
{
Level = LoggingLevel.Info,
Logger = "weather",
Data = JsonSerializer.SerializeToElement(new { city, latency_ms = 123 })
},
ct);
```
The client may have set a `setLevel` filter — don't spam levels below it.
## Progress notifications
For long-running tools, send progress updates so the host can display a spinner with text:
```csharp
[McpServerTool, Description("Processes a large dataset.")]
public static async Task<string> Process(
IMcpServer server,
RequestContext<CallToolRequestParams> ctx,
string datasetId,
CancellationToken ct)
{
var progressToken = ctx.Params.Meta?.ProgressToken;
for (int i = 0; i < 100; i++)
{
await Task.Delay(50, ct);
if (progressToken is not null)
{
await server.SendNotificationAsync(
NotificationMethods.ProgressNotification,
new ProgressNotificationParams
{
ProgressToken = progressToken,
Progress = i + 1,
Total = 100,
Message = $"Processing item {i + 1} of 100"
},
ct);
}
}
return "Done.";
}
```
Only send progress if the client passed a `progressToken` in the request meta — otherwise the host isn't listening.
## Notification handlers (server-side)
Servers can react to notifications the client sends:
```csharp
options.Capabilities ??= new();
options.Capabilities.NotificationHandlers ??= [];
options.Capabilities.NotificationHandlers[NotificationMethods.RootsListChangedNotification] =
async (notification, ct) =>
{
// Refresh root cache, etc.
};
options.Capabilities.NotificationHandlers[NotificationMethods.CancelledNotification] =
async (notification, ct) =>
{
// The client cancelled a request; if you have side-effects in flight, abort them.
};
```
## Filters / middleware
The SDK supports filters that wrap tool calls (think ASP.NET Core middleware for MCP). Use them for cross-cutting concerns: auth checks, telemetry, rate limiting, audit logging.
```csharp
builder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly()
.WithCallToolFilter(async (ctx, next) =>
{
var sw = Stopwatch.StartNew();
try
{
return await next(ctx);
}
finally
{
sw.Stop();
ctx.Server.Services?
.GetRequiredService<ILogger<Program>>()
.LogInformation("Tool {Tool} took {Ms}ms",
ctx.Params.Name, sw.ElapsedMilliseconds);
}
});
```
Similar `With*Filter` helpers exist for resources, prompts, and other capabilities — check the SDK API reference for the current set.
## Server instructions (system-prompt-ish)
You can supply instructions sent to the client at initialise time. Hosts may include them in the LLM's system prompt.
```csharp
builder.Services.AddMcpServer(options =>
{
options.ServerInstructions =
"Use the booking tools to schedule meetings. " +
"Always confirm with the user before booking via elicitation.";
});
```
Keep this short — every token here costs the user.
## Capabilities advertising
If you want to *not* advertise a capability you happen to have code for, you can mute it:
```csharp
builder.Services.AddMcpServer(options =>
{
options.Capabilities = new()
{
Tools = new(), // advertise tools
Prompts = new(), // advertise prompts
Resources = null, // do NOT advertise resources, even if some are registered
Logging = new()
};
});
```
By default, the SDK advertises everything you've registered — usually the right behaviour.