All skills
github avatar

/dotnet-mcp-builder

@aa01464 official
by githubgithub/awesome-copilot40k stars
5,040

Build Model Context Protocol (MCP) servers in C#/.NET against the current ModelContextProtocol 2.x NuGet packages. Helps with cases the model gets wrong without guidance — stale versions (0.x preview or 1.x-era defaults), the v2 stateless-by-default HTTP flip, the 2026-07-28 spec deprecations (roots/sampling/logging), MCP Apps and Tasks extension packages, elicitation URL mode, per-session HTTP wiring, OAuth and reverse-proxy deploy specifics, and debugging MapMcp / STDIO / Streamable-HTTP errors. Also covers STDIO and Streamable HTTP transports (SSE is deprecated), tools, prompts, resources, completions, and a basic .NET MCP client. Trigger when the user says or implies any .NET MCP server work: ModelContextProtocol, McpServerTool, MapMcp, WithStdioServerTransport, "MCP server in C#", "MCP tool in dotnet", "expose this as MCP", or names a primitive (prompt/resource/elicitation/MCP App) in a .NET context. Skip for MCP work in other languages.

Use this Skill: https://skilld.dev/gh/github/awesome-copilot/dotnet-mcp-builder

This session only. Nothing lands on disk.

referencesresource-primitive.md

≈1.6k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Resources

Resources are server-exposed "things" identified by a URI. Hosts list them so the user can pick which ones to attach to the conversation; tools and prompts can also reference them via EmbeddedResourceBlock. Think files, database rows, API objects, settings — anything addressable.

Two flavours:

  • Static resource — a fixed URI (config://app/settings). Useful for singletons.
  • Resource template — a URI with placeholders (docs://articles/{id}). The host (or LLM) substitutes parameters; your method receives them.

Static resource

using System.ComponentModel;
using System.Text.Json;
using ModelContextProtocol.Server;

[McpServerResourceType]
public class AppResources
{
    [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" });
}

Register:

.WithResources<AppResources>()
// or
.WithResourcesFromAssembly()

Templated resource

The placeholders in UriTemplate map by name to method parameters. Anything not a placeholder follows the same DI rules as tools (IMcpServer, CancellationToken, services).

[McpServerResourceType]
public class DocumentResources
{
    [McpServerResource(
        UriTemplate = "docs://articles/{id}",
        Name = "Article",
        MimeType = "text/markdown")]
    [Description("Returns an article by its ID.")]
    public static ResourceContents GetArticle(string id)
    {
        string? content = LoadArticle(id);
        if (content is null)
            throw new McpException($"Article not found: {id}");

        return new TextResourceContents
        {
            Uri = $"docs://articles/{id}",
            MimeType = "text/markdown",
            Text = content
        };
    }
}

Return types

Return Result
string Wrapped in TextResourceContents with the URI from the template and the declared MimeType.
byte[] Wrapped in BlobResourceContents.
TextResourceContents Returned as-is — set Uri, MimeType, Text.
BlobResourceContents Returned as-is — use BlobResourceContents.FromBytes(...).
IEnumerable<ResourceContents> Multi-part resource.

Binary resource

[McpServerResource(
    UriTemplate = "images://photos/{id}",
    Name = "Photo",
    MimeType = "image/png")]
public static BlobResourceContents GetPhoto(int id)
{
    byte[] data = LoadPhoto(id);
    return BlobResourceContents.FromBytes(data, $"images://photos/{id}", "image/png");
}

Pointing at the file system

A common pattern is exposing files from disk. Be careful about path traversal — never trust the URI verbatim.

[McpServerResource(
    UriTemplate = "file://workspace/{*relativePath}",
    Name = "Workspace file")]
public static TextResourceContents ReadFile(string relativePath, IOptions<WorkspaceOptions> opts)
{
    var root = opts.Value.RootPath;
    var fullPath = Path.GetFullPath(Path.Combine(root, relativePath));
    if (!fullPath.StartsWith(root, StringComparison.Ordinal))
        throw new McpException("Path traversal blocked.");

    return new TextResourceContents
    {
        Uri = $"file://workspace/{relativePath.Replace("\\", "/")}",
        MimeType = "text/plain",
        Text = File.ReadAllText(fullPath)
    };
}

Listing dynamic resources

Attribute-based discovery covers the common case (one method per template). When you need to enumerate resources that don't fit a template — say, "list every file in the workspace" — implement a low-level handler in McpServerOptions.Capabilities.Resources:

builder.Services.Configure<McpServerOptions>(options =>
{
    options.Capabilities ??= new();
    options.Capabilities.Resources ??= new();

    options.Capabilities.Resources.ListResourcesHandler = (ctx, ct) =>
    {
        var resources = Directory
            .EnumerateFiles(WorkspaceRoot, "*.*", SearchOption.AllDirectories)
            .Select(path => new Resource
            {
                Uri = "file://workspace/" + Path.GetRelativePath(WorkspaceRoot, path).Replace('\\', '/'),
                Name = Path.GetFileName(path),
                MimeType = "text/plain"
            })
            .ToList();

        return ValueTask.FromResult(new ListResourcesResult { Resources = resources });
    };
});

You can mix attribute-based and handler-based — the SDK merges both.

Resource subscriptions (server-pushed updates)

If a client subscribes to a resource and it changes, push a notification:

await server.SendNotificationAsync(
    NotificationMethods.ResourceUpdatedNotification,
    new ResourceUpdatedNotificationParams { Uri = "docs://articles/42" },
    cancellationToken);

For wholesale list changes:

await server.SendNotificationAsync(
    NotificationMethods.ResourceListChangedNotification,
    new ResourceListChangedNotificationParams(),
    cancellationToken);

Both require a stateful transport.

Reading resources from a client

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)
        File.WriteAllBytes("out.bin", blob.DecodedData.ToArray());
}

Resources vs. tools — when to pick which

  • Resource: the user (or LLM) wants to attach context to the conversation. Read-only, addressable, listable. The host controls when/whether to load it. Ideal for documents, configs, schemas.
  • Tool: the LLM wants to do something (which may include reading data). Side-effects, actions, parameters that don't fit a URI.

If you have something the LLM might want to search over, expose both: a search_articles tool and docs://articles/{id} resource template. The tool returns a list of URIs; the host fetches the content via the resource.

Source: SKILL.md on GitHub

No alerts15d3 checks · Risk SAFE
  • Gen Agent Trust Hub15d

    The skill provides technical guidance and code references for building Model Context Protocol (MCP) servers and clients using the official C#/.NET SDK. It includes security best practices for resource access and follows standard development patterns.

  • Socket15d

    No alerts

  • Snyk15d

    Risk: LOW · No issues

Signed by skilld at aa01464. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 19 hours ago.

Activeupdated 2 months ago
  • MCP
  • dotnet
  • csharp
  • model-context-protocol
  • stdio
  • http
  • tools
  • prompts
  • resources
  • streaming

README badge

README badge for github/awesome-copilot/dotnet-mcp-builder

Builds Model Context Protocol servers in C# / .NET against the stable 1.x SDK, covering STDIO and HTTP transports, tools, prompts, resources, sampling, elicitation, MCP Apps, and debugging. Targets the specific pitfalls that cause breakage: stale preview package versions, stdout pollution in STDIO mode, stateless HTTP misconfiguration, and missing primitive registration in DI.

Generated from the current SKILL.md.

Does this skill work with preview versions of the ModelContextProtocol NuGet packages?
No. The skill targets stable 1.x packages only. Preview versions (0.3, 0.4) have breaking differences and won't compile against current samples. Always pin the latest 1.x release.
Can I use stateless HTTP transport with sampling, elicitation, or server-initiated notifications?
No. Stateless HTTP breaks those features at runtime because it cannot maintain bidirectional communication. Use stateful HTTP or STDIO if you need server-to-client capabilities.
What should I do if my STDIO server isn't working?
First check that nothing is writing to stdout — configure `LogToStandardErrorThreshold = LogLevel.Trace` and remove any `Console.WriteLine` calls, since stdout is the JSON-RPC channel. Also verify tools and prompts are registered with `.WithToolsFromAssembly()` or equivalent.
Does this skill cover building MCP servers in other languages like Python or TypeScript?
No. This skill is C#/.NET only. It skips MCP work in other languages.
Can I write a .NET program that consumes an MCP server instead of building one?
Yes. Load `references/client.md` for guidance on writing a basic .NET MCP client that calls an existing server.

Generated from the current SKILL.md. These answers refresh after source changes.