From e7cd58adad08e9c9b3666381b8969a16879ebc8b Mon Sep 17 00:00:00 2001 From: Leslie Richardson Date: Fri, 24 Jul 2026 14:43:06 -0700 Subject: [PATCH] Retire mcp-csharp-create, mcp-csharp-test, and mcp-csharp-publish skills Removes the remaining three mcp-csharp-* skills from the dotnet-ai plugin. Following the cut of mcp-csharp-debug (#938), this retires the rest of the family based on the cross-family skills-eval (#889) and the 30-day telemetry review (https://gist.github.com/AbhitejJohn/db57f38051ebf4e7de5556a4bd98ec15): - mcp-csharp-test: STRENGTHEN 0/5 families; 2 real users, 0 with >=5 invocations. - mcp-csharp-publish: STRENGTHEN 0/5 families; 0 real users. - mcp-csharp-create: wins only 2/5 families (Sonnet, Haiku) and REGRESSES both frontier models; impact 0.40 at avgN 2.6 (directional only); absent from the telemetry report. Validating it would require a full 5-family eval sweep with no evidence of real demand to justify the investment. Changes: - Delete plugins/dotnet-ai/skills/mcp-csharp-{create,test,publish}/ (SKILL.md + references) - Delete tests/dotnet-ai/mcp-csharp-{create,test,publish}/ (eval.yaml) - Remove the dotnet-ai CODEOWNERS block for the three skills and their tests - Repoint the skill-validator doc example to a surviving skill (technology-selection) dotnet-ai retains technology-selection. plugin.json auto-discovers ./skills/, so no manifest change is needed. Verified no remaining mcp-csharp-* references repo-wide (git grep). Refs dotnet/skills#889. Supersedes #939, #940, #941. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 6a8ce111-58e5-48a7-9f7f-8de972bdc693 --- .github/CODEOWNERS | 10 - .../src/docs/InvestigatingResults.md | 2 +- .../skills/mcp-csharp-create/SKILL.md | 263 ----------------- .../references/api-patterns.md | 148 ---------- .../references/transport-config.md | 141 --------- .../skills/mcp-csharp-publish/SKILL.md | 274 ------------------ .../references/docker-azure.md | 187 ------------ .../references/mcp-registry.md | 140 --------- .../references/nuget-packaging.md | 139 --------- .../dotnet-ai/skills/mcp-csharp-test/SKILL.md | 188 ------------ .../mcp-csharp-test/references/evaluations.md | 59 ---- .../references/test-patterns.md | 158 ---------- tests/dotnet-ai/mcp-csharp-create/eval.yaml | 69 ----- tests/dotnet-ai/mcp-csharp-publish/eval.yaml | 69 ----- tests/dotnet-ai/mcp-csharp-test/eval.yaml | 63 ---- 15 files changed, 1 insertion(+), 1909 deletions(-) delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-create/SKILL.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-create/references/api-patterns.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-create/references/transport-config.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-publish/SKILL.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-publish/references/docker-azure.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-publish/references/mcp-registry.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-publish/references/nuget-packaging.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-test/SKILL.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-test/references/evaluations.md delete mode 100644 plugins/dotnet-ai/skills/mcp-csharp-test/references/test-patterns.md delete mode 100644 tests/dotnet-ai/mcp-csharp-create/eval.yaml delete mode 100644 tests/dotnet-ai/mcp-csharp-publish/eval.yaml delete mode 100644 tests/dotnet-ai/mcp-csharp-test/eval.yaml diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 7fa936c0ec..6944addec2 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -24,16 +24,6 @@ /plugins/dotnet-advanced/skills/nuget-trusted-publishing/ @lewing @kartheekp-ms @dotnet/skills-csharp-language-reviewers /tests/dotnet-advanced/nuget-trusted-publishing/ @lewing @kartheekp-ms @dotnet/skills-csharp-language-reviewers -# dotnet-ai (AI generation development) -/plugins/dotnet-ai/skills/mcp-csharp-create/ @leslierichardson95 @cathysull -/tests/dotnet-ai/mcp-csharp-create/ @leslierichardson95 @cathysull - -/plugins/dotnet-ai/skills/mcp-csharp-publish/ @leslierichardson95 @cathysull -/tests/dotnet-ai/mcp-csharp-publish/ @leslierichardson95 @cathysull - -/plugins/dotnet-ai/skills/mcp-csharp-test/ @leslierichardson95 @cathysull -/tests/dotnet-ai/mcp-csharp-test/ @leslierichardson95 @cathysull - # dotnet-upgrade (migrating and upgrading .NET projects) /plugins/dotnet-upgrade/skills/thread-abort-migration/ @dotnet/appmodel @dotnet/skills-upgrade-reviewers /tests/dotnet-upgrade/thread-abort-migration/ @dotnet/appmodel @dotnet/skills-upgrade-reviewers diff --git a/eng/skill-validator/src/docs/InvestigatingResults.md b/eng/skill-validator/src/docs/InvestigatingResults.md index 4087d629d4..31b91b5224 100644 --- a/eng/skill-validator/src/docs/InvestigatingResults.md +++ b/eng/skill-validator/src/docs/InvestigatingResults.md @@ -36,7 +36,7 @@ This downloads all result artifacts into subdirectories, each containing `result From the PR comment, click the **Full results** link to open the GitHub Actions workflow run. Then: -1. Click on any job (e.g., `evaluate (mcp-csharp-create)`) +1. Click on any job (e.g., `evaluate (technology-selection)`) 2. Expand the **Upload results** step 3. Find the `Artifact download URL` in the log output 4. Download and extract diff --git a/plugins/dotnet-ai/skills/mcp-csharp-create/SKILL.md b/plugins/dotnet-ai/skills/mcp-csharp-create/SKILL.md deleted file mode 100644 index 15f90aca14..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-create/SKILL.md +++ /dev/null @@ -1,263 +0,0 @@ ---- -name: mcp-csharp-create -description: > - Create MCP servers using the C# SDK and .NET project templates. Covers scaffolding, - tool/prompt/resource implementation, and transport configuration for stdio and HTTP. - USE FOR: creating new MCP server projects, scaffolding with dotnet new mcpserver, adding - MCP tools/prompts/resources, choosing stdio vs HTTP transport, configuring MCP hosting in - Program.cs, setting up ASP.NET Core MCP endpoints with MapMcp. - DO NOT USE FOR: writing tests (use mcp-csharp-test), publishing or deploying - (use mcp-csharp-publish), building MCP clients, non-.NET MCP servers. -license: MIT ---- - -# C# MCP Server Creation - -Create Model Context Protocol servers using the official C# SDK (`ModelContextProtocol` NuGet package) and the `dotnet new mcpserver` project template. Servers expose tools, prompts, and resources that LLMs can discover and invoke via the MCP protocol. - -## When to Use - -- Starting a new MCP server project from scratch -- Adding tools, prompts, or resources to an existing MCP server -- Choosing between stdio (`--transport local`) and HTTP (`--transport remote`) transport -- Setting up ASP.NET Core hosting for an HTTP MCP server -- Wrapping an external API or service as MCP tools - -## Stop Signals - -- **Need tests or evaluations?** → Use `mcp-csharp-test` -- **Ready to publish?** → Use `mcp-csharp-publish` -- **Building an MCP client, not a server** → This skill is server-side only - -## Inputs - -| Input | Required | Description | -|-------|----------|-------------| -| Transport type | Yes | `stdio` (local/CLI) or `http` (remote/web). Ask user if not specified — default to stdio | -| Project name | Yes | PascalCase name for the project (e.g., `WeatherMcpServer`) | -| .NET SDK version | Recommended | .NET 10.0+ required. Check with `dotnet --version` | -| Service/API to wrap | Recommended | External API or service the tools will interact with | - -## Workflow - -> **Commit strategy:** Commit after completing each step so scaffolding and implementation are separately reviewable. - -### Step 1: Verify prerequisites - -1. Confirm .NET 10+ SDK: `dotnet --version` (install from https://dotnet.microsoft.com if < 10.0) - -2. Check if the MCP server template is already installed: - ```bash - dotnet new list mcpserver - ``` - If "No templates found" → install: `dotnet new install Microsoft.McpServer.ProjectTemplates` - -### Step 2: Choose transport - -| Choose **stdio** if… | Choose **HTTP** if… | -|----------------------|---------------------| -| Local CLI tool or IDE plugin | Cloud/web service deployment | -| Single user at a time | Multiple simultaneous clients | -| Running as subprocess (VS Code, GitHub Copilot) | Cross-network access needed | -| Simpler setup, no network config | Containerized deployment (Docker/Azure) | - -**Default:** stdio — simpler, works for most local development. Users can add HTTP later. - -### Step 3: Scaffold the project - -**stdio server:** -```bash -dotnet new mcpserver -n -``` -If the template times out or is unavailable, use `dotnet new console -n ` and add `dotnet add package ModelContextProtocol`. - -**HTTP server:** -```bash -dotnet new web -n -cd -dotnet add package ModelContextProtocol.AspNetCore -``` -This is the recommended approach — faster and more reliable than the template. The template also supports HTTP via `dotnet new mcpserver -n --transport remote`, but `dotnet new web` gives you more control over the project structure. - -**Template flags reference:** `--transport local` (stdio, default), `--transport remote` (ASP.NET Core HTTP), `--aot`, `--self-contained`. - -### Step 4: Implement tools - -Tools are the primary way MCP servers expose functionality. Add a class with `[McpServerToolType]` and methods with `[McpServerTool]`: - -```csharp -using ModelContextProtocol.Server; -using System.ComponentModel; - -[McpServerToolType] -public static class MyTools -{ - [McpServerTool, Description("Brief description of what the tool does.")] - public static async Task DoSomething( - [Description("What this parameter controls")] string input, - CancellationToken cancellationToken = default) - { - // Implementation - return $"Result: {input}"; - } -} -``` - -**Critical rules:** -- Every tool method **must** have a `[Description]` attribute — LLMs use this to decide when to call the tool -- Every parameter **must** have a `[Description]` attribute -- Accept `CancellationToken` in all async tools -- Use `[McpServerTool(Name = "custom_name")]` only if the default method name is unclear - -**DI injection patterns** — the SDK supports two styles: - -1. **Method parameter injection (static class):** DI services appear as method parameters. The SDK resolves them automatically — they do not appear in the tool schema. - -2. **Constructor injection (non-static class):** Use when tools need shared state or multiple services: -```csharp -[McpServerToolType] -public class ApiTools(HttpClient httpClient, ILogger logger) -{ - [McpServerTool, Description("Fetch a resource by ID.")] - public async Task FetchResource( - [Description("Resource identifier")] string id, - CancellationToken cancellationToken = default) - { - logger.LogInformation("Fetching {Id}", id); - return await httpClient.GetStringAsync($"/api/{id}", cancellationToken); - } -} -``` -Register services in Program.cs: -```csharp -var builder = Host.CreateApplicationBuilder(args); -builder.Logging.AddConsole(options => - options.LogToStandardErrorThreshold = LogLevel.Trace); - -builder.Services.AddHttpClient(); // registers IHttpClientFactory + HttpClient -// ILogger is registered by default — no extra setup needed. - -builder.Services.AddMcpServer() - .WithStdioServerTransport() - .WithToolsFromAssembly(); // discovers non-static [McpServerToolType] classes - -await builder.Build().RunAsync(); -``` - -**For the full attribute reference, return types, DI injection, and builder API patterns**, see [references/api-patterns.md](references/api-patterns.md). - -### Step 5: Add prompts and resources (optional) - -**Prompts** — reusable LLM interaction templates: -```csharp -[McpServerPromptType] -public static class MyPrompts -{ - [McpServerPrompt, Description("Summarize content into one sentence.")] - public static ChatMessage Summarize( - [Description("Content to summarize")] string content) => - new(ChatRole.User, $"Summarize this into one sentence: {content}"); -} -``` - -**Resources** — data the LLM can read: -```csharp -[McpServerResourceType] -public static class MyResources -{ - [McpServerResource(UriTemplate = "config://app", Name = "App Config", - MimeType = "application/json"), Description("Application configuration")] - public static string GetConfig() => JsonSerializer.Serialize(AppConfig.Current); -} -``` - -### Step 6: Configure Program.cs - -**stdio transport:** -```csharp -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; -using ModelContextProtocol.Server; - -var builder = Host.CreateApplicationBuilder(args); -builder.Logging.AddConsole(options => - options.LogToStandardErrorThreshold = LogLevel.Trace); // CRITICAL: stderr only - -builder.Services.AddMcpServer() - .WithStdioServerTransport() - .WithToolsFromAssembly(); - -await builder.Build().RunAsync(); -``` - -**HTTP transport:** -```csharp -using ModelContextProtocol.Server; - -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddMcpServer() - .WithHttpTransport() - .WithToolsFromAssembly(); - -// Register services your tools need via DI -// builder.Services.AddHttpClient(); -// builder.Services.AddSingleton(); - -var app = builder.Build(); -app.MapMcp(); // exposes MCP endpoint at /mcp (Streamable HTTP) -app.MapGet("/health", () => "ok"); // health check for container orchestrators -app.Run(); -``` - -**Key HTTP details:** `MapMcp()` defaults to `/mcp` path. For containers, set `ASPNETCORE_URLS=http://+:8080` and `EXPOSE 8080`. The MCP HTTP protocol uses Streamable HTTP — no special client config needed beyond the URL. - -**For transport configuration details** (stateless mode, auth, path prefix, `HttpContextAccessor`), see [references/transport-config.md](references/transport-config.md). - -### Step 7: Verify the server starts - -```bash -cd -dotnet build -dotnet run -``` - -For stdio: the process starts and waits for JSON-RPC input on stdin. -For HTTP: the server listens on the configured port. - -## Validation - -- [ ] Project builds with no errors (`dotnet build`) -- [ ] All tool classes have `[McpServerToolType]` attribute -- [ ] All tool methods have `[McpServerTool]` and `[Description]` attributes -- [ ] All parameters have `[Description]` attributes -- [ ] stdio: logging directed to stderr, not stdout -- [ ] HTTP: `app.MapMcp()` is called in Program.cs -- [ ] Server starts successfully with `dotnet run` - -## Common Pitfalls - -| Pitfall | Solution | -|---------|----------| -| stdio server outputs garbage or hangs | Logging to stdout corrupts JSON-RPC protocol. Set `LogToStandardErrorThreshold = LogLevel.Trace` | -| Tool not discovered by LLM clients | Missing `[McpServerToolType]` on the class or `[McpServerTool]` on the method. Verify `.WithToolsFromAssembly()` in Program.cs | -| LLM doesn't understand when to use a tool | Add clear `[Description]` attributes on both the method and all parameters | -| `WithToolsFromAssembly()` fails in AOT | Reflection-based discovery is incompatible with Native AOT. Use `.WithTools()` instead | -| Parameters not appearing in tool schema | `CancellationToken`, `IMcpServer`, and DI services are injected automatically — they do not appear in the schema. Only parameters with `[Description]` are exposed | -| HTTP server returns 404 | `app.MapMcp()` must be called. Check the request path matches the configured route | - -## Related Skills - -- `mcp-csharp-test` — Unit tests, integration tests, evaluations -- `mcp-csharp-publish` — NuGet, Docker, Azure deployment - -## Reference Files - -- [references/api-patterns.md](references/api-patterns.md) — Complete attribute reference, return types, DI injection, builder API, dynamic tools, experimental APIs. **Load when:** implementing tools, prompts, or resources beyond the basic patterns shown above. -- [references/transport-config.md](references/transport-config.md) — Detailed transport configuration: stateless HTTP mode, OAuth/auth, custom path prefix, `HttpContextAccessor`, OpenTelemetry observability. **Load when:** configuring advanced transport options or authentication. - -## More Info - -- [C# MCP SDK](https://github.com/modelcontextprotocol/csharp-sdk) — Official SDK repository -- [Build an MCP server (.NET)](https://learn.microsoft.com/dotnet/ai/quickstarts/build-mcp-server) — Microsoft quickstart -- [MCP Specification](https://modelcontextprotocol.io/specification/) — Protocol specification diff --git a/plugins/dotnet-ai/skills/mcp-csharp-create/references/api-patterns.md b/plugins/dotnet-ai/skills/mcp-csharp-create/references/api-patterns.md deleted file mode 100644 index f01e357c71..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-create/references/api-patterns.md +++ /dev/null @@ -1,148 +0,0 @@ -# C# MCP SDK API Patterns - -Complete reference for MCP server implementation patterns using the C# SDK. - -## Attribute Reference - -### Tool Attributes - -| Attribute | Target | Key Properties | -|-----------|--------|----------------| -| `[McpServerToolType]` | Class | Marks class as containing tool methods | -| `[McpServerTool]` | Method | `Name`, `Title`, `Destructive`, `Idempotent`, `OpenWorld`, `ReadOnly` | -| `[Description("...")]` | Method/Parameter | From `System.ComponentModel` — provides LLM-visible descriptions | -| `[McpMeta("key", value)]` | Any | Adds `_meta` entries to the MCP protocol response | - -### Prompt Attributes - -| Attribute | Target | Key Properties | -|-----------|--------|----------------| -| `[McpServerPromptType]` | Class | Marks class as containing prompt methods | -| `[McpServerPrompt]` | Method | `Name`, `Title` | - -### Resource Attributes - -| Attribute | Target | Key Properties | -|-----------|--------|----------------| -| `[McpServerResourceType]` | Class | Marks class as containing resource methods | -| `[McpServerResource]` | Method | `UriTemplate`, `Name`, `Title`, `MimeType` | - -## Tool Return Types - -Tools can return any of these types (or their `Task`/`ValueTask` async variants): - -| Return Type | Behavior | -|-------------|----------| -| `string` | Wrapped as `TextContentBlock` | -| `TextContentBlock` | Text content with optional annotations | -| `ImageContentBlock` | Base64-encoded image data | -| `AudioContentBlock` | Base64-encoded audio data | -| `EmbeddedResourceBlock` | Resource reference | -| `CallToolResult` | Full control over content blocks and `isError` flag | -| `IEnumerable` | Multiple content blocks | - -## Injected Parameters - -These types are automatically injected by the framework and **do not appear** in the tool's JSON schema: - -| Type | Purpose | -|------|---------| -| `CancellationToken` | Cooperative cancellation | -| `IMcpServer` / `McpServer` | Access to server instance for notifications, logging | -| `RequestContext` | Full request context, progress tokens | -| `IProgress` | Report progress back to the client | -| Any DI-registered service | Constructor or method parameter injection | - -Example with DI and progress: -```csharp -[McpServerToolType] -public class MyTools(IHttpClientFactory httpFactory) -{ - [McpServerTool, Description("Fetches data from API")] - public async Task FetchData( - [Description("Resource identifier")] string resourceId, - IProgress progress, - CancellationToken cancellationToken) - { - progress.Report(new() { Progress = 0, Total = 100 }); - var client = httpFactory.CreateClient(); - var result = await client.GetStringAsync($"/api/{resourceId}", cancellationToken); - progress.Report(new() { Progress = 100, Total = 100 }); - return result; - } -} -``` - -## Builder API - -The fluent builder API configures the MCP server via dependency injection: - -```csharp -services.AddMcpServer() - // Transports (choose one) - .WithStdioServerTransport() // stdio: Generic Host - .WithHttpTransport() // HTTP: ASP.NET Core - - // Register primitives (attribute-based) - .WithTools() // Specific class - .WithToolsFromAssembly() // All [McpServerToolType] in entry assembly - .WithPrompts() - .WithPromptsFromAssembly() - .WithResources() - .WithResourcesFromAssembly() - - // Register primitives (handler-based) - .WithListToolsHandler(async (ctx, ct) => { ... }) - .WithCallToolHandler(async (ctx, ct) => { ... }) - - // Middleware - .WithRequestFilters(filters => { ... }); -``` - -> **AOT warning:** `.WithToolsFromAssembly()` uses reflection and is not compatible with Native AOT. Use `.WithTools()` for AOT scenarios. - -## Dynamic Tool Creation - -Create tools at runtime without attribute-decorated classes: - -```csharp -var tool = McpServerTool.Create( - (int count, string prefix) => Enumerable.Range(1, count).Select(i => $"{prefix}-{i}"), - new McpServerToolCreateOptions { Name = "generate_ids", Description = "Generate sequential IDs" }); -``` - -## McpServerOptions - -Configure server behavior via `McpServerOptions`: - -```csharp -services.AddMcpServer(options => -{ - options.ServerInfo = new() { Name = "MyServer", Version = "1.0.0" }; - options.ServerInstructions = "You are connected to MyService. Use tools to query data."; - options.Capabilities = new() - { - Tools = new() { ListChanged = true }, - Resources = new() { Subscribe = true, ListChanged = true } - }; -}); -``` - -Key properties: `ServerInfo`, `Capabilities`, `ServerInstructions`, `InitializationTimeout`, `ToolCollection`, `ResourceCollection`, `PromptCollection`. - -## Experimental APIs - -| Diagnostic ID | Feature | Suppression | -|---------------|---------|-------------| -| `MCPEXP001` | Tasks feature | `#pragma warning disable MCPEXP001` | -| `MCPEXP002` | Subclassing `McpServer`/`McpClient` | `#pragma warning disable MCPEXP002` | - -Suppress project-wide: `MCPEXP001;MCPEXP002` in `.csproj`. - -## NuGet Packages - -| Package | When to Use | -|---------|-------------| -| `ModelContextProtocol` | **Default.** Hosting, DI, attribute-based discovery | -| `ModelContextProtocol.AspNetCore` | HTTP servers with ASP.NET Core (`MapMcp()`) | -| `ModelContextProtocol.Core` | Minimum dependencies — low-level client/server APIs only | diff --git a/plugins/dotnet-ai/skills/mcp-csharp-create/references/transport-config.md b/plugins/dotnet-ai/skills/mcp-csharp-create/references/transport-config.md deleted file mode 100644 index 70181831f9..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-create/references/transport-config.md +++ /dev/null @@ -1,141 +0,0 @@ -# Transport Configuration - -Detailed configuration for stdio and HTTP transports in C# MCP servers. - -## Stdio Transport (Generic Host) - -Uses `Microsoft.Extensions.Hosting` for the application lifecycle: - -```csharp -var builder = Host.CreateApplicationBuilder(args); - -// CRITICAL: All logging must go to stderr — stdout is reserved for JSON-RPC -builder.Logging.AddConsole(options => - options.LogToStandardErrorThreshold = LogLevel.Trace); - -builder.Services.AddMcpServer() - .WithStdioServerTransport() - .WithToolsFromAssembly(); - -await builder.Build().RunAsync(); -``` - -### Stdio Key Points - -- `stdout` carries JSON-RPC messages — **never** write anything else to stdout -- All logging, diagnostics, and debug output must use stderr -- The process runs as a subprocess of the MCP client -- No network configuration needed - -## HTTP Transport (ASP.NET Core) - -Uses ASP.NET Core with Streamable HTTP (default) or SSE (legacy): - -```csharp -var builder = WebApplication.CreateBuilder(args); -builder.Services.AddMcpServer() - .WithHttpTransport() - .WithToolsFromAssembly(); - -var app = builder.Build(); -app.MapMcp(); -app.Run(); -``` - -### Custom Path Prefix - -```csharp -app.MapMcp("/custom-mcp-path"); -``` - -### Stateless Mode - -Disables session state — each request is independent: - -```csharp -builder.Services.AddMcpServer() - .WithHttpTransport(options => options.Stateless = true); -``` - -### Idle Timeout - -Configure session cleanup: - -```csharp -builder.Services.AddMcpServer() - .WithHttpTransport(options => options.IdleTimeout = TimeSpan.FromMinutes(30)); -``` - -### Port Configuration - -```csharp -app.Run("http://localhost:3001"); -// or via launchSettings.json / ASPNETCORE_URLS environment variable -``` - -## Authentication and Authorization - -### JWT Bearer Auth - -```csharp -builder.Services.AddAuthentication() - .AddJwtBearer(options => - { - options.Authority = "https://your-auth-server"; - options.Audience = "mcp-server"; - }); - -builder.Services.AddAuthorization(); - -var app = builder.Build(); -app.UseAuthentication(); -app.UseAuthorization(); -app.MapMcp().RequireAuthorization(); -``` - -### Accessing HttpContext in Tools - -Register `HttpContextAccessor` to access HTTP request details from tool methods: - -```csharp -builder.Services.AddHttpContextAccessor(); - -[McpServerToolType] -public class AuthAwareTools(IHttpContextAccessor httpContextAccessor) -{ - [McpServerTool, Description("Returns the authenticated user's ID")] - public string GetCurrentUser() - { - var user = httpContextAccessor.HttpContext?.User; - return user?.Identity?.Name ?? "anonymous"; - } -} -``` - -### OAuth 2.0 with Dynamic Client Registration - -The SDK includes a full OAuth sample in `samples/ProtectedMcpServer/` covering: -- JWT bearer token validation -- OAuth 2.0 authorization flows -- Dynamic Client Registration (RFC 7591) - -## OpenTelemetry Observability - -Built-in distributed tracing and metrics: - -| Component | Name | -|-----------|------| -| `ActivitySource` | `Experimental.ModelContextProtocol` | -| `Meter` | `Experimental.ModelContextProtocol` | - -```csharp -builder.Services.AddOpenTelemetry() - .WithTracing(tracing => tracing - .AddSource("Experimental.ModelContextProtocol") - .AddAspNetCoreInstrumentation()) - .WithMetrics(metrics => metrics - .AddMeter("Experimental.ModelContextProtocol")); -``` - -Trace context propagated via `_meta.traceparent` across client/server boundaries. -Metrics follow [MCP semantic conventions](https://github.com/open-telemetry/semantic-conventions/blob/main/docs/gen-ai/mcp.md#metrics). diff --git a/plugins/dotnet-ai/skills/mcp-csharp-publish/SKILL.md b/plugins/dotnet-ai/skills/mcp-csharp-publish/SKILL.md deleted file mode 100644 index db8bf91faa..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-publish/SKILL.md +++ /dev/null @@ -1,274 +0,0 @@ ---- -name: mcp-csharp-publish -description: > - Publish and deploy C# MCP servers. Covers NuGet packaging for stdio servers, Docker - containerization for HTTP servers, Azure Container Apps and App Service deployment, - and publishing to the official MCP Registry. - USE FOR: packaging stdio MCP servers as NuGet tools, creating Dockerfiles for HTTP MCP - servers, deploying to Azure Container Apps or App Service, publishing to the MCP Registry - at registry.modelcontextprotocol.io, configuring server.json for MCP package metadata, - setting up CI/CD for MCP server publishing. - DO NOT USE FOR: publishing general NuGet libraries (not MCP-specific), general Docker - guidance unrelated to MCP, creating new servers (use mcp-csharp-create), writing tests - (use mcp-csharp-test). -license: MIT ---- - -# C# MCP Server Publishing - -Publish and deploy MCP servers to their target platforms. stdio servers are distributed as NuGet tool packages. HTTP servers are containerized and deployed to Azure or other container hosts. Both can optionally be listed in the official MCP Registry. - -## When to Use - -- Packaging a stdio MCP server for NuGet distribution -- Creating a Docker container for an HTTP MCP server -- Deploying to Azure Container Apps or App Service -- Publishing to the official MCP Registry for discoverability -- Setting up `server.json` metadata for the MCP Registry - -## Stop Signals - -- **Server not tested yet?** → Use `mcp-csharp-test` first -- **No server project yet?** → Use `mcp-csharp-create` -- **Publishing a non-MCP NuGet package?** → Use `nuget-trusted-publishing` instead - -## Inputs - -| Input | Required | Description | -|-------|----------|-------------| -| Transport type | Yes | `stdio` → NuGet path, `http` → Docker/Azure path | -| Target destination | Yes | NuGet.org, Docker registry, Azure Container Apps, Azure App Service, MCP Registry | -| Project path | Yes | Path to the `.csproj` file | -| Package ID / server name | Required for publishing | NuGet `PackageId` or MCP Registry name | - -## Workflow - -### Step 1: Choose the publishing path - -| Transport | Primary Destination | Users Run With | -|-----------|-------------------|----------------| -| **stdio** | NuGet.org | `dnx YourPackage@version` | -| **HTTP** | Docker → Azure | Container URL | - -Both paths can optionally publish to the MCP Registry for discoverability. - -### Step 2a: NuGet publishing (stdio servers) - -1. **Configure `.csproj`** with package properties: -```xml - - true - mymcpserver - YourUsername.MyMcpServer - 1.0.0 - Your Name - MCP server for interacting with MyService - MIT - mcp;modelcontextprotocol;ai;llm - README.md - - - - - -``` - -2. **Build and pack:** -```bash -dotnet build -c Release -dotnet pack -c Release -``` - -3. **Test locally before publishing:** -```bash -dotnet tool install --global --add-source bin/Release/ YourUsername.MyMcpServer -mymcpserver --help # verify it runs -dotnet tool uninstall --global YourUsername.MyMcpServer -``` - -4. **Push to NuGet.org:** -```bash -dotnet nuget push bin/Release/*.nupkg \ - --api-key YOUR_NUGET_API_KEY \ - --source https://api.nuget.org/v3/index.json -``` - -5. **Verify** — users configure in `mcp.json`: -```json -{ - "servers": { - "MyMcpServer": { - "type": "stdio", - "command": "dnx", - "args": ["YourUsername.MyMcpServer@1.0.0", "--yes"] - } - } -} -``` - -**For detailed NuGet packaging and trusted publishing setup**, see [references/nuget-packaging.md](references/nuget-packaging.md). - -### Step 2b: Docker containerization (HTTP servers) - -1. **Create Dockerfile:** -```dockerfile -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src -COPY *.csproj ./ -RUN dotnet restore -COPY . ./ -RUN dotnet publish -c Release -o /app - -FROM mcr.microsoft.com/dotnet/aspnet:10.0 -WORKDIR /app -COPY --from=build /app . - -# Non-root user for security -RUN adduser --disabled-password --gecos '' appuser -USER appuser - -ENV ASPNETCORE_URLS=http://+:8080 -EXPOSE 8080 -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:8080/health || exit 1 -ENTRYPOINT ["dotnet", "MyMcpServer.dll"] -``` - -2. **Build and test locally:** -```bash -docker build -t mymcpserver:latest . -docker run -d -p 3001:8080 -e API_KEY=test-key --name mymcpserver mymcpserver:latest -curl http://localhost:3001/health -``` - -3. **Push to container registry:** -```bash -# Docker Hub -docker tag mymcpserver:latest /:1.0.0 -docker push /:1.0.0 - -# Azure Container Registry -az acr login --name yourregistry -docker tag mymcpserver:latest .azurecr.io/:1.0.0 -docker push .azurecr.io/:1.0.0 -``` - -### Step 3: Deploy to Azure (HTTP servers) - -**Azure Container Apps** (recommended — serverless with auto-scaling): -```bash -az containerapp create \ - --name mymcpserver \ - --resource-group mygroup \ - --environment myenvironment \ - --image .azurecr.io/:1.0.0 \ - --target-port 8080 \ - --ingress external \ - --min-replicas 0 \ - --max-replicas 10 \ - --secrets api-key=my-actual-api-key \ - --env-vars API_KEY=secretref:api-key -``` - -**Azure App Service** (traditional web hosting): -```bash -az webapp create \ - --name mymcpserver \ - --resource-group mygroup \ - --plan myplan \ - --deployment-container-image-name .azurecr.io/:1.0.0 -``` - -**For detailed Azure deployment**, see [references/docker-azure.md](references/docker-azure.md). - -### Step 4: Publish to MCP Registry (optional) - -List your server in the official MCP Registry for discoverability. - -1. **Install `mcp-publisher`:** -```bash -# macOS/Linux -brew install mcp-publisher - -# Or download from https://github.com/modelcontextprotocol/registry/releases -``` - -2. **Create `.mcp/server.json`** (or run `mcp-publisher init` to generate interactively): -```json -{ - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", - "name": "io.github.username/servername", - "description": "Your server description", - "version": "1.0.0", - "packages": [{ - "registryType": "nuget", - "registryBaseUrl": "https://api.nuget.org", - "identifier": "YourUsername.MyMcpServer", - "version": "1.0.0", - "transport": { "type": "stdio" } - }], - "repository": { - "url": "https://github.com/username/repo", - "source": "github" - } -} -``` - -> **Version consistency (critical):** The root `version`, `packages[].version`, and `` in `.csproj` **must all match**. A mismatch causes registry validation failures or users downloading the wrong version. - -3. **Authenticate and publish:** -```bash -mcp-publisher login github # name must be io.github./... for GitHub auth -mcp-publisher publish -``` - -4. **Verify:** -```bash -curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github./" -``` - -**For Registry details** (namespace conventions, environment variables, CI/CD automation), see [references/mcp-registry.md](references/mcp-registry.md). - -### Step 5: Security checklist - -- [ ] No hardcoded secrets — use environment variables or Key Vault -- [ ] HTTPS enabled for HTTP transport in production -- [ ] Health check endpoint implemented -- [ ] Input validation on all tool parameters -- [ ] Rate limiting considered for HTTP servers - -## Validation - -- [ ] **NuGet:** Package installs and runs via `dnx PackageId@version` -- [ ] **Docker:** Container starts and health check passes -- [ ] **Azure:** Server is reachable and tools respond -- [ ] **MCP Registry:** Server appears at `registry.modelcontextprotocol.io` -- [ ] MCP client can connect and call tools on the deployed server - -## Common Pitfalls - -| Pitfall | Solution | -|---------|----------| -| NuGet package doesn't run as a tool | Missing `true` in `.csproj` | -| Version mismatch between `.csproj` and `server.json` | Keep ``, `server.json` root `version`, and `packages[].version` in sync | -| Docker container exits immediately | Check entrypoint DLL name matches project output. Run `docker logs mymcpserver` for errors | -| Azure Container App returns 502 | Target port mismatch. Ensure `--target-port` matches `ASPNETCORE_URLS` port in the container | -| MCP Registry rejects publish | Name must follow namespace convention: `io.github./` for GitHub auth | -| API keys leaked in Docker image | Use multi-stage builds. Never `COPY` `.env` files. Pass secrets via `--env-vars` at runtime | - -## Related Skills - -- `mcp-csharp-create` — Create a new MCP server project -- `mcp-csharp-test` — Automated tests and evaluations - -## Reference Files - -- [references/nuget-packaging.md](references/nuget-packaging.md) — Complete NuGet `.csproj` configuration, `server.json` for MCP, NuGet.org push, testing with `dnx`, version management. **Load when:** publishing a stdio server to NuGet. -- [references/docker-azure.md](references/docker-azure.md) — Production Dockerfile patterns, ACR setup, Azure Container Apps full configuration, App Service with Key Vault, secrets management. **Load when:** deploying an HTTP server to Docker or Azure. -- [references/mcp-registry.md](references/mcp-registry.md) — `mcp-publisher` CLI installation, `server.json` schema, namespace conventions (GitHub vs DNS auth), CI/CD automation. **Load when:** publishing to the official MCP Registry. - -## More Info - -- [NuGet publishing](https://learn.microsoft.com/nuget/nuget-org/publish-a-package) — NuGet.org publishing guide -- [Azure Container Apps](https://learn.microsoft.com/azure/container-apps/) — Serverless container hosting -- [MCP Registry](https://registry.modelcontextprotocol.io) — Official MCP server registry diff --git a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/docker-azure.md b/plugins/dotnet-ai/skills/mcp-csharp-publish/references/docker-azure.md deleted file mode 100644 index d7bc172f12..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/docker-azure.md +++ /dev/null @@ -1,187 +0,0 @@ -# Docker and Azure Deployment - -Production deployment patterns for HTTP MCP servers. - -## Production Dockerfile - -```dockerfile -# Build stage -FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build -WORKDIR /src - -# Restore first (layer caching) -COPY *.csproj ./ -RUN dotnet restore - -# Build and publish -COPY . ./ -RUN dotnet publish -c Release -o /app --no-restore - -# Runtime stage -FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime -WORKDIR /app -COPY --from=build /app . - -# Non-root user (security) -RUN adduser --disabled-password --gecos '' appuser -USER appuser - -# Configure -ENV ASPNETCORE_URLS=http://+:8080 -EXPOSE 8080 - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=10s --retries=3 \ - CMD curl -f http://localhost:8080/health || exit 1 - -ENTRYPOINT ["dotnet", "MyMcpServer.dll"] -``` - -### Key Practices - -- **Multi-stage build** — keeps the final image small (no SDK, only runtime) -- **Restore-first layer** — `COPY *.csproj` then `dotnet restore` before `COPY .` for better layer caching -- **Non-root user** — run as unprivileged user in production -- **Health check** — enables orchestrator health monitoring -- **Never copy secrets** — no `.env` files, no `appsettings.Production.json` with secrets - -## Azure Container Registry (ACR) - -```bash -# Create ACR (one-time) -az acr create --name yourregistry --resource-group mygroup --sku Basic - -# Login -az acr login --name yourregistry - -# Build and push -docker build -t .azurecr.io/:1.0.0 . -docker push .azurecr.io/:1.0.0 - -# Or build directly in ACR (no local Docker needed) -az acr build --registry yourregistry --image mymcpserver:1.0.0 . -``` - -## Azure Container Apps - -Serverless container hosting with auto-scaling. Best for MCP servers that need to scale to zero when idle. - -### Full Setup - -```bash -# Create environment (one-time) -az containerapp env create \ - --name myenvironment \ - --resource-group mygroup \ - --location eastus - -# Create the container app -az containerapp create \ - --name mymcpserver \ - --resource-group mygroup \ - --environment myenvironment \ - --image .azurecr.io/:1.0.0 \ - --registry-server .azurecr.io \ - --target-port 8080 \ - --ingress external \ - --min-replicas 0 \ - --max-replicas 10 \ - --cpu 0.5 \ - --memory 1.0Gi \ - --secrets api-key="your-secret-value" \ - --env-vars API_KEY=secretref:api-key - -# Get the URL -az containerapp show --name mymcpserver --resource-group mygroup \ - --query properties.configuration.ingress.fqdn -o tsv -``` - -### Update Deployment - -```bash -az containerapp update \ - --name mymcpserver \ - --resource-group mygroup \ - --image .azurecr.io/:1.1.0 -``` - -### Scaling Configuration - -```bash -# Scale based on HTTP requests -az containerapp update \ - --name mymcpserver \ - --resource-group mygroup \ - --scale-rule-name http-rule \ - --scale-rule-type http \ - --scale-rule-http-concurrency 50 -``` - -## Azure App Service - -Traditional web hosting with more control over infrastructure. - -```bash -# Create App Service plan -az appservice plan create \ - --name myplan \ - --resource-group mygroup \ - --sku B1 \ - --is-linux - -# Create web app with container -az webapp create \ - --name mymcpserver \ - --resource-group mygroup \ - --plan myplan \ - --deployment-container-image-name .azurecr.io/:1.0.0 - -# Configure secrets via Key Vault -az webapp config appsettings set \ - --name mymcpserver \ - --resource-group mygroup \ - --settings API_KEY=@Microsoft.KeyVault(VaultName=myvault;SecretName=api-key) -``` - -## Secrets Management - -### Azure Container Apps - -```bash -# Add a secret -az containerapp secret set --name mymcpserver --resource-group mygroup \ - --secrets api-key="value" - -# Reference in environment variables -az containerapp update --name mymcpserver --resource-group mygroup \ - --set-env-vars API_KEY=secretref:api-key -``` - -### Azure Key Vault (App Service) - -```bash -# Create Key Vault -az keyvault create --name myvault --resource-group mygroup - -# Add secret -az keyvault secret set --vault-name myvault --name api-key --value "your-secret" - -# Grant access to App Service identity -az webapp identity assign --name mymcpserver --resource-group mygroup -az keyvault set-policy --name myvault \ - --object-id \ - --secret-permissions get list -``` - -### In Application Code - -```csharp -// Read from environment (works with both approaches) -var apiKey = Environment.GetEnvironmentVariable("API_KEY") - ?? throw new InvalidOperationException("API_KEY environment variable required"); - -// Or use Azure Key Vault directly -builder.Configuration.AddAzureKeyVault( - new Uri($"https://{vaultName}.vault.azure.net/"), - new DefaultAzureCredential()); -``` diff --git a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/mcp-registry.md b/plugins/dotnet-ai/skills/mcp-csharp-publish/references/mcp-registry.md deleted file mode 100644 index ce50cb2284..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/mcp-registry.md +++ /dev/null @@ -1,140 +0,0 @@ -# MCP Registry - -Publish your MCP server to the official registry at [registry.modelcontextprotocol.io](https://registry.modelcontextprotocol.io) for discoverability. - -## When to Publish - -| Publish if… | Skip if… | -|-------------|----------| -| Server is for public/community use | Server is internal/private | -| You want discoverability in MCP clients | Still developing/testing | -| You want to appear in the official registry | No need for public discovery | - -## Prerequisites - -1. Package published to NuGet.org (for stdio) or container registry (for HTTP) -2. GitHub repository with the server source code -3. `mcp-publisher` CLI installed - -## Install mcp-publisher - -```bash -# macOS/Linux (Homebrew) -brew install mcp-publisher - -# Or download binary from releases -# https://github.com/modelcontextprotocol/registry/releases -``` - -## server.json Schema - -Place at `.mcp/server.json` in your repository root: - -```json -{ - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", - "name": "io.github./", - "description": "One-line description of your server", - "version": "1.0.0", - "packages": [ - { - "registryType": "nuget", - "registryBaseUrl": "https://api.nuget.org", - "identifier": "YourUsername.MyMcpServer", - "version": "1.0.0", - "transport": { - "type": "stdio" - }, - "packageArguments": [], - "environmentVariables": [ - { - "name": "API_KEY", - "value": "{api_key}", - "variables": { - "api_key": { - "description": "API key for MyService authentication", - "isRequired": true, - "isSecret": true - } - } - } - ] - } - ], - "repository": { - "url": "https://github.com/username/repo", - "source": "github" - } -} -``` - -## Namespace Conventions - -The `name` field must follow a namespace convention based on your authentication method: - -| Auth Method | Name Format | Example | -|-------------|-------------|---------| -| GitHub | `io.github.{github-username}/{server-name}` | `io.github.jsmith/weather-server` | -| DNS | `{reverse-domain}/{server-name}` | `com.mycompany/weather-server` | - -## Publish Workflow - -```bash -# 1. Initialize server.json (interactive, if not already created) -mcp-publisher init - -# 2. Authenticate with GitHub -mcp-publisher login github - -# 3. Publish to the registry -mcp-publisher publish - -# 4. Verify publication -curl "https://registry.modelcontextprotocol.io/v0.1/servers?search=io.github./" -``` - -## CI/CD Automation - -### GitHub Actions - -```yaml -name: Publish to MCP Registry -on: - release: - types: [published] - -jobs: - publish: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Install mcp-publisher - run: | - curl -sSL https://github.com/modelcontextprotocol/registry/releases/latest/download/mcp-publisher-linux-amd64 -o mcp-publisher - chmod +x mcp-publisher - - - name: Publish to registry - run: ./mcp-publisher publish - env: - MCP_REGISTRY_TOKEN: ${{ secrets.MCP_REGISTRY_TOKEN }} -``` - -## Version Consistency - -Always keep these three versions in sync: - -1. `` in `.csproj` -2. `version` at root of `server.json` -3. `packages[].version` in `server.json` - -A mismatch between any of these will cause registry validation to fail or users to get the wrong version. - -## Troubleshooting - -| Issue | Solution | -|-------|----------| -| "Invalid name format" | Use `io.github./` format | -| "Package not found" | Package must be published to NuGet.org first | -| "Version mismatch" | Sync `.csproj` version with both `server.json` version fields | -| "Authentication failed" | Re-run `mcp-publisher login github` | diff --git a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/nuget-packaging.md b/plugins/dotnet-ai/skills/mcp-csharp-publish/references/nuget-packaging.md deleted file mode 100644 index a56c59a55c..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-publish/references/nuget-packaging.md +++ /dev/null @@ -1,139 +0,0 @@ -# NuGet Packaging - -Detailed guide for publishing stdio MCP servers as NuGet tool packages. - -## Complete .csproj Configuration - -```xml - - - Exe - net10.0 - enable - enable - - - true - mymcpserver - - - YourUsername.MyMcpServer - 1.0.0 - Your Name - MCP server for interacting with MyService API - - - https://github.com/yourusername/mymcpserver - https://github.com/yourusername/mymcpserver - MIT - mcp;modelcontextprotocol;ai;llm - README.md - - - win-x64;linux-x64;osx-x64;osx-arm64 - - - - - - -``` - -### Key Properties - -| Property | Required | Purpose | -|----------|----------|---------| -| `PackAsTool` | Yes | Makes the package installable as a dotnet tool | -| `ToolCommandName` | Recommended | CLI command name. Defaults to assembly name if omitted | -| `PackageId` | Yes | Unique identifier on NuGet.org | -| `Version` | Yes | SemVer version (e.g., `1.0.0`, `2.0.0-preview.1`) | -| `PackageTags` | Recommended | Include `mcp` and `modelcontextprotocol` for discoverability | - -## Build, Pack, and Push - -```bash -# Build -dotnet build -c Release - -# Create NuGet package -dotnet pack -c Release -# Output: bin/Release/YourUsername.MyMcpServer.1.0.0.nupkg - -# Test package locally -dotnet tool install --global --add-source bin/Release/ YourUsername.MyMcpServer -mymcpserver --help -dotnet tool uninstall --global YourUsername.MyMcpServer - -# Push to NuGet.org -dotnet nuget push bin/Release/*.nupkg \ - --api-key YOUR_NUGET_API_KEY \ - --source https://api.nuget.org/v3/index.json -``` - -## User Configuration - -After publishing, users configure their MCP client to run the tool: - -```json -{ - "servers": { - "MyMcpServer": { - "type": "stdio", - "command": "dnx", - "args": ["YourUsername.MyMcpServer@1.0.0", "--yes"], - "env": { - "API_KEY": "${input:api_key}" - } - } - } -} -``` - -The `dnx` tool runner (a `dotnet execute`-style runner for NuGet packages) downloads and runs the package automatically. For more details, see the .NET package execution docs: https://learn.microsoft.com/dotnet/core/tools/dotnet-execute - -## server.json for MCP Registry Integration - -If you plan to publish to the MCP Registry, include `.mcp/server.json` in your repo: - -```json -{ - "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json", - "name": "io.github.yourusername/mymcpserver", - "description": "MCP server for interacting with MyService API", - "version": "1.0.0", - "packages": [ - { - "registryType": "nuget", - "registryBaseUrl": "https://api.nuget.org", - "identifier": "YourUsername.MyMcpServer", - "version": "1.0.0", - "transport": { - "type": "stdio" - }, - "environmentVariables": [ - { - "name": "API_KEY", - "value": "{api_key}", - "variables": { - "api_key": { - "description": "API key for MyService", - "isRequired": true, - "isSecret": true - } - } - } - ] - } - ], - "repository": { - "url": "https://github.com/yourusername/mymcpserver", - "source": "github" - } -} -``` - -**Version consistency:** Keep `` in `.csproj`, root `version` in `server.json`, and `packages[].version` in sync. A mismatch will cause MCP Registry validation to fail. - -## Trusted Publishing (OIDC) - -For CI/CD, use NuGet trusted publishing instead of long-lived API keys. See `nuget-trusted-publishing` skill for the full setup guide. diff --git a/plugins/dotnet-ai/skills/mcp-csharp-test/SKILL.md b/plugins/dotnet-ai/skills/mcp-csharp-test/SKILL.md deleted file mode 100644 index b6be6b1cc2..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-test/SKILL.md +++ /dev/null @@ -1,188 +0,0 @@ ---- -name: mcp-csharp-test -description: > - Test C# MCP servers at multiple levels: unit tests for individual tools and integration - tests using the MCP client SDK. - USE FOR: unit testing MCP tool methods, integration testing with in-memory MCP - client/server, end-to-end testing via MCP protocol, - testing HTTP MCP servers with WebApplicationFactory, mocking dependencies in tool tests, - creating evaluations for MCP servers, writing eval questions, measuring tool quality. - DO NOT USE FOR: testing MCP clients (this is server testing only), load or performance - testing, testing non-.NET MCP servers. -license: MIT ---- - -# C# MCP Server Testing - -Test MCP servers at two levels: unit tests for individual tool methods, and integration tests that exercise the full MCP protocol in-memory. - -## When to Use - -- Adding automated tests to an MCP server -- Testing individual tool methods with mocked dependencies -- Writing integration tests that validate tool listing and invocation via MCP protocol -- Setting up CI test pipelines for MCP servers - -## Stop Signals - -- **No server yet?** → Use `mcp-csharp-create` first - -## Inputs - -| Input | Required | Description | -|-------|----------|-------------| -| MCP server project path | Yes | Path to the server `.csproj` being tested | -| Test framework | Recommended | Default: xUnit. Also supports NUnit or MSTest | -| Transport type | Recommended | Determines integration test approach (stdio vs HTTP) | - -## Workflow - -### Step 1: Create the test project - -```bash -dotnet new xunit -n .Tests -cd .Tests -dotnet add reference ..//.csproj -dotnet add package ModelContextProtocol -dotnet add package Moq -dotnet add package FluentAssertions -``` - -### Step 2: Write unit tests for tool methods - -Test tool methods directly — fastest and most isolated: - -```csharp -public class MyToolTests -{ - [Fact] - public void Echo_ReturnsFormattedMessage() - { - var result = MyTools.Echo("Hello"); - result.Should().Be("Echo: Hello"); - } - - [Theory] - [InlineData("")] - [InlineData(" ")] - public void Echo_HandlesEdgeCases(string input) - { - var result = MyTools.Echo(input); - result.Should().StartWith("Echo:"); - } -} -``` - -For tools with DI dependencies, mock the dependency: -```csharp -public class ApiToolTests -{ - [Fact] - public async Task FetchData_ReturnsApiResponse() - { - var handler = new MockHttpMessageHandler("""{"id": 1}"""); - var httpClient = new HttpClient(handler); - - var result = await ApiTools.FetchData(httpClient, "resource-1"); - result.Should().Contain("id"); - } -} -``` - -### Step 3: Write integration tests with MCP client - -Test the full MCP protocol using a client-server connection: - -```csharp -using ModelContextProtocol.Client; - -public class ServerIntegrationTests : IAsyncLifetime -{ - private McpClient _client = null!; - - public async Task InitializeAsync() - { - var transport = new StdioClientTransport(new StdioClientTransportOptions - { - Name = "TestClient", - Command = "dotnet", - Arguments = ["run", "--project", "..//.csproj"] - }); - _client = await McpClient.CreateAsync(transport); - } - - public async Task DisposeAsync() => await _client.DisposeAsync(); - - [Fact] - public async Task Server_ListsExpectedTools() - { - var tools = await _client.ListToolsAsync(); - tools.Should().Contain(t => t.Name == "echo"); - } - - [Fact] - public async Task Tool_ReturnsExpectedResult() - { - var result = await _client.CallToolAsync("echo", - new Dictionary { ["message"] = "Test" }); - var text = result.Content.OfType().First().Text; - text.Should().Contain("Test"); - } -} -``` - -**For the SDK's `ClientServerTestBase` (in-memory testing) and HTTP testing with `WebApplicationFactory`**, see [references/test-patterns.md](references/test-patterns.md). - -### Step 4: Run tests - -```bash -# Run all tests -dotnet test - -# Run a specific test class -dotnet test --filter "FullyQualifiedName~MyToolTests" - -# Run with coverage -dotnet test --collect:"XPlat Code Coverage" -``` - -### Step 5: Write evaluations - -Evaluations measure how well an LLM uses your tools. Good evaluation questions should be: -- **Read-only and non-destructive** — never modify data as a side effect -- **Deterministic** — have a single verifiable correct answer -- **Multi-step** — require the LLM to call multiple tools or reason across results - -For the evaluation format, example questions, and detailed guidance, see [references/evaluations.md](references/evaluations.md). - -## Validation - -- [ ] Unit tests cover all tool methods, including edge cases -- [ ] Integration tests verify tool listing via `ListToolsAsync()` -- [ ] Integration tests verify tool invocation via `CallToolAsync()` -- [ ] All tests pass: `dotnet test` -- [ ] Tests run in CI without manual setup - -## Common Pitfalls - -| Pitfall | Solution | -|---------|----------| -| Integration test hangs on `CreateAsync` | Server fails to start. Verify `dotnet build` succeeds first. For stdio, ensure no stdout logging | -| `StdioClientTransport` not finding project | Use the correct relative path to `.csproj` from the test project directory | -| Tests pass locally but fail in CI | Run `dotnet build` before test execution. Use `--no-build` only after an explicit build step | -| Mocking `HttpClient` is awkward | Mock `HttpMessageHandler`, not `HttpClient` directly. See [references/test-patterns.md](references/test-patterns.md) | -| Full test suite runs are slow | Use `--filter` for development. Run the full suite only for CI verification | - -## Related Skills - -- `mcp-csharp-create` — Create a new MCP server project -- `mcp-csharp-publish` — NuGet, Docker, Azure deployment - -## Reference Files - -- [references/test-patterns.md](references/test-patterns.md) — Complete test code examples: `ClientServerTestBase` in-memory pattern, `WebApplicationFactory` for HTTP, `MockHttpMessageHandler` helper, test categorization, coverage reporting. **Load when:** writing integration tests or need detailed mock patterns. -- [references/evaluations.md](references/evaluations.md) — Evaluation format, question design principles, and example eval questions. **Load when:** user asks about evaluations, eval questions, or measuring tool quality. - -## More Info - -- [xUnit documentation](https://xunit.net/docs/getting-started/netcore/cmdline) — Getting started with xUnit for .NET diff --git a/plugins/dotnet-ai/skills/mcp-csharp-test/references/evaluations.md b/plugins/dotnet-ai/skills/mcp-csharp-test/references/evaluations.md deleted file mode 100644 index 00ebe17b6a..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-test/references/evaluations.md +++ /dev/null @@ -1,59 +0,0 @@ -# Evaluations - -Evaluations measure how well an LLM uses your MCP server's tools to answer questions. - -## Evaluation Format - -Use XML with `qa_pair` elements: - -```xml - - - What are the top 3 products in the Electronics category by price? - The top 3 products are: 1. Ultra HD TV ($1,299), 2. Gaming Laptop ($1,199), 3. Wireless Headphones ($349). - - - How many products cost less than $50 and are currently in stock? - There are 12 products under $50 that are currently in stock. - - -``` - -## Writing Good Evaluation Questions - -**Questions must be read-only, non-destructive, and deterministic.** The LLM should be able to verify its answer against a known correct result without modifying any data. - -### Principles - -| Principle | Good Example | Bad Example | -|-----------|-------------|-------------| -| Read-only | "List all products in category X" | "Add a new product to category X" | -| Deterministic | "What is the price of product #123?" | "What's a good product to buy?" | -| Multi-tool | "Which category has the highest average price?" (requires listing categories + querying each) | "What is product #1?" (single lookup) | -| Verifiable | "How many products have rating > 4.5?" | "Describe the best products" | - -### Design Tips - -- **Require reasoning across multiple tool calls** — e.g., "Compare the average price of Electronics vs. Books" needs two category queries plus computation. -- **Include edge cases** — empty results, boundary values, special characters in search terms. -- **Vary complexity** — mix simple single-tool questions with multi-step reasoning chains. -- **Answers must be precise** — include exact values, counts, or names that can be verified programmatically. - -### Example: Product Catalog Evaluations - -```xml - - - What is the most expensive product across all categories? - The most expensive product is the Ultra HD TV in Electronics at $1,299.99. - - - Which categories contain products that are out of stock? List the category names and the count of out-of-stock items in each. - Electronics has 3 out-of-stock items, Books has 1 out-of-stock item. - - - Find all products with "wireless" in the name and sort them by price ascending. - 1. Wireless Mouse ($24.99), 2. Wireless Keyboard ($49.99), 3. Wireless Headphones ($349.00). - - -``` diff --git a/plugins/dotnet-ai/skills/mcp-csharp-test/references/test-patterns.md b/plugins/dotnet-ai/skills/mcp-csharp-test/references/test-patterns.md deleted file mode 100644 index 89e860bc1d..0000000000 --- a/plugins/dotnet-ai/skills/mcp-csharp-test/references/test-patterns.md +++ /dev/null @@ -1,158 +0,0 @@ -# Test Patterns - -Complete code patterns for testing C# MCP servers at every level. - -## MockHttpMessageHandler Helper - -Reusable mock for tools that use `HttpClient`: - -```csharp -public class MockHttpMessageHandler : HttpMessageHandler -{ - private readonly string _response; - private readonly HttpStatusCode _statusCode; - - public MockHttpMessageHandler( - string response = "", - HttpStatusCode statusCode = HttpStatusCode.OK) - { - _response = response; - _statusCode = statusCode; - } - - protected override Task SendAsync( - HttpRequestMessage request, - CancellationToken cancellationToken) => - Task.FromResult(new HttpResponseMessage - { - StatusCode = _statusCode, - Content = new StringContent(_response) - }); -} -``` - -## ClientServerTestBase (In-Memory Testing) - -The SDK provides `ClientServerTestBase` for zero-network integration tests using `System.IO.Pipelines`: - -```csharp -using ModelContextProtocol.Tests; // from SDK test utilities - -public class MyToolTests : ClientServerTestBase -{ - public MyToolTests(ITestOutputHelper output) : base(output) { } - - protected override void ConfigureServices( - ServiceCollection services, IMcpServerBuilder builder) - { - builder.WithTools(); - // Register any DI services your tools need - services.AddSingleton(); - } - - [Fact] - public async Task MyTool_ReturnsExpected() - { - await using var client = await CreateMcpClientForServer(); - var result = await client.CallToolAsync("my_tool", - new() { ["input"] = "test" }, - cancellationToken: TestContext.Current.CancellationToken); - Assert.NotNull(result); - } -} -``` - -**Key advantages:** -- In-memory transport — no process spawning, no network -- Full DI support — inject fakes/mocks for external dependencies -- Runs in milliseconds - -## HTTP Testing with WebApplicationFactory - -Test HTTP MCP servers using ASP.NET Core's test infrastructure. - -**Important:** `WebApplicationFactory` requires access to the `Program` class. Either: -- Add `` to the server's `.csproj`, or -- Make the `Program` class public: `public partial class Program { }` - -```csharp -using Microsoft.AspNetCore.Mvc.Testing; - -public class HttpServerTests : IClassFixture> -{ - private readonly WebApplicationFactory _factory; - - public HttpServerTests(WebApplicationFactory factory) - { - _factory = factory; - } - - [Fact] - public async Task McpEndpoint_AcceptsInitialize() - { - var client = _factory.CreateClient(); - var request = new - { - jsonrpc = "2.0", - id = 1, - method = "initialize", - @params = new - { - protocolVersion = "2024-11-05", - capabilities = new { }, - clientInfo = new { name = "test", version = "1.0" } - } - }; - - var response = await client.PostAsJsonAsync("/mcp", request); - response.EnsureSuccessStatusCode(); - } - - [Fact] - public async Task McpEndpoint_InvokesTool() - { - var client = _factory.CreateClient(); - - // First initialize the session - var init = new - { - jsonrpc = "2.0", - id = 1, - method = "initialize", - @params = new - { - protocolVersion = "2024-11-05", - capabilities = new { }, - clientInfo = new { name = "test", version = "1.0" } - } - }; - await client.PostAsJsonAsync("/mcp", init); - - // Then call a tool - var toolCall = new - { - jsonrpc = "2.0", - id = 2, - method = "tools/call", - @params = new - { - name = "echo", - arguments = new { message = "hello" } - } - }; - - var response = await client.PostAsJsonAsync("/mcp", toolCall); - response.EnsureSuccessStatusCode(); - var body = await response.Content.ReadAsStringAsync(); - Assert.Contains("hello", body); - } - - [Fact] - public async Task HealthEndpoint_ReturnsOk() - { - var client = _factory.CreateClient(); - var response = await client.GetAsync("/health"); - response.EnsureSuccessStatusCode(); - } -} -``` diff --git a/tests/dotnet-ai/mcp-csharp-create/eval.yaml b/tests/dotnet-ai/mcp-csharp-create/eval.yaml deleted file mode 100644 index ae9f437f25..0000000000 --- a/tests/dotnet-ai/mcp-csharp-create/eval.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: mcp-csharp-create -description: Evaluates the dotnet-ai/mcp-csharp-create skill -type: capability -config: - timeout: 6m -stimuli: - - name: Implement MCP tools with proper attributes and DI - prompt: | - I have a new C# MCP server project. I need to implement a tool class that - wraps a REST API using HttpClient. The tools should follow MCP SDK conventions - with proper attributes for LLM discovery. Show me the tool class, Program.cs - with stdio transport, and explain how DI works for MCP tools. - graders: - - type: output-matches - config: - pattern: \[McpServerTool[\],\(] - - type: output-matches - config: - pattern: (AddHttpClient|HttpClient) - - type: prompt - rubric: - - Shows a tool class with [McpServerTool] attributes - - Injects HttpClient via DI (constructor injection or method parameter injection) - - Includes [Description] attributes on both the method and all parameters - - Configures Program.cs with AddMcpServer, WithStdioServerTransport, and logging to stderr - - name: Create an HTTP MCP server with tools and resources - prompt: | - I need to create a C# MCP server that uses HTTP transport for deployment - as a web service. It should expose both tools and resources (for example, - a resource that returns configuration data). Show me how to set up the - HTTP transport with MapMcp and implement a resource. - graders: - - type: output-contains - config: - substring: MapMcp - - type: output-matches - config: - pattern: (WithHttpTransport|ModelContextProtocol\.AspNetCore) - - type: output-matches - config: - pattern: (McpServerResource|McpServerResourceType) - - type: prompt - rubric: - - Configures HTTP transport with WithHttpTransport() and references ModelContextProtocol.AspNetCore - - Includes MapMcp() in the endpoint configuration - - Shows a resource class with [McpServerResourceType] and [McpServerResource] attributes including UriTemplate - - Shows how to register tools and resources with WithTools or similar DI registration - - name: Create an MCP server with tools, prompts, and proper logging - prompt: | - I'm building a C# MCP server using stdio transport. I need to add a tool - that calls an external API using HttpClient (injected via DI), and a prompt - template for summarization. Make sure logging doesn't interfere with the - stdio JSON-RPC protocol. - graders: - - type: output-matches - config: - pattern: \[McpServerTool[\]T,\(] - - type: output-matches - config: - pattern: (McpServerPrompt|McpServerPromptType) - - type: output-matches - config: - pattern: (LogToStandardErrorThreshold|stderr) - - type: prompt - rubric: - - Shows a tool class with [McpServerTool] and HttpClient injected via DI - - Shows a prompt class with [McpServerPromptType] and [McpServerPrompt] returning ChatMessage - - Configures logging to stderr (LogToStandardErrorThreshold) to avoid corrupting stdio transport - - Adds [Description] attributes on tools, parameters, and prompts for LLM discoverability diff --git a/tests/dotnet-ai/mcp-csharp-publish/eval.yaml b/tests/dotnet-ai/mcp-csharp-publish/eval.yaml deleted file mode 100644 index 9dc29cc5a7..0000000000 --- a/tests/dotnet-ai/mcp-csharp-publish/eval.yaml +++ /dev/null @@ -1,69 +0,0 @@ -name: mcp-csharp-publish -description: Evaluates the dotnet-ai/mcp-csharp-publish skill -type: capability -config: - timeout: 2m -stimuli: - - name: Publish an MCP server as a NuGet tool package - prompt: | - I have a C# MCP server using stdio transport at src/MyMcpServer/. - I want to publish it to NuGet.org so users can install it as a - dotnet tool. What do I need to configure? - graders: - - type: output-contains - config: - substring: PackAsTool - - type: output-contains - config: - substring: dotnet pack - - type: output-matches - config: - pattern: (dotnet nuget push|nuget\.org) - - type: prompt - rubric: - - Configures PackAsTool and ToolCommandName in the csproj - - Shows how to build, pack, and push to NuGet.org - - Includes local testing with dotnet tool install --global - - Mentions the dnx tool runner for MCP client configuration - - name: Deploy an HTTP MCP server to Azure Container Apps - prompt: | - I have an HTTP-based C# MCP server that I want to deploy to Azure. - It uses environment variables for API keys. What's the best way - to containerize and deploy it? - graders: - - type: output-matches - config: - pattern: (Dockerfile|docker) - - type: output-matches - config: - pattern: (Container Apps|containerapp) - - type: output-matches - config: - pattern: (secret|Secret|KEY) - - type: prompt - rubric: - - Provides a multi-stage Dockerfile with a non-root user - - Shows Azure Container Apps deployment commands - - Configures secrets using Container Apps secrets, not environment variables in plain text - - Includes a health check endpoint - - name: Publish to the MCP Registry - prompt: | - I already published my C# MCP server to NuGet.org. Now I want to - register it in the official MCP Registry so it's discoverable. - How do I do this? - graders: - - type: output-contains - config: - substring: server.json - - type: output-contains - config: - substring: mcp-publisher - - type: output-matches - config: - pattern: io\.github\. - - type: prompt - rubric: - - Shows the server.json schema with the correct format - - Uses the mcp-publisher CLI for publishing - - Explains the naming convention (io.github./) - - Emphasizes version consistency between csproj and server.json diff --git a/tests/dotnet-ai/mcp-csharp-test/eval.yaml b/tests/dotnet-ai/mcp-csharp-test/eval.yaml deleted file mode 100644 index d39b94f4ef..0000000000 --- a/tests/dotnet-ai/mcp-csharp-test/eval.yaml +++ /dev/null @@ -1,63 +0,0 @@ -name: mcp-csharp-test -description: Evaluates the dotnet-ai/mcp-csharp-test skill -type: capability -config: - timeout: 2m -stimuli: - - name: Write unit and integration tests for an MCP server - prompt: | - I have a C# MCP server with tools that call an external REST API. - I want to write tests that verify the tools work correctly without - calling the real API. How do I structure the test project? - graders: - - type: output-contains - config: - substring: xunit - - type: output-matches - config: - pattern: (Mock|Fake|mock|fake|HttpMessageHandler) - - type: output-matches - config: - pattern: (CallToolAsync|McpClient) - - type: prompt - rubric: - - Creates a test project with xUnit and references the server project - - Shows how to mock HttpClient or external dependencies - - Demonstrates an integration test using McpClient to call tools in-memory - - Separates unit tests from integration tests using traits or folders - - name: Test an HTTP MCP server with WebApplicationFactory - prompt: | - I have an HTTP-based C# MCP server built with ASP.NET Core. - How do I write integration tests that test the full MCP protocol - over HTTP without spinning up a real server? - graders: - - type: output-contains - config: - substring: WebApplicationFactory - - type: output-matches - config: - pattern: (initialize|MCP|jsonrpc) - - type: prompt - rubric: - - Uses WebApplicationFactory for in-process HTTP testing - - Shows how to send an MCP initialize request to verify the server responds - - Mentions InternalsVisibleTo or making Program public for test access - - Tests tool invocation through the HTTP endpoint - - name: Create evaluations for an MCP server - prompt: | - I have a C# MCP server that provides tools for querying a product catalog. - I want to create evaluations to measure how well an LLM uses the tools. - What format should I use and what makes a good evaluation question? - graders: - - type: output-matches - config: - pattern: (evaluation|qa_pair|question.*answer) - - type: output-matches - config: - pattern: (read.only|non.destructive|deterministic|verifiable) - - type: prompt - rubric: - - Shows the XML evaluation format with qa_pair elements - - Explains that questions should require multiple tool calls and reasoning - - Emphasizes that questions must be read-only with deterministic answers - - Provides example evaluation questions appropriate for a product catalog