Making AI smarter with an MCP server that manages short URLs

Have you ever wanted to give your AI assistants access to your own custom tools and data? That's exactly what Model Context Protocol (MCP) allows us to do, and I've been experimenting with it lately.

(Version française ici)

I read a lot recently about Model Context Protocol (MCP) and how it is changing the way AI interacts with external systems. I was curious to see how it works and how I can use it in my own projects. There are many tutorial available online but one of my favorite was written by James Montemagno Build a Model Context Protocol (MCP) server in C#. This post isn't a tutorial, but rather a summary of my experience and what I learned along the way while building a real MCP server that manages short URLs.

MCP doesn't change AI itself, it's a protocol that helps your AI model to interact with external resources: API, databases, etc. The protocol simplifies the way AI can access an external system, and it allows the AI to discover the available tools from those resources. Recently I was working on a project that manages short URLs, and I thought it would be a great opportunity to build an MCP server that manages short URLs. I wanted to see how easy it is to build and then use it in VSCode with GitHub Copilot Chat.

Code: All the code of this post is available in the branch exp/mcp-server of the AzUrlShortener repo on GitHub.

Setting Up: Adding an MCP Server to a .NET Aspire Solution

The AzUrlShortener is a web solution that uses .NET Aspire, so the first thing I did was create a new project using the command:

dotnet new web -n Cloud5mins.ShortenerTools.MCPServer -o ./mcpserver

Required Dependencies

To transform this into an MCP server, I added these essential NuGet packages:

  • Microsoft.Extensions.Hosting
  • ModelContextProtocol.AspNetCore

Since this project is part of a .NET Aspire solution, I also added references to:

  • The ServiceDefaults project (for consistent service configuration)
  • The ShortenerTools.Core project (where the business logic lives)

Integrating with Aspire

Next, I needed to integrate the MCP server into the AppHost project, which defines all services in our solution. Here's how I added it to the existing services:

var manAPI = builder.AddProject<Projects.Cloud5mins_ShortenerTools_Api>("api")
						.WithReference(strTables)
						.WaitFor(strTables)
						.WithEnvironment("CustomDomain",customDomain)
						.WithEnvironment("DefaultRedirectUrl",defaultRedirectUrl);

builder.AddProject<Projects.Cloud5mins_ShortenerTools_TinyBlazorAdmin>("admin")
		.WithExternalHttpEndpoints()
		.WithReference(manAPI);

// 👇👇👇 new code for MCP Server
builder.AddProject<Projects.Cloud5mins_ShortenerTools_MCPServer>("mcp")
		.WithReference(manAPI)
		.WithExternalHttpEndpoints();

Notice how I added the MCP server with a reference to the manAPI - this is crucial as it needs access to the URL management API.

Configuring the MCP Server

To complete the setup, I needed to configure the dependency injection in the program.cs file of the MCPServer project. The key part was specifying the BaseAddress of the httpClient:

var builder = WebApplication.CreateBuilder(args);       
builder.Logging.AddConsole(consoleLogOptions =>
{
    // Configure all logs to go to stderr
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});
builder.Services.AddMcpServer()
    .WithTools<UrlShortenerTool>();

builder.AddServiceDefaults();

builder.Services.AddHttpClient<UrlManagerClient>(client => 
            {
                client.BaseAddress = new Uri("https+http://api");
            });
            
var app = builder.Build();

app.MapMcp();

app.Run();

That's all that was needed! Thanks to .NET Aspire, integrating the MCP server was straightforward. When you run the solution, the MCP server starts alongside other projects and will be available at http://localhost:{some port}/sse. The /sse part of the endpoint means (Server-Sent Events) and is critical - it's the URL that AI assistants will use to discover available tools.

Implementing the MCP Server Tools

Looking at the code above, two key lines make everything work:

  1. builder.Services.AddMcpServer().WithTools<UrlShortenerTool>(); - registers the MCP server and specifies which tools will be available
  2. app.MapMcp(); - maps the MCP server to the ASP.NET Core pipeline

Defining Tools with Attributes

The UrlShortenerTool class contains all the methods that will be exposed to AI assistants. Let's examine the ListUrl method:

[McpServerTool, Description("Provide a list of all short URLs.")]
public List<ShortUrlEntity> ListUrl()
{
	var urlList = _urlManager.GetUrls().Result.ToList<ShortUrlEntity>();
	return urlList;
}

The [McpServerTool] attribute marks this method as a tool the AI can use. I prefer keeping tool definitions simple, delegating the actual implementation to the UrlManager class that's injected in the constructor: UrlShortenerTool(UrlManagerClient urlManager).

The URL Manager Client

The UrlManagerClient follows standard HttpClient patterns. It receives the pre-configured httpClient in its constructor and uses it to communicate with the API:

public class UrlManagerClient(HttpClient httpClient)
{
	public async Task<IQueryable<ShortUrlEntity>?> GetUrls()
    {
		IQueryable<ShortUrlEntity> urlList = null;
		try{
			using var response = await httpClient.GetAsync("/api/UrlList");
			if(response.IsSuccessStatusCode){
				var urls = await response.Content.ReadFromJsonAsync<ListResponse>();
				urlList = urls!.UrlList.AsQueryable<ShortUrlEntity>();
			}
		}
		catch(Exception ex){
			Console.WriteLine(ex.Message);
		}
        
		return urlList;
    }

	// other methods to manage short URLs
}

This separation of concerns keeps the code clean - tools handle the MCP interface, while the client handles the API communication.

Using the MCP Server with GitHub Copilot Chat

Now for the exciting part - connecting your MCP server to GitHub Copilot Chat! This is where you'll see your custom tools in action.

Configuring Copilot to Use Your MCP Server

Once the server is running (either deployed in Azure or locally), follow these steps:

  1. Open GitHub Copilot Chat in VS Code
  2. Change the mode to Agent by clicking the dropdown in the chat panel
  3. Click the Select Tools... button, then Add More Tools
Set GitHub Copilot mode to Agent

Selecting the Connection Type

GitHub Copilot supports several ways to connect to MCP servers:

All MCP Server types

There are multiple options available - you could have your server in a container or run it via command line. For our scenario, we'll use HTTP.

Note: At the time of writing this post, I needed to use the HTTP URL of the MCP server rather than HTTPS. You can get this URL from the Aspire dashboard by clicking on the resource and checking the available Endpoints.

After selecting your connection type, Copilot will display the configuration file, which you can modify anytime.

GitHub Copilot Chat Configuration

Interacting with Your Custom Tools

Now comes the fun part! You can interact with your MCP server in two ways:

  1. Natural language queries: Ask questions like "How many short URLs do I have?"
  2. Direct tool references: Use the pound sign to call specific tools: "With #azShortURL list all URLs"

The azShortURL is the name we gave to our MCP server in the configuration.

GitHub Copilot question and response example


Key Learnings and Future Directions

Building this MCP server for AzUrlShortener taught me several valuable lessons:

What Worked Well

  • Integration with .NET Aspire was remarkably straightforward
  • The attribute-based approach to defining tools is clean and intuitive
  • The separation of tool definitions from implementation logic keeps the code maintainable

Challenges and Considerations

  • The csharp-SDK is only a few weeks old and still in preview
  • OAuth authentication isn't defined yet (though it's being actively worked on)
  • Documentation is present but evolving rapidly as the technology matures, so some features may not be fully documented yet

For the AzUrlShortener project specifically, I'm keeping this MCP server implementation in the experimental branch mcp-server until I can properly secure it. However, I'm already envisioning numerous other scenarios where MCP servers could add great value.

If you're interested in exploring this technology, I encourage you to:

  • Check out the GitHub repo
  • Fork it and create your own MCP server
  • Experiment with different tools and capabilities

Join the Community

If you have questions or want to share your experiences with others, I invite you to join the Azure AI Community Discord server:

Join Azure AI Community Discord

The MCP ecosystem is growing rapidly, and it's an exciting time to be part of this community!


~Frank


Reading Notes #644

This post gathers my recent reading notes on artificial intelligence, programming, and a few inspiring podcasts. It includes links to articles, tutorials, and fascinating discussions. Whether you're interested in the latest AI developments, .NET tools, or modern architectures, there's plenty here to spark your curiosity. 


Happy reading!

AI


Programming


Podcasts

Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 

If you have interesting content, share it!

~Frank

Reading Notes #643

In this week Reading Notes, we explore a diverse range of updates and insights from the tech world. From the latest features in the Azure SDK and Developer CLI, to an introduction to .NET Aspire and its innovative approach to Infrastructure as Code, there's plenty to catch up on. 

Jump into discussions on AI productivity, free Azure SQL tiers, and even a refreshing podcast on stress-free living. 


Let's get started!


Cloud

Databases

AI

Programming

Podcasts


Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 


If you have interesting content, share it!

~Frank

Migrating AzUrlShortener from Azure Static WebApp to Azure Container Apps

It's been already 2 years since I stopped working on the AzUrlShortener project. Not that I didn't like it, but I was busy with other projects. Recently, the opportunity to work on it again came up, and I jumped on it. So many things changed in two years, and I was excited to see how I could improve the solution's developer experience and modernize the user interface and architecture.

This post is the first of a series where I share a few interesting details, tips, and tricks I learned while working on this project.


AzUrlShortener is an Open source project that consists of a simple URL shortener. The goal was simple: having a budget-friendly solution to share short URLs that would be secure, easy to use, and where the data would stay ours. Each instance is hosted in Azure and used to consist of an API (Azure Function), a Blazor WebAssembly website (Azure Static Web App), and Data Storage (Azure Storage table).

 

Key Changes at a Glance

  • Migration from Azure Static Web Apps to Azure Container Apps
  • Upgrade to .NET 9.0 and integration with .NET Aspire
  • Enhanced security with separated API responsibilities
  • Simplified deployment using Azure Developer CLI (azd)
  • Modern UI with FluentUI Blazor

Upgrading SDKs and packages

As mentioned earlier, a lot changed in two years. The first thing I did was to upgrade the SDKs to .NET 9.0. That was a breeze; I changed the target framework in the project file then used dotnet-updated to list all the packages that needed to be updated. I then used dotnet outdated -u to update all the packages.

And while we were at it, since I started using .NET Aspire, I couldn't resist using it as much as possible. It simplifies the development cycle so much, and the code is much cleaner. So I added that to the mix. It added two more projects to the solution, but now the entire solution is defined in C# in the AppHost project (the Aspire orchestrator).

So now the solution looks like this:

src/
├── Cloud5mins.ShortenerTools.Api               # Internal management API
├── Cloud5mins.ShortenerTools.AppHost           # .NET Aspire orchestrator
├── Cloud5mins.ShortenerTools.Core              # Shared business logic
├── Cloud5mins.ShortenerTools.FunctionsLight    # Public redirect API
├── Cloud5mins.ShortenerTools.ServiceDefaults   # Common service configurations
└── Cloud5mins.ShortenerTools.TinyBlazorAdmin   # Frontend application


Changes to Improve Security

Security should come first, and I wanted to make sure that the solution was as secure as possible. I decided to split the API into two parts. The first part is the API that redirects the users, and it can only do that. The second part is the internal API to manage all the URLs and other data.

I decided to migrate the solution to use Azure Container Apps and have it running in two containers: the TinyBlazorAdmin and the Api. With Azure Container Apps, I can use Microsoft Entra, without any line of code, to secure TinyBlazorAdmin. The API will only be accessible from the TinyBlazorAdmin and won't be exposed to the Internet. As a bonus, since TinyBlazorAdmin and the API are now running inside containers, you could also decide to run them locally.

The storage access got also a security upgrade. Instead of using a connection string, I will be using a Managed Identity to access the Azure Storage Table. This is a much more secure way to access Azure resources, and thanks to .NET Aspire, it is also very easy to implement.


Architecture

The architecture is changing a little. The API is now split in two: FunctionsLight and API. The two APIs use services from Core to avoid code duplication. The TinyBlazorAdmin runs in a container and is secured by Microsoft Entra. API is also running in a container and is not exposed to the Internet. And Azure Storage Table is still our faithful data source.


Previous Architecture

  • Azure Function (API)
  • Azure Storage (Function Code)
  • Azure Static Web App (Blazor WebAssembly)
  • Azure Storage Table (Data)
  • Application Insights


New Architecture

  • Container registry (Docker images)
  • Container Apps Environment
    • Container App/ Function: FunctionsLight Public redirect-only API
    • Container App: Internal API Protected management interface
    • Container App: TinyBlazorAdmin Secured Blazor website
  • Azure Storage Table (Data)
  • Managed Identity
  • Log Analytics


AzUrlShortener Global Diagram

Deployment

The Deployment is also changing. Instead of using a button from my GitHub repo, we will be using the Azure Developer CLI (azd) or a GitHub Action from your own repo (aka fork). The deployment will take ~10 minutes and will be done with one simple command azd up. The entire solution will still have Infrastructure as Code (IaC) using Bicep, instead of ARM.

Here a video about it




Were there any challenges?

While working, there were a few challenges or "detours" that slowed the progress of the migration a little, but most of them were due to decisions made to improve the solution. Let's take a look at the first one in the next post: Franky's Notes: Converting a Blazor WASM to FluentUI Blazor server.


Want to Learn more?

To learn more about Azure Container Apps I strongly suggest this repository: Getting Started .NET on Azure Container Apps, it contains many step-by-step tutorials (with videos) to learn how to use Azure Container Apps with .NET.


Reading Notes #642

This week, I explored posts about improving cache management for ASP.NET Core applications and understanding error handling in Blazor. These articles, along with others on AI model selection and development productivity, offer valuable insights for developers.


Cloud


Programming


AI


Miscellaneous



Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 


If you have interesting content, share it!

~Frank

Reading Notes #641

This week's reading notes cover an array of topics, from programming insights and AI advancements to cloud solutions and practical tips for developers. Explore the updates, tools, and advice that could inspire your next project!
web dev in a D&D monsters card style
prompt: Web dev in a D&D monsters card style


 

Suggestion of the week


Cloud


Programming


AI


Miscellaneous


Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 


If you have interesting content, share it!

~Frank

Reading Notes #640

Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 

If you have interesting content, share it! 

Cloud


AI


Programming

~frank

Reading Notes #639

Welcome to Reading Notes #639, a curated selection of intriguing articles across programming, AI, cloud computing, and more. Dive into new productivity features in Visual Studio, the latest front-end development trends, .NET AI evaluations, and essential learning techniques. 

Grab your coffee and enjoy the read!


Cloud

Programming

AI

Miscellaneous


Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 

If you have interesting content, share it! 

~frank

Reading Notes #638

Welcome to Reading Notes —a curated dive into the latest and greatest in programming, cloud, and AI. From mastering multithreading with Azure to exploring GitHub Copilot's productivity potential, this collection is brimming with knowledge. Let's unravel what's new, innovative, and worth your attention!


Cloud


Programming

AI

Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 

If you have interesting content, share it! 

~frank


Reading Notes #637

In this edition of my Reading Notes, I've curated some fascinating content that spans across programming, creativity, and enlightening podcasts. Whether you're eager to enhance your coding skills, explore unique ideas, or stay updated with the latest in the tech world, there's something here for everyone. 
Dive in and enjoy these insightful reads and discussions!

Programming

Podcasts

Miscellaneous



Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. If you have interesting content, share it! 

 ~Frank

Reading Notes #636

Welcome back to another edition of my Reading Notes! This week, we've got some great content lined up, including insights on cloud development with Azure Developer CLI, tips for promoting your open source projects, updates on .NET, and more. Dive in to explore a wealth of knowledge and stay updated with the latest trends and developments.

Cloud

Programming

AI

Miscellaneous


Sharing my Reading Notes is a habit I started a long time ago, where I share a list of all the articles, blog posts, and books that catch my interest during the week. 

 If you have interesting content, share

~Frank