BrandGhost
MCP Resources and Prompts in C#: Exposing Data and Reusable Templates

MCP Resources and Prompts in C#: Exposing Data and Reusable Templates

If you're already looking beyond callable tools, MCP resources in C# give you a way to expose readable context, while MCP prompts let you publish reusable prompt templates that clients can discover. That distinction matters. A tool asks the model to do something. A resource gives the application context to read. A prompt gives the user a repeatable workflow to start from.

This article is a MOFU implementation guide for the two MCP server primitives that are not tools: resources and prompts. We will stay focused on how to model them in C# with the official SDK, when to choose each one, and how clients discover them. As of July 2026, NuGet lists latest stable version 1.4.0 for ModelContextProtocol, ModelContextProtocol.Core, and ModelContextProtocol.AspNetCore, with 2.0.0-preview.1 available separately as a prerelease. Check NuGet before you pin a version because the SDK is moving quickly.

Why MCP Resources in C# Deserve Their Own Design Pass

The official MCP server concepts describe resources as passive data sources. That is the phrase I keep coming back to when deciding whether something belongs behind [McpServerResource]. Resources are for data the client can list, select, read, and pass into a model as context.

That makes MCP resources in C# a better fit than tools when the operation is naturally read-oriented. Think about configuration snapshots, documentation pages, policy documents, build metadata, database schema summaries, or generated reports. The client is not asking the server to take action on the user's behalf. It is asking the server to expose data through a URI-shaped contract.

This is also where MCP can feel familiar to .NET developers. A resource URI acts like a stable identifier. The handler can still use your normal application services behind the scenes, but the MCP contract remains about reading content. If your server needs dependency injection patterns to load that content, the underlying principles from how dependency injection containers use reflection internally in C# still apply.

The tradeoff is that resources are less action-oriented than tools. That is good when you want predictable context. It is limiting when the model needs to request work, perform a search, update a system, or trigger a workflow.

Direct MCP Resources in C# With [McpServerResource]

The official C# SDK docs show the common attribute path for MCP resources in C#: mark a class with [McpServerResourceType], then mark methods with [McpServerResource]. The API reference confirms McpServerResourceAttribute applies to methods and exposes properties such as UriTemplate, Name, Title, MimeType, and IconSource.

A direct resource has a fixed URI. It appears in the direct resource list and can be read by URI. This is the shape I would use for stable documents or singleton data where the identifier is known ahead of time.

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

[McpServerResourceType]
public sealed class ApplicationResources
{
    [McpServerResource(
        UriTemplate = "config://app/settings",
        Name = "App Settings",
        MimeType = "application/json")]
    [Description("Returns the current application settings visible to the MCP client.")]
    public static TextResourceContents GetSettings()
    {
        var json = JsonSerializer.Serialize(new
        {
            Theme = "dark",
            Language = "en",
            FeatureFlags = new[] { "resources", "prompts" }
        });

        return new TextResourceContents
        {
            Uri = "config://app/settings",
            MimeType = "application/json",
            Text = json
        };
    }
}

This example returns TextResourceContents from ModelContextProtocol.Protocol. The resource has a URI, a MIME type, and text. You could return a plain string for simple cases, but I prefer returning TextResourceContents when the URI and MIME type are part of the contract. It makes the intent explicit and helps the client handle the content appropriately.

For MCP resources in C#, the key design question is not "can I expose this?" It is "will a client understand this as context?" A settings document, a schema file, or a markdown guide makes sense. A command such as "refresh cache" does not.

Template MCP Resources in C#: URI Templates for Parameterized Data

Template resources use URI templates instead of one fixed URI. The C# SDK resource docs describe template resources as RFC 6570 URI templates with parameters, and the examples use [McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")]. These template resources are returned separately from direct resources, so clients can discover that a family of readable URIs exists.

Use template MCP resources in C# when there are many possible resources with the same shape. Articles by ID. Customer profiles by account number. Run logs by build ID. The URI gives the client a consistent way to identify what it wants without turning the operation into a tool call.

using ModelContextProtocol;
using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

[McpServerResourceType]
public sealed class DocumentationResources
{
    [McpServerResource(UriTemplate = "docs://articles/{id}", Name = "Article")]
    [Description("Returns an article by ID as markdown.")]
    public static ResourceContents GetArticle(
        [Description("The article identifier to read.")] string id)
    {
        var article = LoadArticle(id);

        if (article is null)
        {
            throw new McpException($"Article not found: {id}");
        }

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

    private static string? LoadArticle(string id) =>
        id == "resources"
            ? "# MCP Resources

Resources expose readable context."
            : null;
}

Notice the important modeling point: this still reads content. It is not a search endpoint disguised as a resource. If the model needs to search across a corpus with ranking, filters, and paging, a tool might be a better contract. If the application or user already knows the URI-shaped identity, a resource template is a good fit.

This is similar to the judgment you make with APIs and clients. The contract shape affects how people use it. If you are also designing HTTP integrations around MCP servers, the patterns in HttpClient in C#: the complete guide for .NET developers are a useful reminder that names, boundaries, and response shapes matter.

Text vs Binary MCP Resources in C#

MCP resources in C# can return text or binary content. The SDK docs show TextResourceContents for text resources and BlobResourceContents for binary resources. Use text resources for markdown, JSON, plain text, source files, and other content you expect a model or client to inspect directly.

Binary resources are different. They are appropriate when the client needs the bytes and the MIME type is meaningful, such as an image, PDF, archive, or audio file. The SDK docs show BlobResourceContents.FromBytes(...) for this path. The tradeoff is payload size and client support. A text resource can often be summarized, chunked, or rendered easily. A binary resource depends on whether the client knows what to do with the MIME type. For many agent workflows, I would start with text resources unless the binary artifact itself is the important context.

How Clients List and Read MCP Resources in C#

Clients can list direct resources, list resource templates, and read resources. The official SDK docs and McpClient API reference show ListResourcesAsync(), ListResourceTemplatesAsync(), and ReadResourceAsync(...). This is not meant to be a deep client-side guide, but server authors should understand the experience they are designing for.

using System.Collections.Generic;
using ModelContextProtocol.Client;
using ModelContextProtocol.Protocol;

IList<McpClientResource> resources = await client.ListResourcesAsync();
IList<McpClientResourceTemplate> templates = await client.ListResourceTemplatesAsync();

foreach (var resource in resources)
{
    Console.WriteLine($"{resource.Name}: {resource.Uri}");
}

foreach (var template in templates)
{
    Console.WriteLine($"{template.Name}: {template.UriTemplate}");
}

ReadResourceResult articleResult = await client.ReadResourceAsync(
    "docs://articles/{id}",
    new Dictionary<string, object?> { ["id"] = "resources" });

ReadResourceResult result = await client.ReadResourceAsync("config://app/settings");

foreach (var content in result.Contents)
{
    if (content is TextResourceContents text)
    {
        Console.WriteLine(text.Text);
    }
    else if (content is BlobResourceContents blob)
    {
        Console.WriteLine($"{blob.MimeType}: {blob.DecodedData.Length} bytes");
    }
}

For template resources, pass the URI template plus an argument dictionary so the client can format the concrete URI before reading it. This discovery flow is why descriptions and MIME types matter. The client may show resources in a picker, command palette, or contextual UI. If your names are vague, the client experience becomes vague too.

If your MCP resources in C# expose data from HTTP services, databases, or file systems, think about observability early. Resource reads can become a high-volume path. The practices in logging in .NET apply here: log enough to diagnose failures, but avoid leaking sensitive resource content into logs.

Resource Subscriptions and Change Notifications

Resources can also support updates. The SDK docs describe resource subscriptions, ResourceUpdatedNotification, and ResourceListChangedNotification. The design catch is important: unsolicited notifications require stateful mode or stdio. Stateless HTTP servers cannot push unsolicited notifications.

Use resource subscriptions when a client needs to know that a previously selected resource changed. Configuration, live run status, a generated report, or a document being edited can all fit. Do not add subscriptions just because they exist. They introduce lifecycle and state management concerns.

The C# SDK docs show subscription handlers registered with WithSubscribeToResourcesHandler(...) and WithUnsubscribeFromResourcesHandler(...). When a specific resource changes, the server can call SendNotificationAsync(NotificationMethods.ResourceUpdatedNotification, ...). When the available set changes, it can send NotificationMethods.ResourceListChangedNotification.

The pros are obvious: clients can refresh context without repeatedly polling. The cons are also real: you now need to track sessions, subscribed URIs, disconnected clients, and notification behavior. If your data changes rarely, a simple read-on-demand resource may be enough.

MCP Prompts .NET Teams Can Reuse With [McpServerPrompt]

MCP prompts are the other underused primitive. The official C# prompt docs describe prompts as reusable prompt templates that return structured messages. In C#, the attribute path is [McpServerPromptType] on a class and [McpServerPrompt] on methods.

Prompts are not tools. A prompt does not execute a business operation. It gives the user or client a reusable conversation starter, usually with arguments. That makes MCP prompts .NET teams publish useful for standardized reviews, troubleshooting flows, migration checklists, or domain-specific analysis.

using Microsoft.Extensions.AI;
using ModelContextProtocol.Server;
using System.ComponentModel;

[McpServerPromptType]
public sealed class ReviewPrompts
{
    [McpServerPrompt]
    [Description("Creates a focused code review prompt for a C# snippet.")]
    public static IEnumerable<ChatMessage> CodeReview(
        [Description("The specific review focus, such as correctness or API design.")] string focus,
        [Description("The C# code to review.")] string code) =>
        [
            new(ChatRole.User, $"""
                Review this C# code with a focus on {focus}.

                ~~~csharp
                {code}
                ~~~
                """),
            new(ChatRole.Assistant, "I'll review the code and call out concrete issues first.")
        ];
}

The SDK docs say prompts can return ChatMessage instances for simple text and image content, or PromptMessage when you need protocol-specific content types such as embedded resources. For most reusable prompt templates, IEnumerable<ChatMessage> is a practical starting point.

This is also where MCP prompts connect nicely to agent workflows. If you have been looking at AIAgent and ChatClientAgent core abstractions in Microsoft Agent Framework, prompts can become a reusable boundary around the instructions you want humans to invoke consistently.

Prompt Arguments and Embedded Resource Messages

Prompt arguments should be explicit and small. The docs show using [Description] attributes on prompt parameters, and the GetPromptAsync API accepts an arguments dictionary keyed by parameter name. Special parameters such as McpServer, progress, claims, cancellation, and DI services can be resolved automatically.

When a prompt should include resource-like context, you can return protocol-specific PromptMessage objects and include an EmbeddedResourceBlock. This is useful when the prompt should package both instructions and a document.

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;

[McpServerPromptType]
public sealed class DocumentPrompts
{
    [McpServerPrompt]
    [Description("Creates a prompt for reviewing a policy document.")]
    public static IEnumerable<PromptMessage> ReviewPolicy(
        [Description("The policy document identifier.")] string documentId)
    {
        var markdown = LoadPolicy(documentId);

        return
        [
            new PromptMessage
            {
                Role = Role.User,
                Content = new TextContentBlock { Text = "Review this policy for ambiguity and missing examples." }
            },
            new PromptMessage
            {
                Role = Role.User,
                Content = new EmbeddedResourceBlock
                {
                    Resource = new TextResourceContents
                    {
                        Uri = $"policies://documents/{documentId}",
                        MimeType = "text/markdown",
                        Text = markdown
                    }
                }
            }
        ];
    }

    private static string LoadPolicy(string documentId) =>
        $"# Policy {documentId}

All production changes require review.";
}

This is not a replacement for a resource. It is a prompt that embeds resource content into a specific reusable workflow. If the client should browse and choose the policy independently, model it as a resource. If the workflow is "review this policy using these instructions," a prompt can be the better outer contract.

When a Reusable Prompt Is Better Than a Tool

A reusable prompt is better than a tool when the output is a set of instructions or messages, not the result of a server-side operation. That is the simplest test.

Use MCP prompts .NET teams can discover when you want consistent workflows. A security review prompt, an incident summary prompt, or a "turn this support transcript into release notes" prompt can all encode a repeatable structure. The user chooses it. The prompt returns messages. The model then works from that conversation context.

Use a tool when the server must perform an operation: query a service, send a request, calculate a result, mutate state, or retrieve ranked results. Tools are model-controlled in the MCP mental model, so they need stronger safety thinking. If your tool can cause side effects, consider approval patterns like those discussed in tool approval and human-in-the-loop in Microsoft Agent Framework.

Use a resource when the server is exposing readable data and the client decides how to include it. This is where MCP resources in C# shine. A prompt can refer to resources, but it should not become a dumping ground for every document your server knows about.

Resource vs Tool vs Prompt: A Decision Framework

Here is the practical framework I use:

If the capability reads stable or addressable data, start with a resource. The client can list it, read it, and decide how much context to include. Direct resources fit fixed URIs. Template resources fit URI families with parameters.

If the capability performs work, start with a tool. That includes searches with ranking, calculations, API calls, file changes, database writes, or anything that has operational behavior. Keep the tool focused, but do not force action into a resource just to avoid designing a tool.

If the capability standardizes how a user starts a workflow, start with a prompt. Prompts are excellent for reusable instructions with arguments. They are less appropriate for hidden business logic because the client expects messages, not an operation.

The edge cases are where design matters. A "customer profile by ID" is probably a resource. "Find customers at risk" is probably a tool because it implies query logic and ranking. "Analyze this customer account for renewal risk" might be a prompt if the user provides or selects the account context first. If you are building broader agent systems, MCP Tool Integration in Microsoft Agent Framework in C# is a useful companion for the action side of that decision.

Common Implementation Mistakes With MCP Resources in C#

The first mistake is modeling everything as a tool. Tools get attention, but known content often reads better as MCP resources in C#. The second mistake is modeling dynamic search as a resource template. If a user enters arbitrary filters, sorting rules, and result counts, a tool contract is usually more honest.

The third mistake is forgetting that prompts are user-facing workflows. A prompt with vague arguments and generic messages is hard to discover and hard to trust. The fourth mistake is ignoring notifications until after clients need them. Resource subscriptions, resource list changes, and prompt list changes are powerful, but they require the right server mode.

FAQ

What are MCP resources in C# used for?

MCP resources in C# are used to expose readable data from a server to an MCP client. They are a good fit for documents, configuration, generated reports, schemas, records, and other content the client may include as context. They are not meant for side effects.

What is [McpServerResource] in the C# SDK?

[McpServerResource] is the method-level attribute used by the official C# SDK to expose a method as an MCP resource. It is typically used inside a class marked with [McpServerResourceType]. The attribute supports properties such as UriTemplate, Name, Title, MimeType, and IconSource.

What are MCP prompts .NET developers should model separately?

MCP prompts .NET developers expose through [McpServerPrompt] are reusable prompt templates. They return chat messages or protocol prompt messages, often with arguments. Use them for repeatable workflows such as code review, incident summaries, document review, or migration guidance.

How is [McpServerPrompt] different from [McpServerTool]?

[McpServerPrompt] returns reusable messages that guide a conversation. [McpServerTool] exposes a callable operation. If the server is doing work, use a tool. If the server is giving the user a reusable instruction template, use a prompt.

Should I use a direct resource or a template resource?

Use a direct resource when the URI is fixed, such as config://app/settings. Use a template resource when the URI has RFC 6570-style parameters, such as docs://articles/{id}. Both are still resource contracts, so the operation should be about reading content.

Do MCP resource subscriptions work with stateless HTTP?

The SDK docs say unsolicited notifications require stateful mode or stdio. Resource subscriptions and list-changed notifications are push-style behavior, so a stateless HTTP server cannot send those unsolicited updates. If subscriptions matter, account for that mode decision explicitly.

Final Thoughts on MCP Resources in C# and Prompts

MCP resources in C# and MCP prompts solve different problems than tools. Resources expose readable context through direct or template URIs. Prompts expose reusable workflows through returned messages and arguments. Tools perform operations.

That separation is what makes an MCP server easier for clients to reason about. Use resources for data, prompts for reusable instructions, and tools for actions. The result is not just a technically valid server. It is a server whose capabilities are easier for humans, clients, and models to understand.

Building MCP Servers and Clients in C#: The Complete Guide

Learn how to build an MCP server C# developers can trust, from stdio and HTTP transports to tools, clients, security, Azure hosting, and debugging tips.

MCP Tool Integration in Microsoft Agent Framework in C#

MCP tool integration in Microsoft Agent Framework in C# using AIFunctionFactory and ChatClientAgent -- real working code with filesystem tools.

Model Context Protocol (MCP) Explained for C# and .NET Developers

Model Context Protocol explained for C# and .NET developers: what MCP is, why it matters, and how hosts, clients, servers, tools, resources, and prompts fit.

An error has occurred. This application may no longer respond until reloaded. Reload