diff --git a/docs/concepts/cancellation/cancellation.md b/docs/concepts/cancellation/cancellation.md new file mode 100644 index 000000000..50753d259 --- /dev/null +++ b/docs/concepts/cancellation/cancellation.md @@ -0,0 +1,62 @@ +--- +title: Cancellation +author: jeffhandley +description: How to cancel in-flight MCP requests using cancellation tokens and notifications. +uid: cancellation +--- + +## Cancellation + +MCP supports [cancellation] of in-flight requests. Either side can cancel a previously issued request, and `CancellationToken` parameters on MCP methods are wired to send and receive `notifications/cancelled` notifications over the protocol. + +[cancellation]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/cancellation +[task cancellation]: https://learn.microsoft.com/dotnet/standard/parallel-programming/task-cancellation + +### How cancellation maps to MCP notifications + +When a `CancellationToken` passed to a client method (such as ) is cancelled, a `notifications/cancelled` notification is sent to the server with the request ID. On the server side, the `CancellationToken` provided to the tool method is then triggered, allowing the handler to stop work gracefully. This same mechanism works in reverse for server-to-client requests. + +### Server-side cancellation handling + +Server tool methods receive a `CancellationToken` that is triggered when the client sends a cancellation notification. Pass this token through to any async operations so they stop promptly: + +```csharp +[McpServerTool, Description("A long-running computation")] +public static async Task LongComputation( + [Description("Number of iterations")] int iterations, + CancellationToken cancellationToken) +{ + for (int i = 0; i < iterations; i++) + { + await Task.Delay(1000, cancellationToken); + } + + return $"Completed {iterations} iterations."; +} +``` + +When the client sends a cancellation notification, the `OperationCanceledException` propagates back to the client as a cancellation response. + +### Cancellation notification details + +The cancellation notification includes: + +- **RequestId**: The ID of the request to cancel, allowing the receiver to correlate the cancellation with the correct in-flight request. +- **Reason**: An optional human-readable reason for the cancellation. + +Cancellation notifications can be observed by registering a handler. For broader interception of notifications and other messages, delegates can be added to the collection in . + +```csharp +mcpClient.RegisterNotificationHandler( + NotificationMethods.CancelledNotification, + (notification, ct) => + { + var cancelled = notification.Params?.Deserialize( + McpJsonUtilities.DefaultOptions); + if (cancelled is not null) + { + Console.WriteLine($"Request {cancelled.RequestId} cancelled: {cancelled.Reason}"); + } + return default; + }); +``` diff --git a/docs/concepts/capabilities/capabilities.md b/docs/concepts/capabilities/capabilities.md new file mode 100644 index 000000000..b466736a4 --- /dev/null +++ b/docs/concepts/capabilities/capabilities.md @@ -0,0 +1,122 @@ +--- +title: Capabilities +author: jeffhandley +description: How capability and protocol version negotiation works in MCP. +uid: capabilities +--- + +## Capabilities + +MCP uses a [capability negotiation] mechanism during connection setup. Clients and servers exchange their supported capabilities so each side can adapt its behavior accordingly. Both sides should check the other's capabilities before using optional features. + +[capability negotiation]: https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle#initialization + +### Client capabilities + + declares what features the client supports: + +| Capability | Type | Description | +|-----------|------|-------------| +| `Roots` | | Client can provide filesystem root URIs | +| `Sampling` | | Client can handle LLM sampling requests | +| `Elicitation` | | Client can present forms or URLs to the user | +| `Experimental` | `IDictionary` | Experimental capabilities | + +Configure client capabilities when creating an MCP client: + +```csharp +var options = new McpClientOptions +{ + Capabilities = new ClientCapabilities + { + Roots = new RootsCapability { ListChanged = true }, + Sampling = new SamplingCapability(), + Elicitation = new ElicitationCapability + { + Form = new FormElicitationCapability(), + Url = new UrlElicitationCapability() + } + } +}; + +await using var client = await McpClient.CreateAsync(transport, options); +``` + +Handlers for each capability (roots, sampling, elicitation) are covered in their respective documentation pages. + +### Server capabilities + + declares what features the server supports: + +| Capability | Type | Description | +|-----------|------|-------------| +| `Tools` | | Server exposes callable tools | +| `Prompts` | | Server exposes prompt templates | +| `Resources` | | Server exposes readable resources | +| `Logging` | | Server can send log messages | +| `Completions` | | Server supports argument completions | +| `Experimental` | `IDictionary` | Experimental capabilities | + +Server capabilities are automatically inferred from the configured features. For example, registering tools with `.WithTools()` automatically declares the tools capability. + +### Checking capabilities + +Before using an optional feature, check whether the other side declared the corresponding capability. + +#### Checking server capabilities from the client + +```csharp +await using var client = await McpClient.CreateAsync(transport); + +// Check if the server supports tools +if (client.ServerCapabilities.Tools is not null) +{ + var tools = await client.ListToolsAsync(); +} + +// Check if the server supports resources with subscriptions +if (client.ServerCapabilities.Resources is { Subscribe: true }) +{ + await client.SubscribeToResourceAsync("config://app/settings"); +} + +// Check if the server supports prompts with list-changed notifications +if (client.ServerCapabilities.Prompts is { ListChanged: true }) +{ + mcpClient.RegisterNotificationHandler( + NotificationMethods.PromptListChangedNotification, + async (notification, ct) => + { + var prompts = await mcpClient.ListPromptsAsync(cancellationToken: ct); + }); +} + +// Check if the server supports logging +if (client.ServerCapabilities.Logging is not null) +{ + await client.SetLoggingLevelAsync(LoggingLevel.Info); +} + +// Check if the server supports completions +if (client.ServerCapabilities.Completions is not null) +{ + var completions = await client.CompleteAsync( + new PromptReference { Name = "my_prompt" }, + argumentName: "language", + argumentValue: "py"); +} +``` + +### Protocol version negotiation + +During connection setup, the client and server negotiate a mutually supported MCP protocol version. After initialization, the negotiated version is available on both sides: + +```csharp +// On the client +string? version = client.NegotiatedProtocolVersion; + +// On the server (within a tool or handler) +string? version = server.NegotiatedProtocolVersion; +``` + +Version negotiation is handled automatically. If the client and server cannot agree on a compatible protocol version, the initialization fails with an error. diff --git a/docs/concepts/completions/completions.md b/docs/concepts/completions/completions.md new file mode 100644 index 000000000..0bc0b21aa --- /dev/null +++ b/docs/concepts/completions/completions.md @@ -0,0 +1,122 @@ +--- +title: Completions +author: jeffhandley +description: How to implement and use argument auto-completion for prompts and resources. +uid: completions +--- + +## Completions + +MCP [completions] allow servers to provide argument auto-completion suggestions for prompt and resource template parameters. This helps clients offer a better user experience by suggesting valid values as the user types. + +[completions]: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/completion + +### Overview + +Completions work with two types of references: + +- **Prompt argument completions**: Suggest values for prompt parameters (e.g., language names, style options) +- **Resource template argument completions**: Suggest values for URI template parameters (e.g., file paths, resource IDs) + +The server returns a object containing a list of suggested values, an optional total count, and a flag indicating if more values are available. + +### Implementing completions on the server + +Register a completion handler when building the server. The handler receives a reference (prompt or resource template) and the current argument value: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithPrompts() + .WithResources() + .WithCompleteHandler(async (ctx, ct) => + { + if (ctx.Params is not { } @params) + throw new McpProtocolException("Params are required.", McpErrorCode.InvalidParams); + + var argument = @params.Argument; + + // Handle prompt argument completions + if (@params.Ref is PromptReference promptRef) + { + var suggestions = argument.Name switch + { + "language" => new[] { "csharp", "python", "javascript", "typescript", "go", "rust" }, + "style" => new[] { "casual", "formal", "technical", "friendly" }, + _ => Array.Empty() + }; + + // Filter suggestions based on what the user has typed so far + var filtered = suggestions.Where(s => s.StartsWith(argument.Value, StringComparison.OrdinalIgnoreCase)).ToList(); + + return new CompleteResult + { + Completion = new Completion + { + Values = filtered, + Total = filtered.Count, + HasMore = false + } + }; + } + + // Handle resource template argument completions + if (@params.Ref is ResourceTemplateReference resourceRef) + { + var availableIds = new[] { "1", "2", "3", "4", "5" }; + var filtered = availableIds.Where(id => id.StartsWith(argument.Value)).ToList(); + + return new CompleteResult + { + Completion = new Completion + { + Values = filtered, + Total = filtered.Count, + HasMore = false + } + }; + } + + return new CompleteResult(); + }); +``` + +### Requesting completions on the client + +Clients request completions using . Provide a reference to the prompt or resource template, the argument name, and the current partial value: + +#### Prompt argument completions + +```csharp +// Get completions for a prompt argument +CompleteResult result = await client.CompleteAsync( + new PromptReference { Name = "code_review" }, + argumentName: "language", + argumentValue: "type"); + +// result.Completion.Values might contain: ["typescript"] +foreach (var suggestion in result.Completion.Values) +{ + Console.WriteLine($" {suggestion}"); +} + +if (result.Completion.HasMore == true) +{ + Console.WriteLine($" ... and more ({result.Completion.Total} total)"); +} +``` + +#### Resource template argument completions + +```csharp +// Get completions for a resource template argument +CompleteResult result = await client.CompleteAsync( + new ResourceTemplateReference { Uri = "file:///{path}" }, + argumentName: "path", + argumentValue: "src/"); + +foreach (var suggestion in result.Completion.Values) +{ + Console.WriteLine($" {suggestion}"); +} +``` diff --git a/docs/concepts/elicitation/elicitation.md b/docs/concepts/elicitation/elicitation.md index b4d73a85d..3f7759843 100644 --- a/docs/concepts/elicitation/elicitation.md +++ b/docs/concepts/elicitation/elicitation.md @@ -34,6 +34,80 @@ For enum types, the SDK supports several schema formats: - **TitledMultiSelectEnumSchema**: A multi-select enum with display titles for each option. - **LegacyTitledEnumSchema** (deprecated): The legacy enum schema using `enumNames` for backward compatibility. +#### Default values + +Each schema type supports a `Default` property that specifies a pre-populated value for the form field. +Clients should use defaults to pre-fill form fields, making it easier for users to accept common values or see expected input formats. + +```csharp +var result = await server.ElicitAsync(new ElicitRequestParams +{ + Message = "Configure your preferences", + RequestedSchema = new ElicitRequestParams.RequestSchema + { + Properties = new Dictionary + { + ["name"] = new ElicitRequestParams.StringSchema + { + Description = "Your display name", + Default = "User" + }, + ["maxResults"] = new ElicitRequestParams.NumberSchema + { + Description = "Maximum number of results", + Default = 25 + }, + ["enableNotifications"] = new ElicitRequestParams.BooleanSchema + { + Description = "Enable push notifications", + Default = true + }, + ["theme"] = new ElicitRequestParams.UntitledSingleSelectEnumSchema + { + Description = "UI theme", + Enum = ["light", "dark", "system"], + Default = "system" + } + } + } +}, cancellationToken); +``` + +#### Enum schema formats + +Enum schemas allow the server to present a set of choices to the user. + +- : Simple single-select where enum values serve as both the value and display text. +- : Single-select with separate display titles for each option using JSON Schema `oneOf` with `const` and `title`. +- : Multi-select allowing multiple values. +- : Multi-select with display titles. + +```csharp +// Titled single-select: display titles differ from values +["priority"] = new ElicitRequestParams.TitledSingleSelectEnumSchema +{ + Description = "Task priority", + OneOf = + [ + new() { Const = "p0", Title = "Critical (P0)" }, + new() { Const = "p1", Title = "High (P1)" }, + new() { Const = "p2", Title = "Normal (P2)" }, + ], + Default = "p2" +}, + +// Multi-select: user can select multiple values +["tags"] = new ElicitRequestParams.UntitledMultiSelectEnumSchema +{ + Description = "Tags to apply", + Items = new() + { + Enum = ["bug", "feature", "docs", "test"] + }, + Default = ["bug"] +} +``` + The server can request a single input or multiple inputs at once. To help distinguish multiple inputs, each input has a unique name. diff --git a/docs/concepts/index.md b/docs/concepts/index.md index 044d4fee7..94d1224c3 100644 --- a/docs/concepts/index.md +++ b/docs/concepts/index.md @@ -4,10 +4,33 @@ Welcome to the conceptual documentation for the Model Context Protocol SDK. Here ## Contents +### Base Protocol + | Title | Description | | - | - | +| [Capabilities](capabilities/capabilities.md) | Learn how client and server capabilities are negotiated during initialization, including protocol version negotiation. | +| [Transports](transports/transports.md) | Learn how to configure stdio, Streamable HTTP, and SSE transports for client-server communication. | +| [Ping](ping/ping.md) | Learn how to verify connection health using the ping mechanism. | | [Progress tracking](progress/progress.md) | Learn how to track progress for long-running operations through notification messages. | +| [Cancellation](cancellation/cancellation.md) | Learn how to cancel in-flight MCP requests using cancellation tokens and notifications. | +| [Pagination](pagination/pagination.md) | Learn how to use cursor-based pagination when listing tools, prompts, and resources. | + +### Client Features + +| Title | Description | +| - | - | +| [Sampling](sampling/sampling.md) | Learn how servers request LLM completions from the client using the sampling feature. | +| [Roots](roots/roots.md) | Learn how clients provide filesystem roots to servers for context-aware operations. | | [Elicitation](elicitation/elicitation.md) | Learn how to request additional information from users during interactions. | + +### Server Features + +| Title | Description | +| - | - | +| [Tools](tools/tools.md) | Learn how to implement and consume tools that return text, images, audio, and embedded resources. | +| [Resources](resources/resources.md) | Learn how to expose and consume data through MCP resources, including templates and subscriptions. | +| [Prompts](prompts/prompts.md) | Learn how to implement and consume reusable prompt templates with rich content types. | +| [Completions](completions/completions.md) | Learn how to implement argument auto-completion for prompts and resource templates. | | [Logging](logging/logging.md) | Learn how to implement logging in MCP servers and how clients can consume log messages. | | [HTTP Context](httpcontext/httpcontext.md) | Learn how to access the underlying `HttpContext` for a request. | | [MCP Server Handler Filters](filters.md) | Learn how to add filters to the handler pipeline. Filters let you wrap the original handler with additional functionality. | diff --git a/docs/concepts/pagination/pagination.md b/docs/concepts/pagination/pagination.md new file mode 100644 index 000000000..bb48ffa0b --- /dev/null +++ b/docs/concepts/pagination/pagination.md @@ -0,0 +1,101 @@ +--- +title: Pagination +author: jeffhandley +description: How to use cursor-based pagination when listing tools, prompts, and resources. +uid: pagination +--- + +## Pagination + +MCP uses [cursor-based pagination] for all list operations that may return large result sets. + +[cursor-based pagination]: https://modelcontextprotocol.io/specification/2025-11-25/server/utilities/pagination + +### Overview + +Instead of offset-based pagination (page 1, page 2, etc.), MCP uses opaque cursor tokens. Each paginated response may include a `NextCursor` value. If present, pass it in the next request to retrieve the next page of results. + +Two levels of API are provided for paginated operations: + +1. **Convenience methods** (e.g., `ListToolsAsync()` returning `IList`) that automatically handle pagination and return all results. +2. **Raw methods** (e.g., `ListToolsAsync(ListToolsRequestParams)` returning the result type directly) that provide direct control over pagination. + +### Automatic pagination + +The convenience methods on handle pagination automatically, fetching all pages and returning the complete list: + +```csharp +// Fetches all tools, handling pagination automatically +IList allTools = await client.ListToolsAsync(); + +// Fetches all resources, handling pagination automatically +IList allResources = await client.ListResourcesAsync(); + +// Fetches all prompts, handling pagination automatically +IList allPrompts = await client.ListPromptsAsync(); + +// Fetches all resource templates, handling pagination automatically +IList allTemplates = await client.ListResourceTemplatesAsync(); +``` + +### Manual pagination + +For more control, use the raw methods that accept request parameters and return paginated results. This is useful for processing results page by page or limiting the number of results retrieved: + +```csharp +string? cursor = null; + +do +{ + var result = await client.ListToolsAsync(new ListToolsRequestParams + { + Cursor = cursor + }); + + // Process this page of results + foreach (var tool in result.Tools) + { + Console.WriteLine($"{tool.Name}: {tool.Description}"); + } + + // Get the cursor for the next page (null when no more pages) + cursor = result.NextCursor; + +} while (cursor is not null); +``` + +### Pagination on the server + +When implementing custom list handlers on the server, pagination is supported by examining the `Cursor` property of the request parameters and returning a `NextCursor` in the result: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithListResourcesHandler(async (ctx, ct) => + { + const int pageSize = 10; + int startIndex = 0; + + // Parse cursor to determine starting position + if (ctx.Params?.Cursor is { } cursor) + { + startIndex = int.Parse(cursor); + } + + var allResources = GetAllResources(); + var page = allResources.Skip(startIndex).Take(pageSize).ToList(); + var hasMore = startIndex + pageSize < allResources.Count; + + return new ListResourcesResult + { + Resources = page, + NextCursor = hasMore ? (startIndex + pageSize).ToString() : null + }; + }); +``` + +> [!NOTE] +> The cursor format is opaque to the client. Servers can use any encoding scheme (numeric offsets, encoded tokens, database cursors, etc.) as long as they can parse their own cursors on subsequent requests. + +> [!NOTE] +> Because the cursor format is opaque to the client, _any_ value specified in the cursor, including the empty string, signals that more results are available. If an MCP server erroneously sends an empty string cursor with the final page of results, clients can implement their own low-level pagination scheme to work around this case. diff --git a/docs/concepts/ping/ping.md b/docs/concepts/ping/ping.md new file mode 100644 index 000000000..c455cb64e --- /dev/null +++ b/docs/concepts/ping/ping.md @@ -0,0 +1,24 @@ +--- +title: Ping +author: jeffhandley +description: How to use the MCP ping mechanism to check connection health. +uid: ping +--- + +## Ping + +MCP includes a [ping mechanism] that allows either side of a connection to verify that the other side is still responsive. This is useful for connection health monitoring and keep-alive scenarios. + +[ping mechanism]: https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/ping + +### Pinging from the client + +Use the method to verify the server is responsive: + +```csharp +await client.PingAsync(cancellationToken: cancellationToken); +``` + +### Automatic ping handling + +Incoming ping requests from either side are responded to automatically. No additional configuration is needed—when a ping request is received, a ping response is sent immediately. diff --git a/docs/concepts/prompts/prompts.md b/docs/concepts/prompts/prompts.md new file mode 100644 index 000000000..08ceaf9c0 --- /dev/null +++ b/docs/concepts/prompts/prompts.md @@ -0,0 +1,221 @@ +--- +title: Prompts +author: jeffhandley +description: How to implement and consume MCP prompts that return text, images, and embedded resources. +uid: prompts +--- + +## Prompts + +MCP [prompts] allow servers to expose reusable prompt templates to clients. Prompts provide a way for servers to define structured messages that can be parameterized and composed into conversations. + +[prompts]: https://modelcontextprotocol.io/specification/2025-11-25/server/prompts + +This document covers implementing prompts on the server, consuming them from the client, rich content types, and change notifications. + +### Defining prompts on the server + +Prompts can be defined in several ways: + +- Using the attribute on methods within a class marked with +- Using factory methods from a delegate, `MethodInfo`, or `AIFunction` +- Deriving from or +- Implementing a custom via +- Implementing a low-level + +The attribute-based approach is the most common and is shown throughout this document. Prompts can return `ChatMessage` instances for simple text/image content, or instances when protocol-specific content types like are needed. + +#### Simple prompts + +A prompt without arguments: + +```csharp +[McpServerPromptType] +public class MyPrompts +{ + [McpServerPrompt, Description("A simple greeting prompt")] + public static ChatMessage Greeting() + => new(ChatRole.User, "Hello! How can you help me today?"); +} +``` + +#### Prompts with arguments + +Prompts can accept parameters to customize the generated messages. Use `[Description]` attributes to document each parameter. In addition to prompt arguments, methods can accept special parameter types that are resolved automatically: , `IProgress`, `ClaimsPrincipal`, and any service registered through dependency injection. + +```csharp +[McpServerPromptType] +public class CodePrompts +{ + [McpServerPrompt, Description("Generates a code review prompt")] + public static IEnumerable CodeReview( + [Description("The programming language")] string language, + [Description("The code to review")] string code) => + [ + new(ChatRole.User, $"Please review the following {language} code:\n\n```{language}\n{code}\n```"), + new(ChatRole.Assistant, "I'll review the code for correctness, style, and potential improvements.") + ]; + } +} +``` + +Register prompt types when building the server: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithPrompts() + .WithPrompts(); +``` + +### Rich content in prompts + +Prompt messages can contain more than just text. For text and image content, use `ChatMessage` from Microsoft.Extensions.AI. `DataContent` is automatically mapped to the appropriate MCP content block: image MIME types become , audio MIME types become , and all other MIME types become with binary resource contents. For text embedded resources specifically, use directly. + +#### Image content + +Include images in prompts using `DataContent`: + +```csharp +[McpServerPrompt, Description("A prompt that includes an image for analysis")] +public static IEnumerable AnalyzeImage( + [Description("Instructions for the analysis")] string instructions) +{ + byte[] imageBytes = LoadSampleImage(); + return + [ + new ChatMessage(ChatRole.User, + [ + new TextContent($"Please analyze this image: {instructions}"), + new DataContent(imageBytes, "image/png") + ]) + ]; +} +``` + +#### Embedded resources + +For protocol-specific content types like , use instead of `ChatMessage`. `PromptMessage` has a `Role` property and a single `Content` property of type : + +```csharp +[McpServerPrompt, Description("A prompt that includes a document resource")] +public static IEnumerable ReviewDocument( + [Description("The document ID to review")] string documentId) +{ + string content = LoadDocument(documentId); // application logic to load by ID + return + [ + new PromptMessage + { + Role = Role.User, + Content = new TextContentBlock { Text = "Please review the following document:" } + }, + new PromptMessage + { + Role = Role.User, + Content = new EmbeddedResourceBlock + { + Resource = new TextResourceContents + { + Uri = $"docs://documents/{documentId}", + MimeType = "text/plain", + Text = content + } + } + } + ]; +} +``` + +For binary resources, use the factory method: + +```csharp +new PromptMessage +{ + Role = Role.User, + Content = new EmbeddedResourceBlock + { + Resource = BlobResourceContents.FromBytes(pdfBytes, "data://report.pdf", "application/pdf") + } +} +``` + +### Consuming prompts on the client + +Clients can discover and use prompts through . + +#### Listing prompts + +```csharp +IList prompts = await client.ListPromptsAsync(); + +foreach (var prompt in prompts) +{ + Console.WriteLine($"{prompt.Name}: {prompt.Description}"); + + // Show available arguments + if (prompt.ProtocolPrompt.Arguments is { Count: > 0 }) + { + foreach (var arg in prompt.ProtocolPrompt.Arguments) + { + var required = arg.Required == true ? " (required)" : ""; + Console.WriteLine($" - {arg.Name}: {arg.Description}{required}"); + } + } +} +``` + +#### Getting a prompt + +```csharp +GetPromptResult result = await client.GetPromptAsync( + "code_review", + new Dictionary + { + ["language"] = "csharp", + ["code"] = "public static int Add(int a, int b) => a + b;" + }); + +// Process the returned messages (PromptMessage has a single Content block) +foreach (var message in result.Messages) +{ + Console.WriteLine($"[{message.Role}]:"); + switch (message.Content) + { + case TextContentBlock text: + Console.WriteLine($" {text.Text}"); + break; + case ImageContentBlock image: + Console.WriteLine($" [image] {image.MimeType}"); + break; + case EmbeddedResourceBlock resource: + Console.WriteLine($" Resource: {resource.Resource.Uri}"); + break; + } +} +``` + +### Prompt list change notifications + +Servers can dynamically add, remove, or modify prompts at runtime and notify connected clients. + +#### Sending notifications from the server + +```csharp +// After adding or removing prompts dynamically +await server.SendNotificationAsync( + NotificationMethods.PromptListChangedNotification, + new PromptListChangedNotificationParams()); +``` + +#### Handling notifications on the client + +```csharp +mcpClient.RegisterNotificationHandler( + NotificationMethods.PromptListChangedNotification, + async (notification, cancellationToken) => + { + var updatedPrompts = await mcpClient.ListPromptsAsync(cancellationToken: cancellationToken); + Console.WriteLine($"Prompt list updated. {updatedPrompts.Count} prompts available."); + }); +``` diff --git a/docs/concepts/resources/resources.md b/docs/concepts/resources/resources.md new file mode 100644 index 000000000..5d0268000 --- /dev/null +++ b/docs/concepts/resources/resources.md @@ -0,0 +1,267 @@ +--- +title: Resources +author: jeffhandley +description: How to implement and consume MCP resources for exposing data to clients. +uid: resources +--- + +## Resources + +MCP [resources] allow servers to expose data and content to clients. Resources represent any kind of data that a server wants to make available—files, database records, API responses, live system data, and more. + +[resources]: https://modelcontextprotocol.io/specification/2025-11-25/server/resources + +This document covers implementing resources on the server, consuming them from the client, resource templates, subscriptions, and change notifications. + +### Defining resources on the server + +Resources can be defined in several ways: + +- Using the attribute on methods within a class marked with +- Using factory methods from a delegate, `MethodInfo`, or `AIFunction` +- Deriving from or +- Implementing a custom via +- Implementing a low-level + +The attribute-based approach is the most common and is shown throughout this document. + +#### Direct resources + +Direct resources have a fixed URI and are returned in the resource list: + +```csharp +[McpServerResourceType] +public class MyResources +{ + [McpServerResource(UriTemplate = "config://app/settings", Name = "App Settings", MimeType = "application/json")] + [Description("Returns application configuration settings")] + public static string GetSettings() => JsonSerializer.Serialize(new { theme = "dark", language = "en" }); +} +``` + +#### Template resources + +Template resources use [URI templates (RFC 6570)] with parameters. They are returned separately in the resource templates list and can match a range of URIs: + +[URI templates (RFC 6570)]: https://tools.ietf.org/html/rfc6570 + +```csharp +[McpServerResourceType] +public class DocumentResources +{ + [McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")] + [Description("Returns an article by its ID")] + public static ResourceContents GetArticle(string id) + { + string? content = LoadArticle(id); // application logic to load by ID + + if (content is null) + { + throw new McpException($"Article not found: {id}"); + } + + return new TextResourceContents + { + Uri = $"docs://articles/{id}", + MimeType = "text/plain", + Text = content + }; + } +} +``` + +Register resource types when building the server: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithResources() + .WithResources(); +``` + +### Reading text resources + +Text resources return their content as with a `Text` property: + +```csharp +[McpServerResource(UriTemplate = "notes://daily/{date}", Name = "Daily Notes")] +[Description("Returns notes for a given date")] +public static TextResourceContents GetDailyNotes(string date) +{ + return new TextResourceContents + { + Uri = $"notes://daily/{date}", + MimeType = "text/markdown", + Text = $"# Notes for {date}\n\n- Meeting at 10am\n- Review PRs" + }; +} +``` + +### Reading binary resources + +Binary resources return their content as with a `Blob` property containing the raw bytes. Use the factory method to construct instances: + +```csharp +[McpServerResource(UriTemplate = "images://photos/{id}", Name = "Photo")] +[Description("Returns a photo by ID")] +public static BlobResourceContents GetPhoto(int id) +{ + byte[] imageData = LoadPhoto(id); + return BlobResourceContents.FromBytes(imageData, $"images://photos/{id}", "image/png"); +} +``` + +### Consuming resources on the client + +Clients can discover and read resources using : + +#### Listing resources + +```csharp +// List direct resources +IList resources = await client.ListResourcesAsync(); + +foreach (var resource in resources) +{ + Console.WriteLine($"{resource.Name} ({resource.Uri})"); + Console.WriteLine($" MIME: {resource.MimeType}"); + Console.WriteLine($" Description: {resource.Description}"); +} +``` + +#### Listing resource templates + +```csharp +// List resource templates (parameterized URIs) +IList templates = await client.ListResourceTemplatesAsync(); + +foreach (var template in templates) +{ + Console.WriteLine($"{template.Name}: {template.UriTemplate}"); +} +``` + +#### Reading a resource + +```csharp +// Read a direct resource by URI +ReadResourceResult result = await client.ReadResourceAsync("config://app/settings"); + +foreach (var content in result.Contents) +{ + if (content is TextResourceContents text) + { + Console.WriteLine($"[{text.MimeType}] {text.Text}"); + } + else if (content is BlobResourceContents blob) + { + Console.WriteLine($"[{blob.MimeType}] {blob.Blob.Length} bytes"); + } +} +``` + +#### Reading a template resource + +```csharp +// Read a resource using a URI template with parameter values +ReadResourceResult result = await client.ReadResourceAsync( + "file:///{path}", + new Dictionary { ["path"] = "docs/readme.md" }); +``` + +### Resource subscriptions + +Clients can subscribe to resource updates to be notified when a resource's content changes. The server must declare subscription support in its capabilities. + +#### Subscribing on the client + +```csharp +// Subscribe with an inline handler +IAsyncDisposable subscription = await client.SubscribeToResourceAsync( + "config://app/settings", + async (notification, cancellationToken) => + { + Console.WriteLine($"Resource updated: {notification.Uri}"); + + // Re-read the resource to get updated content + var updated = await client.ReadResourceAsync(notification.Uri, cancellationToken: cancellationToken); + // Process updated content... + }); + +// Later, unsubscribe by disposing +await subscription.DisposeAsync(); +``` + +Clients can also subscribe and unsubscribe separately: + +```csharp +// Subscribe without a handler (use a global notification handler instead) +await client.SubscribeToResourceAsync("config://app/settings"); + +// Unsubscribe when no longer interested +await client.UnsubscribeFromResourceAsync("config://app/settings"); +``` + +#### Handling subscriptions on the server + +Register subscription handlers when building the server: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithResources() + .WithSubscribeToResourcesHandler(async (ctx, ct) => + { + if (ctx.Params?.Uri is { } uri) + { + // Track the subscription (e.g., in a concurrent dictionary) + subscriptions[ctx.Server.SessionId].TryAdd(uri, 0); + } + return new EmptyResult(); + }) + .WithUnsubscribeFromResourcesHandler(async (ctx, ct) => + { + if (ctx.Params?.Uri is { } uri) + { + subscriptions[ctx.Server.SessionId].TryRemove(uri, out _); + } + return new EmptyResult(); + }); +``` + +#### Sending resource update notifications + +When a resource's content changes, the server notifies subscribed clients: + +```csharp +// Notify that a specific resource was updated +await server.SendNotificationAsync( + NotificationMethods.ResourceUpdatedNotification, + new ResourceUpdatedNotificationParams { Uri = "config://app/settings" }); +``` + +### Resource list change notifications + +When the set of available resources changes (resources added or removed), the server notifies clients: + +#### Sending notifications from the server + +```csharp +// After adding or removing resources dynamically +await server.SendNotificationAsync( + NotificationMethods.ResourceListChangedNotification, + new ResourceListChangedNotificationParams()); +``` + +#### Handling notifications on the client + +```csharp +mcpClient.RegisterNotificationHandler( + NotificationMethods.ResourceListChangedNotification, + async (notification, cancellationToken) => + { + // Refresh the resource list + var updatedResources = await mcpClient.ListResourcesAsync(cancellationToken: cancellationToken); + Console.WriteLine($"Resource list updated. {updatedResources.Count} resources available."); + }); +``` diff --git a/docs/concepts/roots/roots.md b/docs/concepts/roots/roots.md new file mode 100644 index 000000000..94b330871 --- /dev/null +++ b/docs/concepts/roots/roots.md @@ -0,0 +1,105 @@ +--- +title: Roots +author: jeffhandley +description: How to provide filesystem roots from clients to MCP servers. +uid: roots +--- + +## Roots + +MCP [roots] allow clients to inform servers about the relevant locations in the filesystem or other hierarchical data sources. Roots are a client-provided feature—the client declares its root URIs during initialization, and the server can request them to understand the working context. + +[roots]: https://modelcontextprotocol.io/specification/2025-11-25/client/roots + +### Overview + +Roots provide a mechanism for the client to tell the server which directories, projects, or repositories are relevant to the current session. A server might use roots to: + +- Scope file searches to the user's project directories +- Understand which repositories are being worked on +- Limit operations to specific filesystem boundaries + +Each root is represented by a with a URI and an optional human-readable name. + +### Declaring roots capability on the client + +Clients advertise their support for roots in the capabilities sent during initialization. The roots capability is created automatically when a roots handler is provided. Configure the handler through : + +```csharp +var options = new McpClientOptions +{ + Handlers = new McpClientHandlers + { + RootsHandler = (request, cancellationToken) => + { + return ValueTask.FromResult(new ListRootsResult + { + Roots = + [ + new Root + { + Uri = "file:///home/user/projects/my-app", + Name = "My Application" + }, + new Root + { + Uri = "file:///home/user/projects/shared-lib", + Name = "Shared Library" + } + ] + }); + } + } +}; + +await using var client = await McpClient.CreateAsync(transport, options); +``` + +### Requesting roots from the server + +Servers can request the client's root list using : + +```csharp +[McpServerTool, Description("Lists the user's project roots")] +public static async Task ListProjectRoots(McpServer server, CancellationToken cancellationToken) +{ + var result = await server.RequestRootsAsync(new ListRootsRequestParams(), cancellationToken); + + var summary = new StringBuilder(); + foreach (var root in result.Roots) + { + summary.AppendLine($"- {root.Name ?? root.Uri}: {root.Uri}"); + } + + return summary.ToString(); +} +``` + +### Roots change notifications + +When the set of roots changes (for example, the user opens a new project), the client notifies the server so it can update its understanding of the working context. + +#### Sending change notifications from the client + +Roots change notifications are automatically sent when the client's roots handler is updated. However, clients can also send the notification explicitly: + +```csharp +await mcpClient.SendNotificationAsync( + NotificationMethods.RootsListChangedNotification, + new RootsListChangedNotificationParams()); +``` + +#### Handling change notifications on the server + +Servers can register a handler to respond when the client's roots change: + +```csharp +server.RegisterNotificationHandler( + NotificationMethods.RootsListChangedNotification, + async (notification, cancellationToken) => + { + // Re-request the roots list to get the updated set + var result = await server.RequestRootsAsync(new ListRootsRequestParams(), cancellationToken); + Console.WriteLine($"Roots updated. {result.Roots.Count} roots available."); + }); +``` diff --git a/docs/concepts/sampling/sampling.md b/docs/concepts/sampling/sampling.md new file mode 100644 index 000000000..6ff7ec6fa --- /dev/null +++ b/docs/concepts/sampling/sampling.md @@ -0,0 +1,119 @@ +--- +title: Sampling +author: jeffhandley +description: How servers request LLM completions from the client using the sampling feature. +uid: sampling +--- + +## Sampling + +MCP [sampling] allows servers to request LLM completions from the client. This enables agentic behaviors where a server-side tool delegates reasoning back to the client's language model — for example, summarizing content, generating text, or making decisions. + +[sampling]: https://modelcontextprotocol.io/specification/2025-11-25/client/sampling + +### How sampling works + +1. The server calls (or uses the adapter) during tool execution. +2. The request is sent to the connected client over MCP. +3. The client's processes the request — typically by forwarding it to an LLM. +4. The client returns the LLM response to the server, which continues tool execution. + +### Server: requesting a completion + +Inject into a tool method and use the extension method to get an that sends requests through the connected client: + +```csharp +[McpServerTool(Name = "SummarizeContent"), Description("Summarizes the given text")] +public static async Task Summarize( + McpServer server, + [Description("The text to summarize")] string text, + CancellationToken cancellationToken) +{ + ChatMessage[] messages = + [ + new(ChatRole.User, "Briefly summarize the following content:"), + new(ChatRole.User, text), + ]; + + ChatOptions options = new() + { + MaxOutputTokens = 256, + Temperature = 0.3f, + }; + + return $"Summary: {await server.AsSamplingChatClient().GetResponseAsync(messages, options, cancellationToken)}"; +} +``` + +Alternatively, use directly for lower-level control: + +```csharp +CreateMessageResult result = await server.SampleAsync( + new CreateMessageRequestParams + { + Messages = + [ + new SamplingMessage + { + Role = Role.User, + Content = [new TextContentBlock { Text = "What is 2 + 2?" }] + } + ], + MaxTokens = 100, + }, + cancellationToken); + +string response = result.Content.OfType().FirstOrDefault()?.Text ?? string.Empty; +``` + +### Client: handling sampling requests + +Set when creating the client. This handler is called when a server sends a `sampling/createMessage` request. + +#### Using an IChatClient + +The simplest approach is to use with any implementation: + +```csharp +IChatClient chatClient = new OllamaChatClient(new Uri("http://localhost:11434"), "llama3"); + +McpClientOptions options = new() +{ + Handlers = new() + { + SamplingHandler = chatClient.CreateSamplingHandler() + } +}; + +await using var client = await McpClient.CreateAsync(transport, options); +``` + +#### Custom handler + +For full control, provide a custom delegate: + +```csharp +McpClientOptions options = new() +{ + Handlers = new() + { + SamplingHandler = async (request, progress, cancellationToken) => + { + // Forward to your LLM, apply content filtering, etc. + string prompt = request?.Messages?.LastOrDefault()?.Content + .OfType().FirstOrDefault()?.Text ?? string.Empty; + + return new CreateMessageResult + { + Model = "my-model", + Role = Role.Assistant, + Content = [new TextContentBlock { Text = $"Response to: {prompt}" }] + }; + } + } +}; +``` + +### Capability negotiation + +Sampling requires the client to advertise the `sampling` capability. This is handled automatically — when a is set, the client includes the sampling capability during initialization. The server can check whether the client supports sampling before calling ; if sampling is not supported, the method throws . diff --git a/docs/concepts/toc.yml b/docs/concepts/toc.yml index 59ef1f1b7..18845e64a 100644 --- a/docs/concepts/toc.yml +++ b/docs/concepts/toc.yml @@ -3,16 +3,36 @@ items: href: index.md - name: Base Protocol items: + - name: Capabilities + uid: capabilities + - name: Transports + uid: transports + - name: Ping + uid: ping - name: Progress uid: progress + - name: Cancellation + uid: cancellation + - name: Pagination + uid: pagination - name: Tasks uid: tasks - name: Client Features items: + - name: Roots + uid: roots - name: Elicitation uid: elicitation - name: Server Features items: + - name: Tools + uid: tools + - name: Resources + uid: resources + - name: Prompts + uid: prompts + - name: Completions + uid: completions - name: Logging uid: logging - name: HTTP Context diff --git a/docs/concepts/tools/tools.md b/docs/concepts/tools/tools.md new file mode 100644 index 000000000..6ac2f9a5e --- /dev/null +++ b/docs/concepts/tools/tools.md @@ -0,0 +1,317 @@ +--- +title: Tools +author: jeffhandley +description: How to implement and consume MCP tools that return text, images, audio, and embedded resources. +uid: tools +--- + +## Tools + +MCP [tools] allow servers to expose callable functions to clients. Tools are the primary mechanism for LLMs to take action through MCP—they enable everything from querying databases to calling web APIs. + +[tools]: https://modelcontextprotocol.io/specification/2025-11-25/server/tools + +This document covers tool content types, change notifications, and schema generation. + +### Defining tools on the server + +Tools can be defined in several ways: + +- Using the attribute on methods within a class marked with +- Using factory methods from a delegate, `MethodInfo`, or `AIFunction` +- Deriving from or +- Implementing a custom via +- Implementing a low-level + +The attribute-based approach is the most common and is shown throughout this document. Parameters are automatically deserialized from JSON and documented using `[Description]` attributes. In addition to tool arguments, methods can accept special parameter types that are resolved automatically: , `IProgress`, `ClaimsPrincipal`, and any service registered through dependency injection. + +```csharp +[McpServerToolType] +public class MyTools +{ + [McpServerTool, Description("Echoes the input message back")] + public static string Echo([Description("The message to echo")] string message) + => $"Echo: {message}"; +} +``` + +Register the tool type when building the server: + +```csharp +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithTools(); +``` + +### Content types + +Tools can return various content types. The simplest is a `string`, which is automatically wrapped in a . For richer content, tools can return one or more instances. Tools can also return `DataContent` from Microsoft.Extensions.AI, which is automatically mapped to the appropriate MCP content block: image MIME types become , audio MIME types become , and all other MIME types become with binary resource contents. + +#### Text content + +Return a `string` or a directly: + +```csharp +[McpServerTool, Description("Returns a greeting")] +public static string Greet(string name) => $"Hello, {name}!"; +``` + +#### Image content + +Return an with base64-encoded image data and a MIME type. +Use the factory method or construct the block directly: + +```csharp +[McpServerTool, Description("Returns a generated image")] +public static ImageContentBlock GenerateImage() +{ + byte[] pngBytes = CreateImage(); // your image generation logic + return ImageContentBlock.FromBytes(pngBytes, "image/png"); +} +``` + +#### Audio content + +Return an with base64-encoded audio data and a MIME type. +The factory method encodes the raw bytes automatically: + +```csharp +[McpServerTool, Description("Returns a synthesized audio clip")] +public static AudioContentBlock Synthesize(string text) +{ + byte[] wavBytes = TextToSpeech(text); // your audio synthesis logic + return AudioContentBlock.FromBytes(wavBytes, "audio/wav"); +} +``` + +Supported audio MIME types include `audio/wav`, `audio/mp3`, `audio/ogg`, and others depending on what the client can handle. + +#### Embedded resources + +Return an to embed a resource directly in a tool result. +The resource can contain either text or binary data through or : + +```csharp +[McpServerTool, Description("Returns a document as an embedded resource")] +public static EmbeddedResourceBlock GetDocument() +{ + return new EmbeddedResourceBlock + { + Resource = new TextResourceContents + { + Uri = "docs://readme", + MimeType = "text/plain", + Text = "This is the document content." + } + }; +} +``` + +For binary resources, use : + +```csharp +[McpServerTool, Description("Returns a binary resource")] +public static EmbeddedResourceBlock GetBinaryData(string id) +{ + byte[] data = LoadData(id); // application logic to load data by ID + return new EmbeddedResourceBlock + { + Resource = BlobResourceContents.FromBytes(data, $"data://items/{id}", "application/octet-stream") + }; +} +``` + +#### Mixed content + +Tools can return multiple content blocks by returning `IEnumerable`: + +```csharp +[McpServerTool, Description("Returns text and an image")] +public static IEnumerable DescribeImage() +{ + byte[] imageBytes = GetImage(); + return + [ + new TextContentBlock { Text = "Here is the generated image:" }, + ImageContentBlock.FromBytes(imageBytes, "image/png"), + new TextContentBlock { Text = "The image shows a landscape." } + ]; +} +``` + +#### Content annotations + +Any content block can include to provide hints about the intended audience and priority: + +```csharp +new TextContentBlock +{ + Text = "Detailed debug information", + Annotations = new Annotations + { + Audience = [Role.Assistant], // Only for the LLM, not the user + Priority = 0.3f // Low priority (0.0 to 1.0) + } +} +``` + +### Consuming tools on the client + +Clients can discover and call tools using : + +```csharp +// List available tools +IList tools = await client.ListToolsAsync(); + +foreach (var tool in tools) +{ + Console.WriteLine($"{tool.Name}: {tool.Description}"); +} + +// Call a tool by finding it in the list +McpClientTool echoTool = tools.First(t => t.Name == "echo"); +CallToolResult result = await echoTool.CallAsync( + new Dictionary { ["message"] = "Hello!" }); + +// Process the result content blocks +foreach (var content in result.Content) +{ + switch (content) + { + case TextContentBlock text: + Console.WriteLine(text.Text); + break; + case ImageContentBlock image: + File.WriteAllBytes("output.png", image.DecodedData.ToArray()); + break; + case AudioContentBlock audio: + File.WriteAllBytes("output.wav", audio.DecodedData.ToArray()); + break; + case EmbeddedResourceBlock resource: + if (resource.Resource is TextResourceContents textResource) + Console.WriteLine(textResource.Text); + break; + } +} +``` + +### Error handling + +Tool errors in MCP are distinct from protocol errors. When a tool encounters an error during execution, the error is reported inside the with set to `true`, rather than as a protocol-level exception. This allows the LLM to see the error and potentially recover. + +#### Automatic exception handling + +When a tool method throws an exception, the server catches it and returns a `CallToolResult` with `IsError = true`, with the following exceptions: + +- is re-thrown as a JSON-RPC error response (not a tool error result). +- `OperationCanceledException` is re-thrown when the cancellation token was triggered. + +For all other exceptions, the error is returned as a tool result. If the exception derives from (excluding `McpProtocolException`, which is re-thrown above), its message is included in the error text; otherwise, a generic message is returned to avoid leaking internal details. + +```csharp +[McpServerTool, Description("Divides two numbers")] +public static double Divide(double a, double b) +{ + if (b == 0) + { + // ArgumentException is not an McpException, so the client receives a generic message: + // "An error occurred invoking 'divide'." + throw new ArgumentException("Cannot divide by zero"); + } + + return a / b; +} +``` + +#### Protocol errors + +Throw to signal a protocol-level error (e.g., invalid parameters or unknown tool). These exceptions propagate as JSON-RPC error responses rather than tool error results: + +```csharp +[McpServerTool, Description("Processes the input")] +public static string Process(string input) +{ + if (string.IsNullOrEmpty(input)) + { + // Propagates as a JSON-RPC error with code -32602 (InvalidParams) + // and message "Missing required input" + throw new McpProtocolException("Missing required input", McpErrorCode.InvalidParams); + } + + return $"Processed: {input}"; +} +``` + +#### Checking for errors on the client + +On the client side, inspect the property after calling a tool: + +```csharp +CallToolResult result = await client.CallToolAsync("divide", new Dictionary +{ + ["a"] = 10, + ["b"] = 0 +}); + +if (result.IsError is true) +{ + // Prints: "Tool error: An error occurred invoking 'divide'." + Console.WriteLine($"Tool error: {result.Content.OfType().FirstOrDefault()?.Text}"); +} +``` + +### Tool list change notifications + +Servers can dynamically add, remove, or modify tools at runtime. When the tool list changes, the server notifies connected clients so they can refresh their tool list. + +#### Sending notifications from the server + +Inject and call the notification method after modifying the tool list: + +```csharp +// After adding or removing tools dynamically +await server.SendNotificationAsync( + NotificationMethods.ToolListChangedNotification, + new ToolListChangedNotificationParams()); +``` + +#### Handling notifications on the client + +Register a notification handler on the client to respond to tool list changes: + +```csharp +mcpClient.RegisterNotificationHandler( + NotificationMethods.ToolListChangedNotification, + async (notification, cancellationToken) => + { + // Refresh the tool list + var updatedTools = await mcpClient.ListToolsAsync(cancellationToken: cancellationToken); + Console.WriteLine($"Tool list updated. {updatedTools.Count} tools available."); + }); +``` + +### JSON Schema generation + +Tool parameters are described using [JSON Schema 2020-12]. JSON schemas are automatically generated from .NET method signatures when the `[McpServerTool]` attribute is applied. Parameter types are mapped to JSON Schema types: + +[JSON Schema 2020-12]: https://json-schema.org/specification + +| .NET Type | JSON Schema Type | +|-----------|-----------------| +| `string` | `string` | +| `int`, `long` | `integer` | +| `float`, `double` | `number` | +| `bool` | `boolean` | +| Complex types | `object` with `properties` | + +Use `[Description]` attributes on parameters to populate the `description` field in the generated schema. This helps LLMs understand what each parameter expects. + +```csharp +[McpServerTool, Description("Searches for items")] +public static string Search( + [Description("The search query string")] string query, + [Description("Maximum results to return (1-100)")] int maxResults = 10) +{ + // Schema will include descriptions and default value for maxResults +} +``` diff --git a/docs/concepts/transports/transports.md b/docs/concepts/transports/transports.md new file mode 100644 index 000000000..976df29d5 --- /dev/null +++ b/docs/concepts/transports/transports.md @@ -0,0 +1,188 @@ +--- +title: Transports +author: jeffhandley +description: How to configure stdio, Streamable HTTP, and SSE transports for MCP communication. +uid: transports +--- + +## Transports + +MCP uses a [transport layer] to handle the communication between clients and servers. Three transport mechanisms are supported: **stdio**, **Streamable HTTP**, and **SSE** (Server-Sent Events, legacy). + +[transport layer]: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports + +### stdio transport + +The stdio transport communicates over standard input and output streams. It is best suited for local integrations, as the MCP server runs as a child process of the client. + +#### stdio client + +Use to launch a server process and communicate over its stdin/stdout. This example connects to the [NuGet MCP Server]: + +[NuGet MCP Server]: https://learn.microsoft.com/nuget/concepts/nuget-mcp-server + +```csharp +var transport = new StdioClientTransport(new StdioClientTransportOptions +{ + Command = "dnx", + Arguments = ["NuGet.Mcp.Server"], + ShutdownTimeout = TimeSpan.FromSeconds(10) +}); + +await using var client = await McpClient.CreateAsync(transport); +``` + +Key properties: + +| Property | Description | +|----------|-------------| +| `Command` | The executable to launch (required) | +| `Arguments` | Command-line arguments for the process | +| `WorkingDirectory` | Working directory for the server process | +| `EnvironmentVariables` | Environment variables (merged with current; `null` values remove variables) | +| `ShutdownTimeout` | Graceful shutdown timeout (default: 5 seconds) | +| `StandardErrorLines` | Callback for stderr output from the server process | +| `Name` | Optional transport identifier for logging | + +#### stdio server + +Use for servers that communicate over stdin/stdout: + +```csharp +var builder = Host.CreateApplicationBuilder(args); + +builder.Services.AddMcpServer() + .WithStdioServerTransport() + .WithTools(); + +await builder.Build().RunAsync(); +``` + +### Streamable HTTP transport + +The [Streamable HTTP] transport uses HTTP for bidirectional communication with optional streaming. This is the recommended transport for remote servers. + +[Streamable HTTP]: https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#streamable-http + +#### Streamable HTTP client + +Use with : + +```csharp +var transport = new HttpClientTransport(new HttpClientTransportOptions +{ + Endpoint = new Uri("https://my-mcp-server.example.com/mcp"), + TransportMode = HttpTransportMode.StreamableHttp, + ConnectionTimeout = TimeSpan.FromSeconds(30), + AdditionalHeaders = new Dictionary + { + ["X-Custom-Header"] = "value" + } +}); + +await using var client = await McpClient.CreateAsync(transport); +``` + +The client also supports automatic transport detection with (the default), which tries Streamable HTTP first and falls back to SSE if the server does not support it: + +```csharp +var transport = new HttpClientTransport(new HttpClientTransportOptions +{ + Endpoint = new Uri("https://my-mcp-server.example.com/mcp"), + // TransportMode defaults to AutoDetect +}); +``` + +#### Resuming sessions + +Streamable HTTP supports session resumption. Save the session ID, server capabilities, and server info from the original session, then use to reconnect: + +```csharp +var transport = new HttpClientTransport(new HttpClientTransportOptions +{ + Endpoint = new Uri("https://my-mcp-server.example.com/mcp"), + KnownSessionId = previousSessionId +}); + +await using var client = await McpClient.ResumeSessionAsync(transport, new ResumeClientSessionOptions +{ + ServerCapabilities = previousServerCapabilities, + ServerInfo = previousServerInfo +}); +``` + +#### Streamable HTTP server (ASP.NET Core) + +Use the `ModelContextProtocol.AspNetCore` package to host an MCP server over HTTP. The method maps the Streamable HTTP endpoint at the specified route (root by default). It also maps legacy SSE endpoints at `{route}/sse` and `{route}/message` for backward compatibility. + +```csharp +var builder = WebApplication.CreateBuilder(args); + +builder.Services.AddMcpServer() + .WithHttpTransport() + .WithTools(); + +var app = builder.Build(); +app.MapMcp(); +app.Run(); +``` + +A custom route can be specified. For example, the [AspNetCoreMcpPerSessionTools] sample uses a route parameter: + +[AspNetCoreMcpPerSessionTools]: https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/AspNetCoreMcpPerSessionTools + +```csharp +app.MapMcp("/mcp"); +``` + +When using a custom route, Streamable HTTP clients should connect directly to that route (e.g., `https://host/mcp`), while SSE clients should connect to `{route}/sse` (e.g., `https://host/mcp/sse`). + +See the `ModelContextProtocol.AspNetCore` package [README](https://github.com/modelcontextprotocol/csharp-sdk/blob/main/src/ModelContextProtocol.AspNetCore/README.md) for more configuration options. + +### SSE transport (legacy) + +The [SSE (Server-Sent Events)] transport is a legacy mechanism that uses unidirectional server-to-client streaming with a separate HTTP endpoint for client-to-server messages. New implementations should prefer Streamable HTTP. + +[SSE (Server-Sent Events)]: https://modelcontextprotocol.io/specification/2024-11-05/basic/transports#http-with-sse + +> [!NOTE] +> The SSE transport is considered legacy. The [Streamable HTTP](#streamable-http-transport) transport is the recommended approach for HTTP-based communication and supports bidirectional streaming. + +#### SSE client + +Use with : + +```csharp +var transport = new HttpClientTransport(new HttpClientTransportOptions +{ + Endpoint = new Uri("https://my-mcp-server.example.com/sse"), + TransportMode = HttpTransportMode.Sse, + MaxReconnectionAttempts = 5, + DefaultReconnectionInterval = TimeSpan.FromSeconds(1) +}); + +await using var client = await McpClient.CreateAsync(transport); +``` + +SSE-specific configuration options: + +| Property | Description | +|----------|-------------| +| `MaxReconnectionAttempts` | Maximum number of reconnection attempts on stream disconnect (default: 5) | +| `DefaultReconnectionInterval` | Wait time between reconnection attempts (default: 1 second) | + +#### SSE server (ASP.NET Core) + +The ASP.NET Core integration supports SSE transport alongside Streamable HTTP. The same `MapMcp()` endpoint handles both protocols — clients connecting with SSE are automatically served using the legacy SSE mechanism: + +No additional configuration is needed. When a client connects using the SSE protocol, the server responds with an SSE stream for server-to-client messages and accepts client-to-server messages via a separate POST endpoint. + +### Transport mode comparison + +| Feature | stdio | Streamable HTTP | SSE (Legacy) | +|---------|-------|----------------|--------------| +| Process model | Child process | Remote HTTP | Remote HTTP | +| Direction | Bidirectional | Bidirectional | Server→client stream + client→server POST | +| Session resumption | N/A | ✓ | ✗ | +| Authentication | Process-level | HTTP auth (OAuth, headers) | HTTP auth (OAuth, headers) | +| Best for | Local tools | Remote servers | Legacy compatibility |