Sr. Content Developer at Microsoft, working remotely in PA, TechBash conference organizer, former Microsoft MVP, Husband, Dad and Geek.
148943 stories
·
33 followers

Coding Azure 22: Adding Configuration Information to an App Configuration Service

1 Share

With the App Configuration service we built last time, we can now apply settings and configuration information. Here’s a look at how to do that and what the options are.

In my last post, Coding Azure 21: Managing Configuration Information with App Configuration, as part of creating the infrastructure to share configuration data among the components of a distributed application, I walked through creating an App Configuration service.

This is the post where I’ll describe the varieties of settings you can add and why you should pick any one of them. My next post will describe how to access those settings from your application code.

While I’ve positioned App Configuration as infrastructure for distributed applications, this post will show that an App Configuration service is worth considering as your primary source for configuration information. And the conversion doesn’t have to be difficult: If you have settings in an App Service’s Environment variables that you would like to add to your App Configuration service, you can use the Import/Export page under the Operations node in the menu on the left of your service to copy those settings in your service.

In addition, because of the way that App Configuration settings are accessed from your application, you may only have to tweak some code in your application’s Program.cs file to have your application use its settings from an App Configuration service (see my next post).

Adding Settings

First, of course, surf to your App Configuration service and then, in the menu on the left, expand the Operations node and click on the Configuration explorer node to open a page on the right. To add a setting, click on the +Create choice at the left end of the top menu on the Configuration explorer page.

When adding a setting, you must set its Key property and, optionally, its Label property. Those two values, together, uniquely identify a setting. You can have multiple settings with the same value for their Key property, provided all those settings have different values in their Label property. This enables you to have multiple settings with a Key property set to “A/R Queue Name,” but with different Labels—“dev,” “test” and “production.” In your application code, you can choose which Keys you want based on their labels.

Some advice on Labels: They cannot include asterisks (*) or commas (,) and, if your Label includes a backslash (/), you must insert the backslash twice. It’s also a good idea for your Labels to match any environment names that you’re using in your application to signal whether your application is running in production or test or development (and, be aware, Label names are case-sensitive when you use them in your code to retrieve a value).

It’s also a good idea to establish a naming convention for your Keys that will gather together settings that your application will retrieve as a group. The recommended best practice is to use some delimiter (typically a colon, “:”) to separate out the levels of grouping for your keys. A typical set of Key values might look like this:

SalesOrder:Invoicing:WebServiceUrl
SalesOrder:Invoicing:AuthorizationKey
SalesOrder:Products:WebServiceURL

If this naming convention looks suspiciously like the syntax used to retrieve values from an appsettings.json file in an ASP.NET Core application … well, that’s not by accident (as I’ll cover in my next post). This naming convention lets you treat separate settings in your service like sections in an appsettings file. In your application, you could, for example, retrieve all three settings by asking for a section called “SalesOrder”.

You can add one of four kinds of settings:

  • Key Value pair
  • Key Vault reference
  • Snapshot reference
  • AI configuration

Key Value Pairs

This is the default choice for an App Configuration setting—a value associated with a Key and an optional Label. If you select this option, a panel will open on the right that will let you specify, in addition to a Key and Label, a:

  • Value: This can be anything from a single digit to a JSON document
  • Content type: A hint to the application about how to parse the value in your setting.

If you put a JSON document in the Value, you should set Content type to application/json to support the default tools you’ll use when reading your settings.

Other than that, the Content type setting doesn’t make any difference to how a Key Value setting is processed unless you’re taking advantage of App Configuration’s support for Features Flags (and Content Type can’t even be retrieved in your application using the default tools).

Adding a setting with JSON in its Value can simplify moving settings from an appsettings.json file to your App Configuration service.

Key Vault References

This setting holds the information to access secret in one of your Key Vaults. In addition to preventing sensitive information from being stored in your App Configuration service, this choice allows you to split responsibility between the person making the secret available to your application (in the App Configuration service) and the person managing the secret (in the Key Vault).

In your application, the default tools you’ll use in your application for retrieving settings will access the Key Vault to retrieve the secret. This means that only your application will need permission to access the Key Vault (i.e., not your App Configuration service).

It will simplify the code your application needs to access values in the Key Vault if you use only one Key Vault from any App Configuration service. If that’s not possible, you can avoid complicating your code if your application can use the same set of credentials for all the Key Vaults used in your service (e.g., if one Managed Identity can access all the vaults).

In addition to specifying a Key and a Label for this setting, you have two tabs for specifying how your secret will be retrieved: Browse and Input.

If you pick the Browse option, you’ll need to provide:

  • The subscription, resource group and name of the Key Vault is assigned to
  • The key for the secret in the Key Vault
  • The version of the secret

If you leave the version dropdown list blank or select Latest version from the dropdown list, you’ll always retrieve the latest value for the key (probably what you want). If you explicitly select the current version of the key from the dropdown list, then you’ll always get that version (i.e., if the secret is updated with a new version, your application will not get that new, “latest” version).

Using the Browse option, however, does require that you have the required permissions to browse the Key Vault. If you can’t browse the key vault, you’ll have to use the Input option.

With the Input option, you enter the URL that leads to the secret. Since that URL contains the vault name, the secret name and the version you want, it’s not completely opaque to the next developer who will be working with your service.

AI Configuration

This setting type allows you to store configuration settings for a large language model (e.g., the number of tokens the model allocates for processing).

App Snapshot Reference and Snapshots

A snapshot reference points to an App Configuration snapshot, which is a collection of settings with their values at the time the snapshot was created. Snapshots can be useful in, for example, getting a “frozen” view of your settings before a rollout or to capture the “last known good” state of some settings.

The values in the snapshot’s collection of settings are read-only/immutable: If you change the values in a setting that’s part of a snapshot, those changes are not reflected in the snapshot. The snapshots themselves cannot be deleted (they can, however, be archived and—eventually—will be purged).

Creating a Snapshot

To create a snapshot, in the menu on the left of your service, under the Operations node, select Snapshots to open a page on the right. Then click on the +Create menu choice at the left end of the menu across the top of the page to open the Create a snapshot page.

On the Create a snapshot page, give your snapshot a:

  • Name: You’ll use this name when adding a Snapshot Reference to your settings in Configuration Explorer
  • Filter: You must add at least one filter (but no more than three) to select the settings that will be added to the snapshot.
  • Composition Type: Controls how the filters are used to select settings.
  • Recovery Options: This allows you to specify how long a snapshot is kept after it is archived (this is separate from the soft delete option you set when you created your service). The maximum length of time is controlled by the pricing plan for your service.
  • Tags: You can associate tags with your snapshot either by adding them directly using the Click to add tags link at the top of the page or conditionally by associating a tag with a filter.

Selecting Snapshot Settings

Adding a filter defines the criteria to be used to select the settings to be added to your snapshot. You can specify up to three filters, with each filter being a combination of Key and Label values:

  • Key: When setting the Key value in a filter you have four choices:
    » Key values that are an exact match to the text you enter in the criteria
    » Key values that start with the text you enter
    » Key values that match a list of comma-delimited text you enter
    » All Key values
  • Labels: When setting the Label value, the only criteria you can pick is an exact match to the text you enter

As an example: If you wanted to create a snapshot containing all the settings labeled “production,” you would create a filter with the Key criteria set to “All” and Label criteria set to “production.”

The snapshot’s Composition type option controls how many different versions for any Key value will be added to the snapshot.

If the Composition type is set to “Key” (the default), then only one setting for any Key value will be added to the snapshot (this also simplifies using the snapshot in your application). If there are multiple settings with the same Key value, then only the setting that matches the rest of the criteria in the last filter in the snapshot for that Key will be added to the snapshot.

This means, for example, that if you have two filters the same criteria for a Key (e.g., “QueueName”) and the Label criteria for the last of those two filters is that the Label must be set to “dev,” then only one setting will be added: the setting with its Key set to “QueueName” and its Label set to “dev.” The Label criteria on the first filter is irrelevant.

If you set the composition type to “Key-Label” then you can have multiple settings with the same Key in the snapshot, each with a different label. Be aware that this will make it more difficult to use the snapshot in your application.

You can also set how long, in days, a snapshot will be kept after you archive it and before it is purged (the upper limit is set by your App Configuration’s pricing plan). You cannot create a new snapshot with the same name as either an existing or archived snapshot (and, by the way, even if you set the archive time to zero, your snapshot is still kept for one hour after you archive it).

After you’ve defined your snapshot, click on the Create button at the bottom of the page. The snapshot will be created and the settings selected in your filters with their current values will be added to the snapshot.

You can view your list of snapshots available from the Snapshots node in the menu on the left of your service. In that list, you can use the View choice from the menu at the right end of the snapshot to see what settings were captured in your snapshot and what values were saved.

Creating a Snapshot Reference

You can now return to Configuration explorer and create a Snapshot reference setting that uses your snapshot. You’ll be able to select your Snapshot from a dropdown list.

Securing the Service to Your Application

Your best choice for controlling access to your service from your application is to assign the application component that will be accessing your service a Managed Identity through the App Service that’s hosting that application component.

For your application or Web Service to read settings, you need to assign it the Managed Identity Data Reader role:

  1. In the menu on the left, click on Access control (IAM) to open a page on the right.
  2. On that page, click on the Add role assignment button to start the wizard.
  3. In the textbox enter App Configuration Data Reader and click on the role when it appears to select it.
  4. Click the Next button at the bottom of the page.
  5. On the next page of the wizard, select the Managed identity radio button.
  6. Click the +Select members link to open the Select managed identities panel on the right.
  7. From the Subscription dropdown list, select the subscription for the Managed Identity your application will be using.
  8. From the Managed Identity textbox, select User-assigned managed identity.
  9. From the list of Managed Identities, select the identity your application will be using.
  10. Click the Select button at the bottom of the page to close the panel and return to the wizard.
  11. Click the Review + assign button at the bottom of the page to assign that Managed Identity to the role.
  12. Click the Review + assign button at the bottom of the page to give that Managed Identity access to your App Configuration service and return to the Access Control (IAM) page.

Now that you’ve created an App Configuration service and loaded it with settings, you’ll want to read those settings from your application. That’s my next post. Stay tuned.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Model Context Protocol (MCP): A New Standard for AI Integrations in .NET

1 Share

Model Context Protocol C# SDK: From Custom Integrations to Standardized AI Workflows

TL;DR: Model Context Protocol (MCP) introduces an open standard that allows LLMs to interact with external tools and data sources. With the new C# SDK, .NET developers can build interoperable AI workflows without relying on fragile custom integrations. This guide explains MCP’s architecture and demonstrates simple tool interactions, showing how MCP enables portable, reliable, and future-proof AI integrations.

In the rapidly evolving world of AI integration, developers want a reliable way to connect large language models (LLMs) with external tools and data. One emerging standard gaining traction is the Model Context Protocol (MCP).

In this blog post, you’ll learn what MCP is, how it’s structured (Host/Client/Server), and how to build a small MCP server using the Model Context Protocol C# SDK, including elicitation for interactive user input and structured outputs for predictable results. We’ll also compare MCP to traditional APIs and discuss where MCP fits compared to other AI SDK patterns.

What is MCP?

The Model Context Protocol (MCP) is an open protocol developed by Anthropic to connect AI applications (often called Hosts) with external tools and data sources via MCP servers. Released in November 2024 and updated with features such as streaming, MCP enables LLMs to access external resources as part of their context, so they can perform more complex, context-aware tasks.

MCP uses standardized message types (e.g., InitializeRequest, ListToolsRequest, and CallToolRequest) to handle communication between:

  • MCP clients (on the AI/host side), and
  • MCP servers (the tool-providing side).

You’ll often hear MCP described as a “USB-C for AI integrations”; it standardizes how models connect to diverse apps and data in a consistent, language-agnostic way.

MCP core components (Host, Client, Server)

To understand MCP better, here are the core pieces:

  • MCP Hosts: AI applications that use MCP to connect to external tools and data sources. Examples include IDEs like Visual Studio Code or other environments with AI features. The host is the “hub” that brings external capabilities into the model’s workflow.
  • MCP Clients: Embedded within the Host, these are the components that implement the MCP protocol. They send requests (e.g., initialize a connection or invoke a tool) to MCP servers and process responses. For example, GitHub Copilot can act as an MCP client within a host like VS Code.
  • MCP Servers: External services that provide tools, resources, and prompts. They receive requests from MCP clients via the protocol, process them (e.g., accessing local databases or web APIs), and return responses. Servers can be local or remote, enabling flexible integration.

Walkthrough: How MCP systems interact

The Model Context Protocol (MCP) enables seamless communication between clients and servers through a structured set of standardized message types. These include InitializeRequest for establishing connections, ListToolsRequest for discovering server-provided tools, CallToolRequest for invoking those tools, and additional message types such as ReadResourceRequest for accessing data. This design promotes flexibility, modularity, and extensibility, supported by a rapidly growing ecosystem of community-built MCP servers.

For developers integrating AI into applications, such as using .NET with the MCP C# SDK, creating a custom MCP server provides a clean way to expose reusable, AI-friendly capabilities. Instead of rebuilding logic across multiple projects, you can encapsulate functionality once and make it accessible to any MCP-compatible client, significantly boosting productivity.

The diagram below illustrates how different components within MCP interact across local and remote environments:

System interactions within MCP
System interactions within MCP

Note: For more information, you can check the official MCP documentation

What is Model Context Protocol C# SDK?

The Model Context Protocol C# SDK is an official, open-source toolkit developed through a partnership between Microsoft and Anthropic to simplify building MCP servers in C#. Available as a NuGet package (ModelContextProtocol), it helps .NET developers create MCP-compatible servers that expose tools, prompts, and resources to AI models.

It also supports capabilities such as:

  • Authentication (where applicable in host/server setups).
  • Elicitation (ask the user for input mid-execution).
  • Structured tool outputs.
  • Resource links in results.

This SDK builds on .NET’s performance features and is hosted on GitHub under the modelcontextprotocol organization. It integrates with Microsoft products like Copilot Studio, Visual Studio Code’s GitHub Copilot agent mode, and Semantic Kernel, making it easier to embed AI capabilities into C# applications.

Key features of MCP C# SDK

Here are the key features that make this approach efficient and easy to integrate:

McpServerToolType

This attribute marks a class that contains methods exposed as tools. It’s essential for organizing tool implementations, and the SDK scans these classes to register tools automatically.

Here’s the sample MCP Tool implementation:

[McpServerToolType]
public static class UtilityTools
{
    [McpServerTool, Description("Checks if the given year is a leap year.")]
    public static bool IsLeapYear(int year)
    {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }
}
MCP tools shown in VS Code
MCP tools shown in VS Code

McpServerPromptType

This attribute defines reusable prompt templates that can be parameterized. Prompts allow AI clients to generate content like commit messages or code snippets by filling in variables.

Here’s a quick demo of prompts in Visual Studio.

[McpServerPromptType]
public static class UtilityPrompts
{
    [McpServerPrompt(
        Description("Generates a prompt explaining leap year rules for a given year.")
    )]
    public static string ExplainLeapYear(
        [Description("The year to explain")] int year)
    {
        string isLeap = UtilityTools.IsLeapYear(year) ? "is" : "is not";
        return  $"Explain why {year} {isLeap} a leap year, including the rules for divisibility by 4, 100, and 400.";
    }
}

We can insert a prompt via chat interfaces for enhanced workflows.

Choose MCP Prompts in Visual Studio 2026
Choose MCP Prompts in Visual Studio 2026
Generating a prompt via the user interface
Generating a prompt via the user interface
Prompt generated in the chat
Prompt generated in the chat interface

McpServerResourceType

This attribute defines access to external data sources, such as files, databases, or URIs. Resources can be referenced in AI queries (e.g., via `#` syntax) and support templating with arguments.

Take a look at how this attribute behaves:

[McpServerResourceType]
public static class UtilityResources
{
    [McpServerResource(
        UriTemplate = "leap-years?start={start}&end={end}",
        Description("Provides a list of leap years in a given range as JSON.")
    )]
    public static string GetLeapYears(
        [Description("Start year of the range")] int start,
        [Description("End year of the range")] int end)
    {
        var leapYears = Enumerable.Range(start, end - start + 1)
            .Where(y => UtilityTools.IsLeapYear(y))
            .ToList();

        return JsonSerializer.Serialize(leapYears);
    }
}
Resources shown in the VS Code
Resources shown in the VS Code
Choosing MCP resources in Copilot Chat
Choosing MCP resources in Copilot Chat
Adding the MCP resource in Visual Studio
Adding the MCP resource in Visual Studio
Resource displayed in the Copilot Chat
Resource displayed in the Copilot Chat

Description attribute

When applied to tools, prompts, or resources, this attribute provides a human-readable metadata that helps AI clients understand each capability and choose the right one. It defines what a tool does, the parameters it expects, and the outputs it produces, enabling natural‑language-driven invocation and more intuitive integration.

Code example to achieve this:

`[Description("Echoes the message back to the client.")]`

MCP server builder methods (automatic discovery)

During server setup (for example, in Program.cs), you can scan your assembly for MCP tool/prompt/resource types. These methods reflect over your assembly (the one containing your server code) and automatically register any types/members that follow the MCP server conventions. This simplifies types and members discovery and reduces boilerplate code.

Add this to your project:

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

If you want to avoid startup overload or need to register specific types with modularity capability, then use the generic WithX() methods explicitly.

Here’s how that looks in code:

builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithPrompts<UtilityPrompts>()
    .WithResources<UtilityResources>()
    .WithTools<UtilityTools>();

These features make the SDK developer-friendly, with support for transports like stdio, http, and sse, as well as advanced capabilities like elicitation for interactive user input.

Example implementation using the MCP C# SDK

To make this concrete, we’ll build a simple MCP server that exposes two utilities for:

  • Checking whether a year is a leap year, and
  • Checking whether text is a palindrome.

Let’s dive into the implementation steps. We’ll include:

  • Dependency injection (DI).
  • UseStructuredContent for structured outputs.
  • Elicitation for interactive input.
  • URI templating for resources.

Step 1: Create a console project and install packages

First, create a new console project in Visual Studio Code and install the necessary packages.

Try this in your terminal:

dotnet new console -n UtilityMCP
cd UtilityMCP
dotnet add package ModelContextProtocol --prerelease
dotnet add package Microsoft.Extensions.Hosting

This sets up a console-hosted MCP server with the .NET hosting and DI infrastructure.

Step 2: Configure the server in Program.cs

In Program.cs, configure the server with DI and scanning:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMcpServer()
    .WithStdioServerTransport()
    .WithPromptsFromAssembly()
    .WithResourcesFromAssembly()
    .WithToolsFromAssembly(); // Scans for tools, prompts, and resources
builder.Services.AddSingleton<DataService>();  // Example DI for services
await builder.Build().RunAsync();

WithStdioServerTransport() is commonly used for IDE integrations. Assembly scanning finds your annotated tool/prompt/resource types automatically.

Tools example (structured content + elicitation)

Define a tools class demonstrating structured content and elicitation:

Here’s the complete C# code block:

using ModelContextProtocol.Protocol;
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json;
using System.Text.Json.Serialization;
using static ModelContextProtocol.Protocol.ElicitRequestParams;

[McpServerToolType]
public class UtilityTools
{
    [McpServerTool, Description("Checks if the given year is a leap year.")]
    public bool IsLeapYear(int year)
    {
        return (year % 4 == 0 && year % 100 != 0) || (year % 400 == 0);
    }

    [McpServerTool, Description("Checks if the given text is a palindrome, ignoring case and non-alphanumeric characters.")]
    public static bool IsPalindrome(string text)
    {
        var cleaned = new string(text.Where(char.IsLetterOrDigit).Select(char.ToLower).ToArray());
        return cleaned == new string(cleaned.Reverse().ToArray());
    }

    // Tool with DI: Injects a service for more complex logic
    [McpServerTool, Description("Retrieves user data by ID, returning JSON.")]
    public static string GetUserData(DataService dataService, [Description("The unique ID of the user")] int userId)
    {
        var user = dataService.GetUserById(userId); // Assume DataService fetches or mocks data
        return JsonSerializer.Serialize(user);
    }

    // Structured output: Returns a typed list with auto-generated schema
    [McpServerTool(UseStructuredContent = true), Description("Gets a list of leap years in a range with metadata.")]
    public static List<LeapYearInfo> GetLeapYearsInRange([Description("Start year")] int start, [Description("End year")] int end)
    {
        return Enumerable.Range(start, end - start + 1)
            .Where(y => (y % 4 == 0 && y % 100 != 0) || (y % 400 == 0))
            .Select(y => new LeapYearInfo { Year = y, Explanation = $"Divisible by 4: {y % 4 == 0}, by 100: {y % 100 == 0}, by 400: {y % 400 == 0}" })
            .ToList();
    }

    // Interactive tool with elicitation: Prompts user for input during execution
    [McpServerTool, Description("Plays a simple palindrome guessing game.")]
    public static async Task<string> PalindromeGame(McpServer server, CancellationToken token)
    {
        string result = "Guess a palindrome word or phrase.";

        // Elicit user's guess
        var guessSchema = new RequestSchema
        {
            Properties = { ["guess"] = new StringSchema { MinLength = 1 } }
        };
        var elicitParams = new ElicitRequestParams
        {
            Message = "Enter your palindrome guess:",
            RequestedSchema = guessSchema
        };
        var response = await server.ElicitAsync(elicitParams, token);

        if (response?.Content is IDictionary<string, JsonElement> content &&
            content.TryGetValue("guess", out var guessElem) &&
            guessElem.GetString() is string guess)
        {
            bool isPal = IsPalindrome(guess); 
            result += isPal ? " Yes, that's a palindrome!" : " No, that's not a palindrome.";
        }
        else
        {
            result += " No valid guess provided.";
        }

        return result;
    }
}

// Supporting types for structured output
public class LeapYearInfo
{
    [JsonPropertyName("year")]
    public int Year { get; set; }

    [JsonPropertyName("explanation")]
    public required string Explanation { get; set; }
}

// Example DataService 
public class DataService
{
    public object GetUserById(int id)
    {
        // Mock data; in reality, this could query a DB or API
        return new { Id = id, Name = $"User{id}", Email = $"user{id}@example.com" };
    }
}

UseStructuredContent

The UseStructuredContent property in the McpServerTool attribute enables schema-aware outputs for tools like GetLeapYearsInRange, allowing AI clients to parse results predictably.

After running the code, you’ll see this:

Structured output in the chat window (year and explanation)
Structured output in the chat window (year and explanation)

Prompts example

Prompts are reusable templates that hosts can insert into chat or use in agent workflows.

Here’s how that looks in code:

using ModelContextProtocol.Server;
using System.ComponentModel;

[McpServerPromptType]
public class UtilityPrompts
{
    [McpServerPrompt, Description("Generates a prompt explaining leap year rules for a given year.")]
    public string ExplainLeapYear(
        [Description("The year to explain")] int year)
    {
        string isLeap = new UtilityTools().IsLeapYear(year) ? "is" : "is not";
        return $"Explain why {year} {isLeap} a leap year, including the rules for divisibility by 4, 100, and 400.";
    }

    [McpServerPrompt, Description("Generates a prompt to create a palindrome sentence based on input text.")]
    public static string GeneratePalindrome(
        [Description("Base text to inspire the palindrome")] string baseText)
    {
        return $"Create a creative palindrome sentence incorporating the words from '{baseText}'. Ignore case and punctuation in the palindrome check.";
    }
}

Resources example (URI templates)

Resources expose data via URIs, with optional templating.

Example: How to implement this feature

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

[McpServerResourceType]
public class UtilityResources
{
    [McpServerResource(UriTemplate = "leap-years?start={start}&end={end}"), Description("Provides a list of leap years in a given range as JSON.")]
    public string GetLeapYears(
        [Description("Start year of the range")] int start,
        [Description("End year of the range")] int end)
    {
        var leapYears = Enumerable.Range(start, end - start + 1)
            .Where(y => new UtilityTools().IsLeapYear(y))
            .ToList();
        return JsonSerializer.Serialize(leapYears);
    }

    [McpServerResource(UriTemplate = "palindrome-examples"), Description("Returns a predefined list of palindrome examples as text.")]
    public static string GetPalindromeExamples()
    {
        return "Examples of palindromes:\n- Racecar\n- A man a plan a canal Panama\n- Madam, I'm Adam";
    }
}

UriTemplate

Customizes the resource URI for parameterized access, for example: `#resource://utility/leap-years?start=2000&end=2100`.

Below is the visual representation.

URI template showing parameterized resource access (start and end years)
URI template showing parameterized resource access (start and end years)

Configure VS Code integration (mcp.json)

Configure mcp.json in the .vscode folder for VS Code integration:

Below is the JSON code you need:

{
    "servers": {
        "UtilityMCP": {
            "type": "stdio",
            "command": "dotnet",
            "args": ["run", "--project", "UtilityMCP.csproj"]
        }
    }
}

Note: In Visual Studio, create .mcp.json where the .slnx (.sln) file is present.

Let’s see this in action: In Copilot Chat (Agent mode), try:

  • “Is 2024 a leap year?”
  • “Check if ‘A man a plan a canal Panama’ is a palindrome.”
  • #resource://utility/palindrome-examples.
Palindrome resource output shown in the chat window (after elicitation input)
Palindrome resource output shown in the chat window (after elicitation input)

Verify your server from a C# client

If you want to verify from a C# client that your server exposes tools/prompts/resources, use the SDK’s client APIs. McpClient.CreateAsync() instantiates and connects a client.

Code example for quick integration:

using ModelContextProtocol.Client;

await using var client = await McpClient.CreateAsync(
    new StdioClientTransport(new()
    {
        Name = "UtilityMcpServer",
        Command = "dotnet",
        Arguments = ["run", "--project", "UtilityMCP.csproj"]
    }),
    cancellationToken: CancellationToken.None
);


// Tools
var tools = await client.ListToolsAsync(cancellationToken:  CancellationToken.None);
Console.WriteLine("Tools: " + string.Join(", ", tools.Select(t => t.Name)));

// Prompts
var prompts = await client.ListPromptsAsync(cancellationToken: CancellationToken.None);
Console.WriteLine("Prompts: " + string.Join(", ", prompts.Select(p => p.Name)));

// Resources
var resources = await client.ListResourcesAsync(cancellationToken: CancellationToken.None);
Console.WriteLine("Resources: " + string.Join(", ", resources.Select(r => r.Name)));
Client verification output showing listed tools, prompts, and resources
Client verification output showing listed tools, prompts, and resources

Why isn’t GetLeapYears listed here?

GetLeapYears uses a UriTemplate that requires arguments (start, end). MCP treats that as a Resource Template, not a concrete Resource. VS Code’s current UI typically lists only concrete resources (fixed URIs) under “Resources,” while templates appear via the protocol as list_resource_templates. Visual Studio tends to surface templates more clearly.

Comparison of traditional APIs and MCP

Here is a quick comparison of traditional APIs (REST/SOAP/GraphQL) and MCP:

Aspect Traditional APIs Model Context Protocol (MCP)
Communication model Direct, structured calls with predefined endpoints and schemas. AI-driven, natural language queries with automatic tool discovery and invocation.
Integration effort Custom code for each API; manual endpoint handling. Standardized protocol; LLMs handle selection and calling.
Use case High-volume, stable operations with control. AI agents need real-time, context-aware interactions.
Flexibility Rigid contracts; changes require updates. Extensible with tools, prompts, resources; supports elicitation.
Data exchange Synchronous/asynchronous via HTTP, etc. Two-way, protocol-based with metadata for AI interpretability.
When to use For developer-to-service interactions. For AI-to-service in dynamic, multi-step workflows.

Advantages of MCP C# SDK over other SDKs

The MCP C# SDK emphasizes standardization and interoperability, which is especially useful when you don’t want tool calling logic tied to a single model vendor. Key advantages include:

  • Language-agnostic interoperability: MCP is protocol-based, so a C# MCP client can call a Python MCP server (and vice versa).
  • Open standard and extensibility: Reduces vendor lock-in and supports a growing ecosystem of servers.
  • Enhanced orchestration: Structured outputs + elicitation help with reliability and interactive workflows.
  • Seamless .NET integration: Fits naturally with DI and .NET hosting patterns.
  • Two-way communication: Supports richer back-and-forth workflows than one-shot calls.

Conclusion

Thank you for reading! MCP and the Model Context Protocol C# SDK represent a practical shift toward standardized AI-tool integrations. Instead of building custom glue code for every model and every tool, you can expose reusable capabilities (tools, prompts, resources) through a consistent protocol that hosts can discover and invoke.

If you’d like a follow-up post, share a real scenario in the comments below (e.g., internal docs search, CI/CD automation, ticket triage, database lookup). We can map it to a clean MCP server design and implementation.

Read the whole story
alvinashcraft
just a second ago
reply
Pennsylvania, USA
Share this story
Delete

Visual Studio at GDC Festival of Gaming 2026

1 Share

Join us at GDC Festival of Gaming 2026 for a deep dive into Visual Studio, GitHub Copilot, PowerToys, and the Windows tools that speed up your daily dev workflow. We’ll show how these tools work together to boost productivity and cut friction across your entire inner loop.

Windows Game Development and Visual Studio 2026 Presented by David Li and Hamza Usmani

Session Title: Windows Game Development with Visual Studio 2026 and GitHub Copilot

Session Info: Thursday, March 12, 10:10 am – 11:10 am Pacific Time | Room 2009, West Hall

Abstract: Developing games on Windows is faster when your tools work together. This session walks through an end-to-end game development workflow, from setting up a repeatable dev environment with Windows Terminal, WinGet, and GitHub Copilot, to reducing daily friction with PowerToys. Discover how integrated agents in Visual Studio accelerate build times, modernize compiler upgrades, and complement code editing with advanced C++ tools. From forking a GitHub project to running WinGet configurations and hitting F5 in Visual Studio, these experiences help you iterate faster and optimize smarter. Join us to see how Windows and Visual Studio deliver secure, manageable, and AI-driven workflows for modern game development.

We look forward to seeing you and chatting with you this year.

The post Visual Studio at GDC Festival of Gaming 2026 appeared first on C++ Team Blog.

Read the whole story
alvinashcraft
15 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Implement Prototype Pattern in C#: Step-by-Step Guide

1 Share

How to implement Prototype pattern in C#: step-by-step guide with code examples, shallow vs deep copy, and best practices for creational design patterns.

Read the whole story
alvinashcraft
21 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

How to Load and Display a PDF in ASP.NET Core

1 Share
Learn how to load and display a PDF document in ASP.NET Core. See more and build your application today. Continue reading
Read the whole story
alvinashcraft
26 seconds ago
reply
Pennsylvania, USA
Share this story
Delete

SQL Server Indexing – Clustered Indexes

1 Share

Join our session to learn about SQL clustered indexes and how they enhance performance in SQL Server indexing.

The post SQL Server Indexing – Clustered Indexes appeared first on MSSQLTips.com.

Read the whole story
alvinashcraft
1 minute ago
reply
Pennsylvania, USA
Share this story
Delete
Next Page of Stories