Claude Skill

dotnet-mcp

Build or consume Model Context Protocol (MCP) servers and clients in .NET using the official MCP C# SDK, including stdio, Streamable HTTP, tools, prompts, resources, and capability negotiation.

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download postpartum-genushyacinthus29-dotnet-skills-skills_dotnet-mcp-bfa4ebd.zip · 10 KB
Part of postpartum-genushyacinthus29/dotnet-skills — 80 skills

Install

skills CLI npx skills add https://github.com/Postpartum-genushyacinthus29/dotnet-skills/tree/main/skills/dotnet-mcp
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install postpartum-genushyacinthus29-dotnet-skills@llmmart
Git git clone https://github.com/Postpartum-genushyacinthus29/dotnet-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole postpartum-genushyacinthus29/dotnet-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

MCP C# SDK for .NET

Trigger On

  • building or consuming MCP servers from a .NET application or library
  • choosing between stdio and HTTP transport for MCP
  • exposing tools, resources, prompts, completions, or logging to an MCP host
  • connecting a .NET app to an existing MCP server and passing discovered tools into IChatClient
  • bootstrapping a minimal MCP client/server from the .NET AI quickstarts or publishing a server to the MCP Registry
  • implementing capability-aware flows such as roots, sampling, elicitation, subscriptions, or session resumption

Use This Skill Instead Of

  • Use dotnet-mcp when protocol interoperability is the requirement.
  • Use dotnet-microsoft-extensions-ai when you only need model/provider abstraction or local tool orchestration without the MCP wire protocol.
  • Use dotnet-microsoft-agent-framework when the main problem is agent orchestration; combine it with dotnet-mcp only when those agents must consume or expose MCP endpoints.
  • Use the .NET AI quickstarts for the very first vertical slice, then come back here to harden transport, capability negotiation, publishing, and host interoperability.

Documentation

References

Load only what the task needs:

  • references/patterns.md - current server/client patterns, transports, capabilities, filters, and chat-client integration
  • references/security.md - safe error handling, auth boundaries, stdio logging hygiene, and defensive tool/resource patterns

Package Selection

Package Choose when
ModelContextProtocol.Core You only need a client or low-level server APIs and want the smallest dependency set.
ModelContextProtocol You want the main SDK package with hosting, DI, attribute discovery, and stdio server support. Start here for most projects.
ModelContextProtocol.AspNetCore You are hosting a remote MCP server in ASP.NET Core over HTTP. This includes the main package.

Transport Selection

Transport Use when Notes
StdioClientTransport / WithStdioServerTransport() The MCP server should run as a local child process. Best for local tooling and editor/agent integrations.
HttpClientTransport + HttpTransportMode.StreamableHttp The server is remote or should be reachable over HTTP. Recommended HTTP transport; supports streaming and session resumption.
HttpTransportMode.Sse You must connect to an older SSE-only server. Legacy compatibility only; do not choose this for new servers.
flowchart LR
    A["Need MCP interoperability in .NET"] --> B{"Role?"}
    B -->|"Expose MCP surface"| C{"Where will it run?"}
    B -->|"Consume an MCP server"| D{"Transport?"}
    C -->|"Local child process"| E["ModelContextProtocol\nAddMcpServer()\nWithStdioServerTransport()"]
    C -->|"Remote HTTP endpoint"| F["ModelContextProtocol.AspNetCore\nAddMcpServer()\nWithHttpTransport()\nMapMcp()"]
    D -->|"stdio"| G["StdioClientTransport\nMcpClient.CreateAsync()"]
    D -->|"HTTP"| H["HttpClientTransport\nAutoDetect or StreamableHttp"]
    E --> I["Register tools/resources/prompts"]
    F --> I
    G --> J["Check ServerCapabilities\nbefore optional features"]
    H --> J

Workflow

  1. Pick the package and transport first.

    • Local child-process server: ModelContextProtocol + WithStdioServerTransport().
    • Remote server: ModelContextProtocol.AspNetCore + WithHttpTransport() + MapMcp().
    • Client-only app: start with ModelContextProtocol or ModelContextProtocol.Core.
    • Registry distribution: pair a minimal server with the MCP Registry publishing flow only after the server contract is stable.
  2. Model the MCP surface explicitly.

    • Tools: [McpServerToolType] + [McpServerTool]
    • Resources: [McpServerResourceType] + [McpServerResource]
    • Prompts: [McpServerPromptType] + [McpServerPrompt]
    • Use custom handlers or filters only for cross-cutting behavior, protocol extensions, or advanced routing.
  3. Prefer attribute discovery for straightforward servers.

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = Host.CreateApplicationBuilder(args);
builder.Logging.AddConsole(options =>
{
    options.LogToStandardErrorThreshold = LogLevel.Trace;
});

builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

await builder.Build().RunAsync();

[McpServerToolType]
public static class EchoTool
{
    [McpServerTool, Description("Echoes the message back to the client.")]
    public static string Echo(string message) => $"hello {message}";
}
  1. For HTTP servers, use the ASP.NET Core transport and map the endpoint directly.
using ModelContextProtocol.Server;
using System.ComponentModel;

var builder = WebApplication.CreateBuilder(args);

builder.Services
    .AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();

var app = builder.Build();
app.MapMcp("/mcp");
app.Run();

[McpServerToolType]
public static class EchoTool
{
    [McpServerTool, Description("Echoes the message back to the client.")]
    public static string Echo(string message) => $"hello {message}";
}
  1. When consuming a server, use McpClient.CreateAsync(...) and stay capability-aware.
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

var transport = new StdioClientTransport(new StdioClientTransportOptions
{
    Name = "Everything",
    Command = "npx",
    Arguments = ["-y", "@modelcontextprotocol/server-everything"],
});

await using var client = await McpClient.CreateAsync(transport);

IList<McpClientTool> tools = await client.ListToolsAsync();

if (client.ServerCapabilities.Prompts is not null)
{
    var prompts = await client.ListPromptsAsync();
}
  1. Treat optional features as negotiated capabilities, not assumptions.

    • Client capabilities: configure McpClientOptions.Capabilities for roots, sampling, and elicitation.
    • Server capabilities are inferred from registered features.
    • Check client.ServerCapabilities before using completions, logging, prompt list-change notifications, or resource subscriptions.
    • Use client.NegotiatedProtocolVersion or server.NegotiatedProtocolVersion only when version-specific behavior matters.
  2. Keep HTTP guidance current.

    • Streamable HTTP is the recommended transport for remote servers.
    • MapMcp() also serves SSE compatibility endpoints for older clients.
    • HTTP clients can use AutoDetect by default, or force StreamableHttp / Sse.
    • Session resumption is available for Streamable HTTP through McpClient.ResumeSessionAsync(...).
  3. Treat the .NET AI MCP quickstarts as bootstrap examples.

    • build-mcp-client and build-mcp-server are good starting points when the surrounding app is still MEAI-centric.
    • publish-mcp-registry is the distribution step, not the design step. Stabilize the protocol surface before publishing.
  4. Respect current error and serialization rules.

    • Tool exceptions normally come back as CallToolResult.IsError == true.
    • Throw McpProtocolException only for protocol-level JSON-RPC failures.
    • McpClientTool inherits from AIFunction, so discovered tools can be passed directly into IChatClient.
    • Experimental APIs use MCPEXP... diagnostics; suppress them intentionally, not globally by accident.
    • If you use a custom JsonSerializerContext, prepend McpJsonUtilities.DefaultOptions.TypeInfoResolver so MCP protocol types keep the SDK's contract.

Anti-Patterns To Avoid

Anti-pattern Why it causes trouble Better approach
Picking HTTP transport for a purely local child-process scenario Adds unnecessary hosting, auth, and deployment surface Use stdio for local/editor-hosted integrations
Treating SSE as the default remote transport Locks new work to legacy behavior Prefer Streamable HTTP and keep SSE only for backward compatibility
Writing tools without [Description] metadata Hosts and models lose schema clarity Describe tool purpose and parameters explicitly
Returning huge binary/text payloads from every tool call Bloats context and slows hosts Return focused content and move large data to resources
Logging to stdout on stdio servers Corrupts the protocol stream Send logs to stderr
Assuming prompts/resources/logging/completions exist Breaks against partial implementations Check negotiated capabilities first
Using filters for normal business logic Makes handlers opaque and hard to reason about Keep filters for cross-cutting policy, audit, or protocol plumbing

Deliver

  • a correctly packaged MCP server or client that matches the deployment topology
  • explicit tool/resource/prompt definitions with descriptions and bounded payloads
  • capability-aware handling for optional MCP features
  • validation notes for transport, auth boundary, and host/client interoperability

Validate

  • chosen package matches the topology: Core, ModelContextProtocol, or AspNetCore
  • stdio servers do not write logs or diagnostics to stdout
  • HTTP servers use MapMcp() and are tested at the final route, for example /mcp
  • tools, resources, and prompts use current [McpServer*] attributes or documented handler/filter alternatives
  • client code checks ServerCapabilities before using subscriptions, completions, logging, or prompt/resource list-change flows
  • Streamable HTTP is the default for new remote servers; SSE is used only for legacy compatibility
  • experimental APIs and custom serialization settings are reviewed intentionally rather than copied blindly
Files (dotnet-skills)
  • references
    • patterns.md 10.8 KB
      # MCP C# SDK Patterns
      
      Use this file when the task needs concrete current patterns from the official MCP C# SDK rather than high-level routing guidance.
      
      ## Package and Transport Matrix
      
      | Scenario | Package | Transport / API |
      |----------|---------|-----------------|
      | Minimal client or low-level host | `ModelContextProtocol.Core` | `McpClient`, low-level server APIs |
      | Typical client or stdio server | `ModelContextProtocol` | `StdioClientTransport`, `WithStdioServerTransport()` |
      | ASP.NET Core server | `ModelContextProtocol.AspNetCore` | `WithHttpTransport()`, `MapMcp()` |
      | Remote client over HTTP | `ModelContextProtocol` or `Core` | `HttpClientTransport` |
      
      ## Minimal stdio server
      
      ```csharp
      using Microsoft.Extensions.DependencyInjection;
      using Microsoft.Extensions.Hosting;
      using Microsoft.Extensions.Logging;
      using ModelContextProtocol.Server;
      using System.ComponentModel;
      
      var builder = Host.CreateApplicationBuilder(args);
      builder.Logging.AddConsole(options =>
      {
          options.LogToStandardErrorThreshold = LogLevel.Trace;
      });
      
      builder.Services
          .AddMcpServer()
          .WithStdioServerTransport()
          .WithToolsFromAssembly();
      
      await builder.Build().RunAsync();
      
      [McpServerToolType]
      public static class EchoTools
      {
          [McpServerTool, Description("Echoes the message back to the caller.")]
          public static string Echo([Description("Message to echo")] string message)
              => $"hello {message}";
      }
      ```
      
      Use `WithTools<T>()`, `WithResources<T>()`, and `WithPrompts<T>()` when you want explicit registration instead of assembly scanning.
      
      ## Minimal ASP.NET Core server
      
      ```csharp
      using ModelContextProtocol.Server;
      using System.ComponentModel;
      
      var builder = WebApplication.CreateBuilder(args);
      
      builder.Services
          .AddMcpServer()
          .WithHttpTransport()
          .WithTools<WeatherTools>()
          .WithResources<WeatherResources>()
          .WithPrompts<WeatherPrompts>();
      
      var app = builder.Build();
      app.MapMcp("/mcp");
      app.Run();
      
      [McpServerToolType]
      public static class WeatherTools
      {
          [McpServerTool, Description("Returns the current weather for a city.")]
          public static string GetCurrentWeather(
              [Description("City name")] string city)
              => $"Current weather for {city}: sunny";
      }
      ```
      
      Notes:
      
      - `MapMcp()` serves Streamable HTTP and legacy SSE endpoints.
      - New remote clients should connect to the mapped route directly and prefer Streamable HTTP.
      - Only point SSE clients to `{route}/sse`.
      
      ## Stdio client pattern
      
      ```csharp
      using ModelContextProtocol.Client;
      using ModelContextProtocol.Protocol;
      
      var transport = new StdioClientTransport(new StdioClientTransportOptions
      {
          Name = "Everything",
          Command = "npx",
          Arguments = ["-y", "@modelcontextprotocol/server-everything"],
      });
      
      await using var client = await McpClient.CreateAsync(transport);
      
      IList<McpClientTool> tools = await client.ListToolsAsync();
      
      CallToolResult result = await client.CallToolAsync(
          "echo",
          new Dictionary<string, object?> { ["message"] = "Hello MCP!" });
      
      Console.WriteLine(result.Content.OfType<TextContentBlock>().First().Text);
      ```
      
      ## HTTP client pattern
      
      ```csharp
      using ModelContextProtocol.Client;
      
      var transport = new HttpClientTransport(new HttpClientTransportOptions
      {
          Endpoint = new Uri("https://example.com/mcp"),
          TransportMode = HttpTransportMode.StreamableHttp,
          ConnectionTimeout = TimeSpan.FromSeconds(30),
          AdditionalHeaders = new Dictionary<string, string>
          {
              ["Authorization"] = "Bearer <token>"
          }
      });
      
      await using var client = await McpClient.CreateAsync(transport);
      ```
      
      For mixed environments, `HttpTransportMode.AutoDetect` is the default. It tries Streamable HTTP first and falls back to SSE when needed.
      
      ## Session resumption
      
      Use this only for Streamable HTTP sessions:
      
      ```csharp
      var transport = new HttpClientTransport(new HttpClientTransportOptions
      {
          Endpoint = new Uri("https://example.com/mcp"),
          KnownSessionId = previousSessionId
      });
      
      await using var client = await McpClient.ResumeSessionAsync(
          transport,
          new ResumeClientSessionOptions
          {
              ServerCapabilities = previousServerCapabilities,
              ServerInfo = previousServerInfo
          });
      ```
      
      ## Tool pattern
      
      ```csharp
      [McpServerToolType]
      public sealed class BuildTools(IBuildService builds)
      {
          [McpServerTool, Description("Queues a build for the requested branch.")]
          public async Task<string> QueueBuildAsync(
              [Description("Git branch to build")] string branch,
              CancellationToken cancellationToken = default)
          {
              if (string.IsNullOrWhiteSpace(branch))
              {
                  throw new McpProtocolException("Branch is required.", McpErrorCode.InvalidParams);
              }
      
              var buildId = await builds.QueueAsync(branch, cancellationToken);
              return $"queued:{buildId}";
          }
      }
      ```
      
      Guidance:
      
      - `string` results are wrapped as `TextContentBlock`.
      - Use `ImageContentBlock`, `AudioContentBlock`, or `EmbeddedResourceBlock` when the tool returns richer content.
      - Use `[Description]` on the method and parameters so hosts can build better schemas.
      - Methods can accept `McpServer`, `ClaimsPrincipal`, `IProgress<ProgressNotificationValue>`, and DI-registered services in addition to normal arguments.
      
      ## Resource pattern
      
      ```csharp
      [McpServerResourceType]
      public static class RepoResources
      {
          [McpServerResource(
              UriTemplate = "repo://readme",
              Name = "Repository README",
              MimeType = "text/markdown")]
          [Description("Returns the repository overview document.")]
          public static string ReadReadme()
              => File.ReadAllText("README.md");
      
          [McpServerResource(UriTemplate = "repo://files/{path}", Name = "Repository File")]
          [Description("Returns a file under the approved repository root.")]
          public static TextResourceContents ReadFile(string path)
          {
              var fullPath = Path.GetFullPath(Path.Combine(Environment.CurrentDirectory, path));
              var root = Path.GetFullPath(Environment.CurrentDirectory);
      
              if (!fullPath.StartsWith(root, StringComparison.Ordinal))
              {
                  throw new McpException("Requested file is outside the repository root.");
              }
      
              return new TextResourceContents
              {
                  Uri = $"repo://files/{path}",
                  MimeType = "text/plain",
                  Text = File.ReadAllText(fullPath)
              };
          }
      }
      ```
      
      Use resource templates when the URI contains parameters. Clients can enumerate them with `ListResourceTemplatesAsync()` and materialize them with `ReadResourceAsync(...)`.
      
      ## Prompt pattern
      
      ```csharp
      using Microsoft.Extensions.AI;
      using ModelContextProtocol.Protocol;
      
      [McpServerPromptType]
      public static class ReviewPrompts
      {
          [McpServerPrompt, Description("Builds a code-review prompt.")]
          public static IEnumerable<ChatMessage> CodeReview(
              [Description("Programming language")] string language,
              [Description("Code to review")] string code) =>
              [
                  new(ChatRole.User, $"Review this {language} code:\n\n```{language}\n{code}\n```")
              ];
      
          [McpServerPrompt, Description("Builds a document-review prompt with an embedded resource.")]
          public static IEnumerable<PromptMessage> ReviewDocument(
              [Description("Document identifier")] string id)
              =>
              [
                  new()
                  {
                      Role = Role.User,
                      Content = new TextContentBlock
                      {
                          Text = "Review the attached document."
                      }
                  },
                  new()
                  {
                      Role = Role.User,
                      Content = new EmbeddedResourceBlock
                      {
                          Resource = new TextResourceContents
                          {
                              Uri = $"docs://documents/{id}",
                              MimeType = "text/plain",
                              Text = LoadDocument(id)
                          }
                      }
                  }
              ];
      }
      ```
      
      Use `ChatMessage` for normal text/image flows and `PromptMessage` when you need protocol-specific content such as embedded resources.
      
      ## Capability-aware client pattern
      
      ```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);
      
      if (client.ServerCapabilities.Resources is { Subscribe: true })
      {
          await client.SubscribeToResourceAsync("repo://readme");
      }
      
      if (client.ServerCapabilities.Logging is not null)
      {
          await client.SetLoggingLevelAsync(LoggingLevel.Info);
      }
      ```
      
      Check `client.ServerCapabilities` before using:
      
      - resource subscriptions
      - prompt/resource list-change notifications
      - completions
      - logging
      - any feature that is optional in the spec
      
      ## Passing MCP tools into a chat client
      
      `McpClientTool` inherits from `AIFunction`, so discovered tools can be passed directly into `IChatClient`:
      
      ```csharp
      IList<McpClientTool> tools = await client.ListToolsAsync();
      
      IChatClient chatClient = ...;
      var response = await chatClient.GetResponseAsync(
          "Use the MCP tools to answer the question.",
          new() { Tools = [.. tools] });
      ```
      
      ## Filters for cross-cutting behavior
      
      Use filters for audit, custom JSON-RPC routing, or policy, not for normal domain logic:
      
      ```csharp
      builder.Services
          .AddMcpServer()
          .WithMessageFilters(messageFilters =>
          {
              messageFilters.AddIncomingFilter(next => async (context, cancellationToken) =>
              {
                  if (context.JsonRpcMessage is JsonRpcRequest request)
                  {
                      Console.Error.WriteLine($"Incoming MCP method: {request.Method}");
                  }
      
                  await next(context, cancellationToken);
              });
          })
          .WithRequestFilters(requestFilters =>
          {
              requestFilters.AddCallToolFilter(next => async (context, cancellationToken) =>
              {
                  Console.Error.WriteLine($"Executing tool: {context.Params?.Name}");
                  return await next(context, cancellationToken);
              });
          })
          .WithTools<WeatherTools>();
      ```
      
      ## Experimental APIs and serialization
      
      When using experimental MCP APIs:
      
      - suppress only the relevant `MCPEXP...` diagnostic ids
      - avoid blanket `NoWarn` entries for unrelated code
      - if you supply a custom `JsonSerializerContext`, prepend `McpJsonUtilities.DefaultOptions.TypeInfoResolver` so MCP protocol types continue to serialize with the SDK's contract
      
      ## Validation Checklist
      
      - client and server transport choices match the deployment topology
      - stdio servers keep stdout protocol-clean
      - HTTP endpoints are tested at the real final route
      - tool/resource/prompt descriptions are explicit
      - optional features are guarded by capability checks
      - filter usage is cross-cutting rather than replacing normal handlers
      
      static string LoadDocument(string id) => $"Document {id}";
      
    • security.md 6.4 KB
      # MCP C# SDK Security Notes
      
      Use this file when the task involves safe server/client design, auth boundaries, or data exposure rules for MCP.
      
      ## Security Priorities
      
      1. Keep the MCP transport clean and predictable.
      2. Limit what tools and resources can reach.
      3. Return safe protocol errors instead of leaking internals.
      4. Authenticate and authorize at the transport boundary.
      5. Make optional capabilities explicit.
      
      ## stdio hygiene
      
      For stdio servers, anything written to stdout can corrupt the protocol stream. Route logs to stderr:
      
      ```csharp
      builder.Logging.AddConsole(options =>
      {
          options.LogToStandardErrorThreshold = LogLevel.Trace;
      });
      ```
      
      Do not:
      
      - write banner text to stdout
      - print debug tracing with `Console.WriteLine`
      - mix app startup messaging into the MCP pipe
      
      ## Parameter validation
      
      Treat every tool/resource/prompt argument as untrusted input.
      
      ```csharp
      [McpServerToolType]
      public sealed class FileTools(IFileSystem files)
      {
          [McpServerTool, Description("Reads a text file below the approved workspace root.")]
          public async Task<string> ReadTextFileAsync(
              [Description("Relative path below the workspace root")] string relativePath,
              CancellationToken cancellationToken = default)
          {
              if (string.IsNullOrWhiteSpace(relativePath))
              {
                  throw new McpProtocolException("Path is required.", McpErrorCode.InvalidParams);
              }
      
              var root = Path.GetFullPath("/path/to/approved/workspace");
              var fullPath = Path.GetFullPath(Path.Combine(root, relativePath));
      
              if (!fullPath.StartsWith(root, StringComparison.Ordinal))
              {
                  throw new McpException("Requested path is outside the allowed workspace.");
              }
      
              return await files.File.ReadAllTextAsync(fullPath, cancellationToken);
          }
      }
      ```
      
      Patterns:
      
      - normalize paths before checking the root
      - whitelist supported operations and file types
      - clamp numeric limits and pagination inputs
      - reject blank or ambiguous identifiers early
      
      ## Error boundaries
      
      Use the right exception type for the right kind of failure:
      
      - `McpProtocolException` for JSON-RPC or contract-level failures such as invalid parameters
      - `McpException` for domain errors whose message is safe to surface
      - ordinary exceptions only for unexpected faults; they will be converted into generic tool errors
      
      This distinction matters because tool failures are exposed differently from protocol failures.
      
      ## Authorization
      
      For HTTP servers, prefer normal ASP.NET Core auth middleware and endpoint policy around `MapMcp()`:
      
      - bearer tokens, cookies, or mutual TLS belong at the HTTP boundary
      - rate limiting belongs in ASP.NET Core middleware or infrastructure, not ad hoc inside every tool
      - map unauthenticated requests to standard HTTP auth behavior before MCP handlers run
      
      Inside MCP handlers, use injected principals for per-operation authorization:
      
      ```csharp
      [McpServerToolType]
      public sealed class DeploymentTools(IDeploymentService deployments)
      {
          [McpServerTool, Description("Cancels a deployment owned by the current user.")]
          public async Task<string> CancelDeploymentAsync(
              [Description("Deployment identifier")] string deploymentId,
              ClaimsPrincipal user,
              CancellationToken cancellationToken = default)
          {
              if (!user.Identity?.IsAuthenticated ?? true)
              {
                  throw new McpException("Authentication is required.");
              }
      
              await deployments.CancelAsync(deploymentId, user, cancellationToken);
              return $"Cancelled deployment {deploymentId}.";
          }
      }
      ```
      
      ## Capability minimization
      
      Only enable features you are prepared to support safely:
      
      - roots: only if the client should disclose filesystem roots
      - sampling: only if the server should request LLM completions from the client
      - elicitation: only if the server is allowed to prompt the user for more input
      - resource subscriptions: only if you can track and notify subscribers correctly
      
      Do not assume a host/client supports these features. Capability negotiation is part of the security boundary.
      
      ## Tool and resource output discipline
      
      Keep payloads small and deliberate.
      
      Prefer:
      
      - summaries plus identifiers
      - paginated lists
      - direct resources for large text or binary data
      - explicit MIME types
      
      Avoid:
      
      - dumping whole databases or repositories into one tool result
      - returning secrets or internal stack traces
      - embedding large binary payloads when a resource URI is enough
      
      ## Remote transport guidance
      
      For remote servers:
      
      - prefer Streamable HTTP
      - use HTTPS
      - pass auth via standard HTTP headers or ASP.NET Core auth
      - use SSE only for legacy compatibility
      
      For local-only integrations:
      
      - prefer stdio
      - keep environment variables explicit
      - avoid inheriting more process privileges than the child server needs
      
      ## Filters as policy points
      
      Filters are appropriate for audit, tracing, and global policy checks:
      
      ```csharp
      builder.Services
          .AddMcpServer()
          .WithRequestFilters(filters =>
          {
              filters.AddCallToolFilter(next => async (context, cancellationToken) =>
              {
                  var toolName = context.Params?.Name;
                  if (toolName is "delete_all_data")
                  {
                      throw new McpException("This tool is disabled in the current environment.");
                  }
      
                  return await next(context, cancellationToken);
              });
          });
      ```
      
      Do not hide primary business rules in filters if the tool handler itself can express them clearly.
      
      ## Experimental APIs
      
      Experimental MCP APIs can change outside normal patch-level expectations. Before adopting them:
      
      - suppress only the specific `MCPEXP...` diagnostic you intend to accept
      - document why the suppression exists
      - isolate experimental usage behind an internal abstraction if the project needs a stable surface
      
      If you use source-generated JSON serialization, prepend `McpJsonUtilities.DefaultOptions.TypeInfoResolver` so MCP protocol types keep the SDK's serialization contract, including experimental fields when required by the wire protocol.
      
      ## Review Checklist
      
      - stdout remains protocol-clean for stdio servers
      - every externally supplied argument is validated and normalized
      - auth happens at the HTTP boundary and is rechecked inside sensitive handlers
      - sensitive operations are scoped to the caller's identity or allowed root
      - optional capabilities are enabled intentionally rather than by accident
      - tool/resource outputs exclude secrets, internal stack traces, and oversized payloads
      
  • SKILL.md 10.8 KB
    ---
    name: dotnet-mcp
    version: "1.1.1"
    category: "AI"
    description: "Build or consume Model Context Protocol (MCP) servers and clients in .NET using the official MCP C# SDK, including stdio, Streamable HTTP, tools, prompts, resources, and capability negotiation."
    compatibility: "Requires the official MCP C# SDK packages (`ModelContextProtocol.Core`, `ModelContextProtocol`, or `ModelContextProtocol.AspNetCore`) on .NET 8+; current guidance targets the v1.1.x SDK."
    ---
    
    # MCP C# SDK for .NET
    
    ## Trigger On
    
    - building or consuming MCP servers from a .NET application or library
    - choosing between stdio and HTTP transport for MCP
    - exposing tools, resources, prompts, completions, or logging to an MCP host
    - connecting a .NET app to an existing MCP server and passing discovered tools into `IChatClient`
    - bootstrapping a minimal MCP client/server from the `.NET AI` quickstarts or publishing a server to the MCP Registry
    - implementing capability-aware flows such as roots, sampling, elicitation, subscriptions, or session resumption
    
    ## Use This Skill Instead Of
    
    - Use `dotnet-mcp` when **protocol interoperability** is the requirement.
    - Use `dotnet-microsoft-extensions-ai` when you only need model/provider abstraction or local tool orchestration without the MCP wire protocol.
    - Use `dotnet-microsoft-agent-framework` when the main problem is agent orchestration; combine it with `dotnet-mcp` only when those agents must consume or expose MCP endpoints.
    - Use the `.NET AI` quickstarts for the very first vertical slice, then come back here to harden transport, capability negotiation, publishing, and host interoperability.
    
    ## Documentation
    
    - [MCP C# SDK overview](https://csharp.sdk.modelcontextprotocol.io/)
    - [Getting Started](https://csharp.sdk.modelcontextprotocol.io/concepts/getting-started.html)
    - [API reference](https://csharp.sdk.modelcontextprotocol.io/api/ModelContextProtocol.html)
    - [Conceptual docs](https://csharp.sdk.modelcontextprotocol.io/concepts/index.html)
    - [Versioning policy](https://csharp.sdk.modelcontextprotocol.io/versioning.html)
    - [Experimental APIs](https://csharp.sdk.modelcontextprotocol.io/experimental.html)
    - [MCP C# SDK repository](https://github.com/modelcontextprotocol/csharp-sdk)
    - [Model Context Protocol specification](https://modelcontextprotocol.io/specification/)
    
    ## References
    
    Load only what the task needs:
    
    - [`references/patterns.md`](references/patterns.md) - current server/client patterns, transports, capabilities, filters, and chat-client integration
    - [`references/security.md`](references/security.md) - safe error handling, auth boundaries, stdio logging hygiene, and defensive tool/resource patterns
    
    ## Package Selection
    
    | Package | Choose when |
    |---------|-------------|
    | `ModelContextProtocol.Core` | You only need a client or low-level server APIs and want the smallest dependency set. |
    | `ModelContextProtocol` | You want the main SDK package with hosting, DI, attribute discovery, and stdio server support. Start here for most projects. |
    | `ModelContextProtocol.AspNetCore` | You are hosting a remote MCP server in ASP.NET Core over HTTP. This includes the main package. |
    
    ## Transport Selection
    
    | Transport | Use when | Notes |
    |-----------|----------|-------|
    | `StdioClientTransport` / `WithStdioServerTransport()` | The MCP server should run as a local child process. | Best for local tooling and editor/agent integrations. |
    | `HttpClientTransport` + `HttpTransportMode.StreamableHttp` | The server is remote or should be reachable over HTTP. | Recommended HTTP transport; supports streaming and session resumption. |
    | `HttpTransportMode.Sse` | You must connect to an older SSE-only server. | Legacy compatibility only; do not choose this for new servers. |
    
    ```mermaid
    flowchart LR
        A["Need MCP interoperability in .NET"] --> B{"Role?"}
        B -->|"Expose MCP surface"| C{"Where will it run?"}
        B -->|"Consume an MCP server"| D{"Transport?"}
        C -->|"Local child process"| E["ModelContextProtocol\nAddMcpServer()\nWithStdioServerTransport()"]
        C -->|"Remote HTTP endpoint"| F["ModelContextProtocol.AspNetCore\nAddMcpServer()\nWithHttpTransport()\nMapMcp()"]
        D -->|"stdio"| G["StdioClientTransport\nMcpClient.CreateAsync()"]
        D -->|"HTTP"| H["HttpClientTransport\nAutoDetect or StreamableHttp"]
        E --> I["Register tools/resources/prompts"]
        F --> I
        G --> J["Check ServerCapabilities\nbefore optional features"]
        H --> J
    ```
    
    ## Workflow
    
    1. Pick the package and transport first.
       - Local child-process server: `ModelContextProtocol` + `WithStdioServerTransport()`.
       - Remote server: `ModelContextProtocol.AspNetCore` + `WithHttpTransport()` + `MapMcp()`.
       - Client-only app: start with `ModelContextProtocol` or `ModelContextProtocol.Core`.
       - Registry distribution: pair a minimal server with the MCP Registry publishing flow only after the server contract is stable.
    
    2. Model the MCP surface explicitly.
       - Tools: `[McpServerToolType]` + `[McpServerTool]`
       - Resources: `[McpServerResourceType]` + `[McpServerResource]`
       - Prompts: `[McpServerPromptType]` + `[McpServerPrompt]`
       - Use custom handlers or filters only for cross-cutting behavior, protocol extensions, or advanced routing.
    
    3. Prefer attribute discovery for straightforward servers.
    
    ```csharp
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using ModelContextProtocol.Server;
    using System.ComponentModel;
    
    var builder = Host.CreateApplicationBuilder(args);
    builder.Logging.AddConsole(options =>
    {
        options.LogToStandardErrorThreshold = LogLevel.Trace;
    });
    
    builder.Services
        .AddMcpServer()
        .WithStdioServerTransport()
        .WithToolsFromAssembly();
    
    await builder.Build().RunAsync();
    
    [McpServerToolType]
    public static class EchoTool
    {
        [McpServerTool, Description("Echoes the message back to the client.")]
        public static string Echo(string message) => $"hello {message}";
    }
    ```
    
    4. For HTTP servers, use the ASP.NET Core transport and map the endpoint directly.
    
    ```csharp
    using ModelContextProtocol.Server;
    using System.ComponentModel;
    
    var builder = WebApplication.CreateBuilder(args);
    
    builder.Services
        .AddMcpServer()
        .WithHttpTransport()
        .WithToolsFromAssembly();
    
    var app = builder.Build();
    app.MapMcp("/mcp");
    app.Run();
    
    [McpServerToolType]
    public static class EchoTool
    {
        [McpServerTool, Description("Echoes the message back to the client.")]
        public static string Echo(string message) => $"hello {message}";
    }
    ```
    
    5. When consuming a server, use `McpClient.CreateAsync(...)` and stay capability-aware.
    
    ```csharp
    using ModelContextProtocol.Client;
    using ModelContextProtocol.Protocol;
    
    var transport = new StdioClientTransport(new StdioClientTransportOptions
    {
        Name = "Everything",
        Command = "npx",
        Arguments = ["-y", "@modelcontextprotocol/server-everything"],
    });
    
    await using var client = await McpClient.CreateAsync(transport);
    
    IList<McpClientTool> tools = await client.ListToolsAsync();
    
    if (client.ServerCapabilities.Prompts is not null)
    {
        var prompts = await client.ListPromptsAsync();
    }
    ```
    
    6. Treat optional features as negotiated capabilities, not assumptions.
       - Client capabilities: configure `McpClientOptions.Capabilities` for roots, sampling, and elicitation.
       - Server capabilities are inferred from registered features.
       - Check `client.ServerCapabilities` before using completions, logging, prompt list-change notifications, or resource subscriptions.
       - Use `client.NegotiatedProtocolVersion` or `server.NegotiatedProtocolVersion` only when version-specific behavior matters.
    
    7. Keep HTTP guidance current.
       - Streamable HTTP is the recommended transport for remote servers.
       - `MapMcp()` also serves SSE compatibility endpoints for older clients.
       - HTTP clients can use `AutoDetect` by default, or force `StreamableHttp` / `Sse`.
       - Session resumption is available for Streamable HTTP through `McpClient.ResumeSessionAsync(...)`.
    
    8. Treat the `.NET AI` MCP quickstarts as bootstrap examples.
       - `build-mcp-client` and `build-mcp-server` are good starting points when the surrounding app is still MEAI-centric.
       - `publish-mcp-registry` is the distribution step, not the design step. Stabilize the protocol surface before publishing.
    
    9. Respect current error and serialization rules.
       - Tool exceptions normally come back as `CallToolResult.IsError == true`.
       - Throw `McpProtocolException` only for protocol-level JSON-RPC failures.
       - `McpClientTool` inherits from `AIFunction`, so discovered tools can be passed directly into `IChatClient`.
       - Experimental APIs use `MCPEXP...` diagnostics; suppress them intentionally, not globally by accident.
       - If you use a custom `JsonSerializerContext`, prepend `McpJsonUtilities.DefaultOptions.TypeInfoResolver` so MCP protocol types keep the SDK's contract.
    
    ## Anti-Patterns To Avoid
    
    | Anti-pattern | Why it causes trouble | Better approach |
    |--------------|-----------------------|-----------------|
    | Picking HTTP transport for a purely local child-process scenario | Adds unnecessary hosting, auth, and deployment surface | Use stdio for local/editor-hosted integrations |
    | Treating SSE as the default remote transport | Locks new work to legacy behavior | Prefer Streamable HTTP and keep SSE only for backward compatibility |
    | Writing tools without `[Description]` metadata | Hosts and models lose schema clarity | Describe tool purpose and parameters explicitly |
    | Returning huge binary/text payloads from every tool call | Bloats context and slows hosts | Return focused content and move large data to resources |
    | Logging to stdout on stdio servers | Corrupts the protocol stream | Send logs to stderr |
    | Assuming prompts/resources/logging/completions exist | Breaks against partial implementations | Check negotiated capabilities first |
    | Using filters for normal business logic | Makes handlers opaque and hard to reason about | Keep filters for cross-cutting policy, audit, or protocol plumbing |
    
    ## Deliver
    
    - a correctly packaged MCP server or client that matches the deployment topology
    - explicit tool/resource/prompt definitions with descriptions and bounded payloads
    - capability-aware handling for optional MCP features
    - validation notes for transport, auth boundary, and host/client interoperability
    
    ## Validate
    
    - chosen package matches the topology: `Core`, `ModelContextProtocol`, or `AspNetCore`
    - stdio servers do not write logs or diagnostics to stdout
    - HTTP servers use `MapMcp()` and are tested at the final route, for example `/mcp`
    - tools, resources, and prompts use current `[McpServer*]` attributes or documented handler/filter alternatives
    - client code checks `ServerCapabilities` before using subscriptions, completions, logging, or prompt/resource list-change flows
    - Streamable HTTP is the default for new remote servers; SSE is used only for legacy compatibility
    - experimental APIs and custom serialization settings are reviewed intentionally rather than copied blindly
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related