mirror of
https://github.com/github/awesome-copilot.git
synced 2026-08-01 23:12:29 +00:00
Update dotnet-mcp-builder skill to ModelContextProtocol 2.x (#2487)
* Update dotnet-mcp-builder skill to ModelContextProtocol 2.x Align the skill with the C# SDK 2.0.0 release and the MCP 2026-07-28 spec: stable line is now 2.x, HttpServerTransportOptions.Stateless defaults to true, roots/sampling/MCP-channel logging are [Obsolete] (MCP9005) with the multi-round-trip input_required pattern as the replacement, discovery-first negotiation (server/discover) supersedes the initialize handshake, Mcp-Method/Mcp-Name routable headers, raw structuredContent for non-object results, required Tool.inputSchema, and the new ModelContextProtocol.Extensions.Tasks and ModelContextProtocol.Extensions.Apps packages (typed MCP Apps support replacing the hand-rolled _meta/ui:// pattern on 1.x). * Address Copilot review: Apps extension accuracy, header scope, capability ownership - packages.md: the Apps package replaces the manual _meta wiring, not the ui:// resource; note the experimental MCPEXP003 diagnostic; label the 1.x -> 2.0 list as highlights and add the OAuth/SSE runtime changes with a pointer to the full release notes. - transport-http.md: Mcp-Method is on every POST, Mcp-Name only on named invocations (tools/call, prompts/get, resources/read) - do not require it globally at gateways. - mcp-apps.md: current MIME type is text/html;profile=mcp-app (skybridge is a legacy draft value); document [McpAppUi] + WithMcpApps(). - server-features.md: roots/sampling are client capabilities, only logging sits on ServerCapabilities. * Correct stateful HTTP guidance: 2026-07-28 has no HTTP sessions Per the official SDK v2 elicitation docs, a server with Stateless=false refuses the 2026-07-28 revision so dual-path clients fall back to an initialize-capable revision; ElicitAsync cannot be used on 2026-07-28 Streamable HTTP at all. Reframe stateful HTTP as down-level compatibility mode and document the multi-round-trip pattern (InputRequiredException / InputRequest.ForElicitation, retry with InputResponses -> ElicitResult) as the current-protocol way to ask mid-tool, across SKILL.md, transport-http.md, and elicitation.md.
This commit is contained in:
@@ -161,7 +161,7 @@ See [CONTRIBUTING.md](../CONTRIBUTING.md#adding-skills) for guidelines on how to
|
||||
| [documentation-writer](../skills/documentation-writer/SKILL.md)<br />`gh skills install github/awesome-copilot documentation-writer` | Diátaxis Documentation Expert. An expert technical writer specializing in creating high-quality software documentation, guided by the principles and structure of the Diátaxis technical documentation authoring framework. | None |
|
||||
| [dotnet-best-practices](../skills/dotnet-best-practices/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-best-practices` | Ensure .NET/C# code meets best practices for the solution/project. | None |
|
||||
| [dotnet-design-pattern-review](../skills/dotnet-design-pattern-review/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-design-pattern-review` | Review the C#/.NET code for design pattern implementation and suggest improvements. | None |
|
||||
| [dotnet-mcp-builder](../skills/dotnet-mcp-builder/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-mcp-builder` | Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 1.x NuGet packages. Especially helps with cases the model often gets wrong without guidance — stale preview versions (it tends to pick 0.3 or 0.4 preview), MCP Apps (interactive UI rendered in the host), elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging concrete MapMcp / STDIO / Streamable-HTTP errors. Also covers the routine work — STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, sampling, roots, completions, logging — and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages. | `references/client.md`<br />`references/elicitation.md`<br />`references/mcp-apps.md`<br />`references/packages.md`<br />`references/prompt-primitive.md`<br />`references/resource-primitive.md`<br />`references/roots.md`<br />`references/sampling.md`<br />`references/server-features.md`<br />`references/testing.md`<br />`references/tool-primitive.md`<br />`references/transport-http.md`<br />`references/transport-stdio.md` |
|
||||
| [dotnet-mcp-builder](../skills/dotnet-mcp-builder/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-mcp-builder` | Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 2.x NuGet packages. Helps with cases the model gets wrong without guidance — stale versions (0.x preview or 1.x-era defaults), the v2 stateless-by-default HTTP flip, the 2026-07-28 spec deprecations (roots/sampling/logging), MCP Apps and Tasks extension packages, elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging MapMcp / STDIO / Streamable-HTTP errors. Also covers STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, completions, and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages. | `references/client.md`<br />`references/elicitation.md`<br />`references/mcp-apps.md`<br />`references/packages.md`<br />`references/prompt-primitive.md`<br />`references/resource-primitive.md`<br />`references/roots.md`<br />`references/sampling.md`<br />`references/server-features.md`<br />`references/testing.md`<br />`references/tool-primitive.md`<br />`references/transport-http.md`<br />`references/transport-stdio.md` |
|
||||
| [dotnet-timezone](../skills/dotnet-timezone/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-timezone` | .NET timezone handling guidance for C# applications. Use when working with TimeZoneInfo, DateTimeOffset, NodaTime, UTC conversion, daylight saving time, scheduling across timezones, cross-platform Windows/IANA timezone IDs, or when a .NET user needs the timezone for a city, address, region, or country and copy-paste-ready C# code. | `references/code-patterns.md`<br />`references/timezone-index.md` |
|
||||
| [dotnet-upgrade](../skills/dotnet-upgrade/SKILL.md)<br />`gh skills install github/awesome-copilot dotnet-upgrade` | Ready-to-use prompts for comprehensive .NET framework upgrade analysis and execution | None |
|
||||
| [doublecheck](../skills/doublecheck/SKILL.md)<br />`gh skills install github/awesome-copilot doublecheck` | Three-layer verification pipeline for AI output. Extracts verifiable claims, finds supporting or contradicting sources via web search, runs adversarial review for hallucination patterns, and produces a structured verification report with source links for human review. | `assets/verification-report-template.md` |
|
||||
|
||||
@@ -1,17 +1,19 @@
|
||||
---
|
||||
name: dotnet-mcp-builder
|
||||
description: 'Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 1.x NuGet packages. Especially helps with cases the model often gets wrong without guidance — stale preview versions (it tends to pick 0.3 or 0.4 preview), MCP Apps (interactive UI rendered in the host), elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging concrete MapMcp / STDIO / Streamable-HTTP errors. Also covers the routine work — STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, sampling, roots, completions, logging — and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages.'
|
||||
description: 'Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 2.x NuGet packages. Helps with cases the model gets wrong without guidance — stale versions (0.x preview or 1.x-era defaults), the v2 stateless-by-default HTTP flip, the 2026-07-28 spec deprecations (roots/sampling/logging), MCP Apps and Tasks extension packages, elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging MapMcp / STDIO / Streamable-HTTP errors. Also covers STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, completions, and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages.'
|
||||
---
|
||||
|
||||
# Building MCP servers in .NET
|
||||
|
||||
This skill helps you write production-quality MCP servers and basic clients in C#/.NET against the **official** [`ModelContextProtocol`](https://www.nuget.org/profiles/ModelContextProtocol) NuGet packages, maintained by Microsoft and the MCP project. It targets the **stable 1.x** line and the current spec (2025-11-25).
|
||||
This skill helps you write production-quality MCP servers and basic clients in C#/.NET against the **official** [`ModelContextProtocol`](https://www.nuget.org/profiles/ModelContextProtocol) NuGet packages, maintained by Microsoft and the MCP project. It targets the **stable 2.x** line and the current spec (2026-07-28).
|
||||
|
||||
## When this skill earns its keep
|
||||
|
||||
The .NET MCP SDK had years of preview packages (`0.x-preview`) before reaching `1.0`. Without help, the model tends to:
|
||||
The .NET MCP SDK had years of preview packages (`0.x-preview`) before reaching `1.0`, and v2 flipped several defaults. Without help, the model tends to:
|
||||
- Pin a stale preview version that won't compile against current samples.
|
||||
- Miss recent spec features (elicitation URL mode, MCP Apps, structured content blocks).
|
||||
- Apply 1.x-era defaults that v2 reversed (HTTP was stateful by default; in 2.x `Stateless` defaults to `true`).
|
||||
- Recommend capabilities the 2026-07-28 spec deprecates (roots, sampling, MCP-channel logging — now `[Obsolete]`, warning `MCP9005`).
|
||||
- Miss recent spec features (multi-round-trip `input_required`, discovery-first negotiation, MCP Apps/Tasks extension packages, elicitation URL mode, structured content blocks).
|
||||
- Get HTTP transport details wrong (stateful/stateless, proxy buffering, OAuth wiring).
|
||||
- Forget the STDIO stdout/stderr trap.
|
||||
|
||||
@@ -32,7 +34,7 @@ builder.Services
|
||||
|
||||
Primitives are plain C# methods on classes marked with attributes (`[McpServerToolType]` + `[McpServerTool]`, `[McpServerPromptType]` + `[McpServerPrompt]`, `[McpServerResourceType]` + `[McpServerResource]`). Parameters bind from JSON-RPC; the SDK builds the JSON Schema from the signature plus `[Description]` attributes.
|
||||
|
||||
Server-to-client features (sampling, elicitation, roots, log/progress notifications) are methods on the injected `IMcpServer`.
|
||||
Server-to-client features (elicitation, progress notifications, and the now-deprecated sampling/roots/log notifications) are methods on the injected `IMcpServer`.
|
||||
|
||||
## Decision tree → which references to load
|
||||
|
||||
@@ -46,8 +48,8 @@ Always load `references/packages.md` if you're creating a new project or unsure
|
||||
| Add/modify a prompt | `references/prompt-primitive.md` |
|
||||
| Add/modify a resource | `references/resource-primitive.md` |
|
||||
| Ask the user a question mid-tool | `references/elicitation.md` |
|
||||
| Call the client's LLM from a tool | `references/sampling.md` |
|
||||
| Read the user's project roots | `references/roots.md` |
|
||||
| Call the client's LLM from a tool (deprecated in 2026-07-28) | `references/sampling.md` |
|
||||
| Read the user's project roots (deprecated in 2026-07-28) | `references/roots.md` |
|
||||
| Return an interactive UI | `references/mcp-apps.md` |
|
||||
| Argument completions, log/progress notifications, filters, server instructions | `references/server-features.md` |
|
||||
| Write a .NET program that **consumes** an MCP server | `references/client.md` |
|
||||
@@ -57,13 +59,14 @@ For multi-primitive tasks, load several at once. For trivial edits in an existin
|
||||
|
||||
## Cardinal rules (apply always; these prevent the highest-frequency breakages)
|
||||
|
||||
1. **Pin the current stable package, not a preview.** Use `ModelContextProtocol` / `ModelContextProtocol.AspNetCore` / `ModelContextProtocol.Core` at the latest **1.x**. If you find yourself writing `0.3-preview` or `0.4-preview`, stop and check NuGet — preview APIs have breaking differences.
|
||||
1. **Pin the current stable package, not a preview.** Use `ModelContextProtocol` / `ModelContextProtocol.AspNetCore` / `ModelContextProtocol.Core` at the latest **2.x**. If you find yourself writing `0.3-preview` or `0.4-preview`, stop and check NuGet — preview APIs have breaking differences. 1.x still works but predates the 2026-07-28 spec.
|
||||
2. **STDIO servers must not write to stdout.** Stdout is the JSON-RPC channel. Configure `LogToStandardErrorThreshold = LogLevel.Trace` before anything else and never `Console.WriteLine` from a tool.
|
||||
3. **HTTP defaults to stateful.** For horizontally-scaled deployments without server-initiated traffic, set `options.Stateless = true`. Server-to-client features (sampling, elicitation, roots, unsolicited notifications) require stateful HTTP **or** STDIO — `Stateless = true` will break them at runtime.
|
||||
3. **HTTP defaults to stateless in 2.x** (v1.x defaulted to stateful — the single most impactful v2 breaking change). The 2026-07-28 revision has no HTTP sessions at all: setting `Stateless = false` makes the server refuse that revision and serve clients via the legacy `initialize` fallback. For "ask the user something mid-tool" on current-protocol HTTP, use the multi-round-trip `InputRequiredException` pattern; reserve stateful HTTP (or STDIO) for the legacy `ElicitAsync`/sampling/roots paths and pushed notifications.
|
||||
4. **SSE-only is deprecated.** Use Streamable HTTP. Only enable legacy SSE (`EnableLegacySse = true`) for an old client you must support, and call it out.
|
||||
5. **Always `[Description]` tools and parameters.** This is what the LLM sees when picking and shaping calls. Vague descriptions are the #1 reason tools don't get used.
|
||||
6. **Show the registration line every time you add a primitive.** A new `[McpServerPromptType]` class without `.WithPrompts<...>()` (or `.WithPromptsFromAssembly()`) is invisible.
|
||||
7. **Don't invent APIs.** If you're unsure a method exists, say so and check the [API reference](https://csharp.sdk.modelcontextprotocol.io/api/ModelContextProtocol.html) — wrong method names cause silent failures.
|
||||
5. **Don't design new servers around deprecated capabilities.** The 2026-07-28 spec deprecates roots, sampling, and MCP-channel logging; the SDK marks them `[Obsolete]` (warning `MCP9005`). They still work against down-level clients, but for new designs prefer the multi-round-trip `input_required` pattern and `ILogger` logging. Suppress `MCP9005` only as a documented transition measure.
|
||||
6. **Always `[Description]` tools and parameters.** This is what the LLM sees when picking and shaping calls. Vague descriptions are the #1 reason tools don't get used.
|
||||
7. **Show the registration line every time you add a primitive.** A new `[McpServerPromptType]` class without `.WithPrompts<...>()` (or `.WithPromptsFromAssembly()`) is invisible.
|
||||
8. **Don't invent APIs.** If you're unsure a method exists, say so and check the [API reference](https://csharp.sdk.modelcontextprotocol.io/api/ModelContextProtocol.html) — wrong method names cause silent failures. This applies doubly to the new v2 extension packages (`ModelContextProtocol.Extensions.Tasks`, `ModelContextProtocol.Extensions.Apps`) — check their docs before writing code against them.
|
||||
|
||||
## Working style
|
||||
|
||||
@@ -78,6 +81,7 @@ Walk this checklist before guessing:
|
||||
2. **HTTP 404:** path mismatch — `app.MapMcp()` is root, `app.MapMcp("/mcp")` puts it under `/mcp`.
|
||||
3. **Tool not appearing:** missing `[McpServerToolType]` on the class, or no `.WithToolsFromAssembly()` / `.WithTools<T>()` registered.
|
||||
4. **Args not bound:** parameter names must match the JSON-RPC `arguments` keys; complex types bind via `System.Text.Json`.
|
||||
5. **Sampling/elicitation/roots failing:** transport is stateless HTTP, or the client doesn't advertise the capability.
|
||||
5. **Sampling/elicitation/roots failing:** these legacy server-to-client calls can't run on current-protocol HTTP — migrate to the multi-round-trip `InputRequiredException` pattern, or (legacy paths only) set `Stateless = false`, knowing that pins HTTP clients to a down-level `initialize` revision. Also check the client actually advertises the capability.
|
||||
6. **`MCP9005` build warnings after upgrading to 2.x:** the code uses deprecated roots/sampling/logging APIs. Plan the migration; suppress only temporarily.
|
||||
|
||||
Still stuck? Point the user at the [`EverythingServer`](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/EverythingServer) sample — it exercises every feature.
|
||||
|
||||
@@ -110,9 +110,13 @@ client.RegisterNotificationHandler(
|
||||
});
|
||||
```
|
||||
|
||||
## Version negotiation (2026-07-28)
|
||||
|
||||
Since 2.x, `McpClient.CreateAsync` probes the server with `server/discover` first and falls back to the legacy `initialize` handshake for down-level servers automatically — no configuration needed on your side.
|
||||
|
||||
## 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:
|
||||
If your server uses these features, your client must handle them. Note that sampling and roots are deprecated in the 2026-07-28 spec (`MCP9005` warnings on 2.x) — you still need the handlers to interoperate with servers that use them. Configure handlers when creating the client:
|
||||
|
||||
```csharp
|
||||
await using var client = await McpClient.CreateAsync(transport, new McpClientOptions
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
Elicitation lets a tool **ask the user for input mid-execution**, via the client. The LLM doesn't see the question; the client surfaces it directly to the user. This turns one-shot tool calls into interactive flows — collecting confirmation, missing parameters, credentials (URL mode), etc.
|
||||
|
||||
> **Spec version:** 2025-11-25. URL mode is the newer addition (originally 2025-06-18 had only form mode).
|
||||
> **Spec version:** current through 2026-07-28 (elicitation is *not* among the v2 deprecations — sampling/roots/logging are). URL mode is the newer addition (originally 2025-06-18 had only form mode).
|
||||
|
||||
> **2026-07-28 changes the rules on HTTP.** The current revision removes HTTP sessions and the server→client `elicitation/create` request, so **`ElicitAsync` cannot be used on 2026-07-28 Streamable HTTP at all** — a server with `Stateless = false` refuses that revision and serves clients via the legacy `initialize` fallback. The current-protocol way to ask mid-tool is the **multi-round-trip pattern**: throw `InputRequiredException` from the tool (building requests with `InputRequest.ForElicitation(...)`), and on the client's retried call read `context.Params.InputResponses` back as an `ElicitResult`. It works under both protocol revisions and both session modes, including fully stateless HTTP. The `ElicitAsync` guidance below applies to **STDIO and down-level stateful HTTP** — see the [SDK elicitation docs](https://csharp.sdk.modelcontextprotocol.io/v2/concepts/elicitation/elicitation.html) for the full MRTR pattern.
|
||||
|
||||
## Two modes
|
||||
|
||||
@@ -11,13 +13,13 @@ Elicitation lets a tool **ask the user for input mid-execution**, via the client
|
||||
| **Form (in-band)** | Server sends a JSON Schema; client renders a form; user submits values back through the same MCP channel. | Confirmations, missing parameters, structured choices. |
|
||||
| **URL (out-of-band)** | Server sends a URL; client opens it in a browser; user completes the flow there; server checks state separately. | OAuth, payments, anything the MCP channel must not see. |
|
||||
|
||||
## Prerequisite: stateful transport
|
||||
## Prerequisite for `ElicitAsync`: stateful transport (down-level revisions)
|
||||
|
||||
Elicitation requires the server to send a request *to* the client and wait for a response. That only works on:
|
||||
`ElicitAsync` requires the server to send a request *to* the client and wait for a response. That only works on:
|
||||
- STDIO (always).
|
||||
- Stateful HTTP (`options.Stateless = false`).
|
||||
- Stateful HTTP (`options.Stateless = false`) — which, on 2.x, means down-level clients on an initialize-capable revision (see banner above).
|
||||
|
||||
In stateless HTTP, `ElicitAsync` will throw — there's no transport channel back.
|
||||
In stateless HTTP — including all 2026-07-28 Streamable HTTP — `ElicitAsync` throws (`InvalidOperationException`): there's no transport channel back. Use the multi-round-trip `InputRequiredException` pattern instead.
|
||||
|
||||
## Form mode — full example
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
[MCP Apps](https://modelcontextprotocol.io/extensions/apps/overview) is the official extension that lets a tool return an **interactive UI** rendered in a sandboxed iframe inside the host (Claude, Claude Desktop, VS Code Copilot, Goose, Postman, MCPJam). Typical use cases: charts, dashboards, multi-step forms, 3D viewers, real-time monitors, PDF/video viewers.
|
||||
|
||||
> **Important:** as of early 2026, the C# SDK does **not** ship a typed convenience layer for MCP Apps (tracked in [csharp-sdk#1431](https://github.com/modelcontextprotocol/csharp-sdk/issues/1431)). You implement the spec by hand: serve a `ui://` resource and emit the right `_meta` on the tool. It's not hard — just untyped. This page shows you the pattern.
|
||||
> **Important:** SDK 2.x ships a dedicated extension package, **`ModelContextProtocol.Extensions.Apps`**, with typed MCP Apps support: register with `.WithMcpApps()` and annotate tools with `[McpAppUi(ResourceUri = "ui://...")]`. It replaces the hand-rolled `_meta` wiring, **not** the `ui://` resource — you still register and serve the UI resource. The APIs are marked experimental (suppress diagnostic `MCPEXP003`); check the [package page](https://www.nuget.org/packages/ModelContextProtocol.Extensions.Apps) and [SDK API reference](https://csharp.sdk.modelcontextprotocol.io/api/ModelContextProtocol.html) for the current surface rather than guessing beyond those names. The manual pattern below is what you need on **1.x**, which has no typed layer (was tracked in [csharp-sdk#1431](https://github.com/modelcontextprotocol/csharp-sdk/issues/1431)): serve a `ui://` resource and emit the right `_meta` on the tool.
|
||||
|
||||
## How it works (short version)
|
||||
|
||||
@@ -31,7 +31,7 @@ public static class ChartUiResource
|
||||
[McpServerResource(
|
||||
UriTemplate = "ui://charts/interactive",
|
||||
Name = "Interactive chart",
|
||||
MimeType = "text/html+skybridge")] // see "MIME type" note below
|
||||
MimeType = "text/html;profile=mcp-app")] // see "MIME type" note below
|
||||
[Description("UI bundle for the interactive chart MCP App.")]
|
||||
public static TextResourceContents GetUi()
|
||||
{
|
||||
@@ -41,7 +41,7 @@ public static class ChartUiResource
|
||||
return new TextResourceContents
|
||||
{
|
||||
Uri = "ui://charts/interactive",
|
||||
MimeType = "text/html+skybridge",
|
||||
MimeType = "text/html;profile=mcp-app",
|
||||
Text = html
|
||||
};
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public static class ChartUiResource
|
||||
}
|
||||
```
|
||||
|
||||
**MIME type note:** the spec uses `text/html+skybridge` for app HTML so hosts can distinguish UI bundles from regular `text/html` previews. Use that, even though plain `text/html` may work today on lenient hosts.
|
||||
**MIME type note:** the current Apps spec (2026-01-26) uses `text/html;profile=mcp-app` for app HTML so hosts can distinguish UI bundles from regular `text/html` previews. Earlier drafts used `text/html+skybridge` — treat that as legacy; some older hosts may still expect it.
|
||||
|
||||
## Step 2: Emit `_meta` on the tool
|
||||
|
||||
@@ -210,11 +210,11 @@ For pure-UI iteration, [MCP Inspector](https://github.com/modelcontextprotocol/i
|
||||
|
||||
## Pitfalls
|
||||
|
||||
- **Wrong MIME type.** Use `text/html+skybridge`. Plain `text/html` may still work but isn't future-proof.
|
||||
- **Wrong MIME type.** Use `text/html;profile=mcp-app` (current spec; `text/html+skybridge` is a legacy draft value). Plain `text/html` may still work on lenient hosts but isn't future-proof.
|
||||
- **CSP too tight or too loose.** If your UI loads from a CDN, declare it in `Meta["ui"]["csp"]` on the `Tool` definition (this serialises to `_meta.ui.csp` on the wire). Otherwise the iframe sandbox blocks it.
|
||||
- **Forgetting `Tool.Meta` on the tool.** Without the `Meta` property containing the `ui.resourceUri` entry, the host treats your tool as a regular text-returning tool. The UI never appears.
|
||||
- **Trying to use browser APIs outside the sandbox.** No cookies, no localStorage from the parent. Use `app.updateModelContext` and tool calls for state.
|
||||
|
||||
## Future-proofing
|
||||
## Migrating from the manual pattern
|
||||
|
||||
When the C# SDK ships its typed MCP Apps helpers (issue [#1431](https://github.com/modelcontextprotocol/csharp-sdk/issues/1431)), you'll likely be able to replace the manual `Configure` block with an attribute or fluent builder. The serving of `ui://` resources won't change. Keep your UI HTML as embedded resources so the migration is mechanical.
|
||||
On 2.x, `ModelContextProtocol.Extensions.Apps` replaces the manual `Configure` block: `.WithMcpApps()` plus `[McpAppUi(ResourceUri = "ui://...")]` on the tool, with the extension handling the Apps capability negotiation. You still serve the `ui://` resource (with the current `text/html;profile=mcp-app` MIME type) and keep your HTML bundle — keep the UI HTML as embedded resources so the migration is mechanical. The APIs are experimental (`MCPEXP003`); consult the package docs for anything beyond this surface rather than inventing it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# NuGet packages and target frameworks
|
||||
|
||||
## The three official packages
|
||||
## The official packages
|
||||
|
||||
All packages live under the [`ModelContextProtocol` NuGet profile](https://www.nuget.org/profiles/ModelContextProtocol). The official C# SDK repo is [`modelcontextprotocol/csharp-sdk`](https://github.com/modelcontextprotocol/csharp-sdk), maintained jointly by the MCP project and Microsoft.
|
||||
|
||||
@@ -9,6 +9,8 @@ All packages live under the [`ModelContextProtocol` NuGet profile](https://www.n
|
||||
| **`ModelContextProtocol`** | Default for STDIO servers and most projects | `Core` + `Microsoft.Extensions.Hosting` integration, attribute discovery (`AddMcpServer`, `WithToolsFromAssembly`, etc.) |
|
||||
| **`ModelContextProtocol.AspNetCore`** | HTTP (Streamable) servers hosted in ASP.NET Core | The above + `WithHttpTransport` and `MapMcp` |
|
||||
| **`ModelContextProtocol.Core`** | Pure clients, custom hosts, low-level scenarios where you don't want the `Microsoft.Extensions.*` dependencies | Just the protocol + transports + low-level `McpServer.Create` / `McpClient.CreateAsync` |
|
||||
| **`ModelContextProtocol.Extensions.Tasks`** (2.x) | Long-running task support (the MCP Tasks extension) | Production replacement for the experimental 1.4.x Tasks APIs; register via `WithTasks(...)` |
|
||||
| **`ModelContextProtocol.Extensions.Apps`** (2.x) | MCP Apps — interactive UI rendered in the host | Typed `[McpAppUi]` attribute + `.WithMcpApps()` registration replacing the hand-rolled `_meta` wiring of 1.x; you still serve the UI as a `ui://` resource, and the APIs are experimental (`MCPEXP003`) — see [`mcp-apps.md`](./mcp-apps.md) |
|
||||
|
||||
**Rule of thumb:**
|
||||
- New STDIO server → `ModelContextProtocol` + `Microsoft.Extensions.Hosting`.
|
||||
@@ -17,7 +19,9 @@ All packages live under the [`ModelContextProtocol` NuGet profile](https://www.n
|
||||
|
||||
## Versions
|
||||
|
||||
As of 2026, the stable line is **1.x** (`1.2.0` is current at time of writing). The `0.x` line was preview and has breaking differences — if you find docs or blog posts referencing `0.4`/`0.6`, treat them as out of date.
|
||||
As of mid-2026, the stable line is **2.x** (`2.0.0` is current at time of writing), aligned with the MCP 2026-07-28 spec. The `0.x` line was preview and has breaking differences — if you find docs or blog posts referencing `0.4`/`0.6`, treat them as out of date. The `1.x` line still compiles and interoperates, but predates the v2 changes (stateless-by-default HTTP, discovery-first negotiation, roots/sampling/logging deprecations, the Tasks/Apps extension packages) — prefer 2.x for new projects.
|
||||
|
||||
**Upgrading 1.x → 2.0 (highlights, not exhaustive):** stable v1.x APIs keep working; the deprecated capabilities (roots, sampling, logging) are now `[Obsolete]` with `MCP9005` warnings, experimental APIs moved (the 1.4.x Tasks surface → `ModelContextProtocol.Extensions.Tasks`), and several behaviors flipped (`HttpServerTransportOptions.Stateless` now defaults to `true`; non-object tool results emit raw `structuredContent` values; `Tool.inputSchema` is required on deserialization). OAuth also changed at runtime — `AuthorizationRedirectDelegate` → `ClientOAuthOptions.AuthorizationCallbackHandler`, RFC 9207 issuer validation, mandatory PKCE S256 in metadata, `application_type` in dynamic registration — and SSE transport failures now propagate the underlying `HttpRequestException`/`TimeoutException`. Before upgrading, read the full [v2.0.0 release notes](https://github.com/modelcontextprotocol/csharp-sdk/releases/tag/v2.0.0).
|
||||
|
||||
To check the latest:
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Roots
|
||||
|
||||
> **Deprecated in the 2026-07-28 spec.** SDK 2.x marks the roots APIs `[Obsolete]` (build warning `MCP9005`). They stay wire-compatible with down-level clients during the transition, but for new designs prefer having the client pass in-scope paths as tool arguments, or ask via elicitation / the multi-round-trip `input_required` pattern. Keep this page for maintaining existing 1.x-era servers.
|
||||
|
||||
Roots are filesystem (or URI) locations the **client** advertises to the server, scoping what the server is allowed to look at. Think "open workspace folders" in an IDE — the user has implicitly approved the server reading from these places. The server pulls the list when it needs it.
|
||||
|
||||
## When you'd use roots
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Sampling
|
||||
|
||||
> **Deprecated in the 2026-07-28 spec.** SDK 2.x marks the sampling APIs `[Obsolete]` (build warning `MCP9005`). They stay wire-compatible with down-level clients during the transition, but **don't design new servers around sampling**: for "the tool needs user/LLM input mid-execution", prefer the multi-round-trip `input_required` pattern; for "the server needs an LLM", call a model directly server-side. Keep this page for maintaining existing 1.x-era servers; suppress `MCP9005` only as a documented transition measure.
|
||||
|
||||
Sampling lets a tool **call the LLM through the client** instead of bringing its own model. The server says "summarise this for me" and the client routes the request to whatever model the user has configured (Claude, GPT, local model, anything). Costs and rate limits live with the client, not the server.
|
||||
|
||||
## When to use sampling
|
||||
|
||||
@@ -60,7 +60,7 @@ public class WeatherTools
|
||||
|
||||
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):
|
||||
> **MCP-channel logging is deprecated in the 2026-07-28 spec** — SDK 2.x marks it `[Obsolete]` (`MCP9005`), along with the `logging` capability and the client's `setLevel` method (replaced by a `_meta` log level on requests). `ILogger`-based logging above is unaffected and remains the right default. Only use the MCP-channel notification below when supporting down-level clients that expect it:
|
||||
|
||||
```csharp
|
||||
await server.SendNotificationAsync(
|
||||
@@ -192,9 +192,8 @@ builder.Services.AddMcpServer(options =>
|
||||
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.
|
||||
By default, the SDK advertises everything you've registered — usually the right behaviour. Note that `Logging` is `[Obsolete]` in 2.x (`MCP9005`) — don't advertise it on new servers. Roots and sampling are *client* capabilities: a server never advertises them, it only checks `server.ClientCapabilities?.Roots` / `.Sampling` before using the (equally deprecated) client-side features.
|
||||
|
||||
@@ -90,6 +90,8 @@ This style lets you assert on the *exposed* behaviour (what a real client sees),
|
||||
|
||||
## Testing tools that use sampling/elicitation/roots
|
||||
|
||||
(Sampling and roots are deprecated on 2.x — expect `MCP9005` warnings in test projects that exercise them; suppress in the test csproj if you're deliberately covering legacy paths.)
|
||||
|
||||
Inject the MCP server, but supply mock client capabilities. With the in-memory pattern above, register handlers on the client:
|
||||
|
||||
```csharp
|
||||
|
||||
@@ -102,6 +102,8 @@ public static Forecast[] GetForecast(string city) =>
|
||||
|
||||
The SDK emits the array as both a JSON text block (for older clients) and `structuredContent` (for newer ones), and infers an output schema from `Forecast`.
|
||||
|
||||
> **v2 behavior change:** non-object results are emitted as raw `structuredContent` values — returning `72` produces `"structuredContent": 72`, where 1.x wrapped it as `{ "result": 72 }`. Clients reading structured output should follow the advertised output schema. If you hand-write `Tool` definitions (rather than using attributes), note that `inputSchema` is **required** on deserialization in 2.x — an empty `{}` is sufficient.
|
||||
|
||||
### Returning images / audio
|
||||
|
||||
```csharp
|
||||
|
||||
@@ -30,13 +30,10 @@ var builder = WebApplication.CreateBuilder(args);
|
||||
|
||||
builder.Services
|
||||
.AddMcpServer()
|
||||
.WithHttpTransport(options =>
|
||||
{
|
||||
// Stateless = true: each request is independent, no Mcp-Session-Id tracking.
|
||||
// Required for horizontal scaling without sticky sessions.
|
||||
// Disables server-to-client features (sampling, elicitation, roots, unsolicited notifications).
|
||||
options.Stateless = true;
|
||||
})
|
||||
// Since 2.x, Stateless defaults to true: each request is independent,
|
||||
// no Mcp-Session-Id tracking, no SSE session endpoints — ready for
|
||||
// horizontal scaling without sticky sessions.
|
||||
.WithHttpTransport()
|
||||
.WithToolsFromAssembly();
|
||||
|
||||
var app = builder.Build();
|
||||
@@ -56,12 +53,14 @@ public static class EchoTool
|
||||
|
||||
## Stateless vs. stateful — the most important decision
|
||||
|
||||
> **v2 breaking change:** `HttpServerTransportOptions.Stateless` now defaults to **`true`** (it defaulted to `false` on 1.x). A server upgraded to 2.x without touching options stops creating sessions and stops exposing SSE endpoints. Set `Stateless = false` explicitly to restore the legacy behavior.
|
||||
|
||||
| Mode | `options.Stateless` | Behaviour | Use when |
|
||||
|---|---|---|---|
|
||||
| **Stateless** | `true` | No `Mcp-Session-Id`. Each POST is independent. | Horizontal scaling, simple tool servers, no server-initiated traffic. |
|
||||
| **Stateful** | `false` (default) | Server assigns and tracks `Mcp-Session-Id`. Long-lived session. | You need elicitation, sampling, roots, log notifications, or anything that pushes from server to client. Requires session affinity at the load balancer. |
|
||||
| **Stateless** | `true` (default since 2.x) | No `Mcp-Session-Id`. Each POST is independent. Serves the current (2026-07-28) revision. | Horizontal scaling, simple tool servers, current-protocol clients. |
|
||||
| **Stateful** | `false` | Server assigns and tracks `Mcp-Session-Id`. Long-lived session. **Down-level compatibility mode:** the server refuses the 2026-07-28 revision so dual-path clients fall back to an initialize-capable revision (2025-11-25 or earlier). | Legacy `ElicitAsync`/sampling/roots paths, pushed log notifications, clients that haven't adopted 2026-07-28. Requires session affinity at the load balancer. |
|
||||
|
||||
**Rule:** if the user wants any of `ElicitAsync`, `SampleAsync`, `RequestRootsAsync`, or to push log/notification messages, **do not** set `Stateless = true`. The calls will fail at runtime with no transport to deliver them on.
|
||||
**Rule:** on the current (2026-07-28) protocol there are no HTTP sessions — "ask the user something mid-tool" uses the multi-round-trip pattern (throw `InputRequiredException`, handle the retried call; see [`elicitation.md`](./elicitation.md)), which works in both session modes and both revisions. Set `Stateless = false` only for the legacy paths — `ElicitAsync`, the deprecated `SampleAsync`/`RequestRootsAsync`, or pushed log/notification messages — and be aware it pins HTTP clients to a down-level, initialize-capable revision. On the stateless default those legacy calls fail at runtime with no channel to deliver them on.
|
||||
|
||||
## Endpoint shape
|
||||
|
||||
@@ -78,6 +77,11 @@ app.MapMcp("/mcp/v1");
|
||||
|
||||
Match this on the client side (`Endpoint = new Uri("https://host/mcp/v1")`).
|
||||
|
||||
## Version negotiation and routing (2026-07-28)
|
||||
|
||||
- **Discovery-first:** v2 clients probe the `server/discover` method to learn capabilities instead of the legacy `initialize` handshake. The SDK answers both and falls back automatically for down-level peers (2025-11-25 and earlier) — you don't write any code for this, but don't be surprised to see `server/discover` in traffic captures.
|
||||
- **Routable headers:** every Streamable HTTP POST carries an `Mcp-Method` header (e.g. `tools/call`), and named invocations (`tools/call`, `prompts/get`, `resources/read`) additionally carry `Mcp-Name` (e.g. the tool name), so gateways and rate limiters can route/throttle per tool without parsing JSON bodies. Don't require `Mcp-Name` globally at the gateway — discovery and list requests legitimately omit it.
|
||||
|
||||
## Per-session configuration (HttpContext access)
|
||||
|
||||
When you need to vary server behaviour per HTTP request (auth, tenant, headers), use the `ConfigureSessionOptions` callback:
|
||||
|
||||
@@ -7,7 +7,7 @@ STDIO is the right choice when the server runs as a child process of the client
|
||||
- Local-first server (file-system access, dev tools, CLI integrations).
|
||||
- Distributing as a single executable or a `dnx`-runnable NuGet package.
|
||||
- You want the simplest possible deployment story (no network, no auth).
|
||||
- You need server-to-client features (sampling, elicitation, roots) — STDIO always supports them, no `Stateless` flag to worry about.
|
||||
- You need server-to-client features (elicitation, notifications, the deprecated sampling/roots) — STDIO always supports them, no `Stateless` flag to worry about.
|
||||
|
||||
If the user wants a remote/multi-tenant server, use [HTTP Streamable](./transport-http.md) instead.
|
||||
|
||||
@@ -65,7 +65,7 @@ The single most common bug in STDIO servers is something writing to stdout that
|
||||
|
||||
## Server identity
|
||||
|
||||
The SDK sends `serverInfo` (name + version) in the `initialize` response. By default it derives them from your assembly. To override:
|
||||
The SDK sends `serverInfo` (name + version) during negotiation (the 2026-07-28 `server/discover` exchange, or the legacy `initialize` response for down-level clients — the SDK handles both automatically). By default it derives them from your assembly. To override:
|
||||
|
||||
```csharp
|
||||
builder.Services
|
||||
|
||||
Reference in New Issue
Block a user