Monday, October 6, 2025

Explore Docker MCP Toolkit and VS Code

To explore Docker MCP Toolkit, we will use two MCP server in the toolkit, namely PostgreSQL and Playwright.

Companion Video: https://youtu.be/43oJi_gAucU

What is Docker MCP Toolkit?

The Docker MCP Toolkit enables hosting and managing MCP servers. These servers expose APIs for specific development tasks, such as retrieving GitHub issue data or querying databases using natural language.

Prerequisites

You will need the following before you can continue:

  • Docker Desktop (latest version)
  • Visual Studio Code (latest version)
  • GitHub Copilot extension for VS Code
  • GitHub Copilot with Chat and Agent Mode enabled

1) Explore PostgreSQL MCP Server

We will use natural language to query a PostgreSQL database that is already pre-loaded with the sample Northwind database. To run the PostgreSQL server in a docker container on your computer, execute the following command from any terminal window:


docker run --name psqlnw -e POSTGRES_PASSWORD=VerySecret -p 5433:5432 -d melmasry/nw-psql:1.0.0

Start “Docker Desktop” on your computer and go to the Containers tab on the left navigation. You will see that the psqlnw container is running.


Next, let us use the Docker MCP Toolkit. In Docker Desktop, click on “MCP Toolkit” in the left navigation.

Click on the Catalog tab. This will show you a list of MCP Servers that are ready for you to explore. 

We will start with PostgreSQL. Find the PostgreSQL MCP Server by entering ‘postgr’ in the filter field. Then click on + to add it to your list.

You will be asked to enter a secret. This is nothing but the connection string and it is based on this format:

postgresql://readonly_user:readonly_password@host:port/database_name

In our case, this would be:

postgresql://postgres:VerySecret@host.docker.internal:5433/northwind

Click on the "My Servers" tab to see the MCP servers that you have chosen.

Connect Docker MCP Toolkit to Visual Studio Code

Let us query our northwind database in PostgreSQL from VS Code. Go to a working directory and execute these commands to create an empty folder on your computer:

mkdir mcp-server
cd mcp-server

In the same terminal window, logout and login into docker with:

docker logout  
docker login

Start VS Code in the folder with

code .

In VS Code, open the Command Palette by pressing Ctrl + Shift + P (or Cmd + Shift + P on macOS).

Select “Add MCP Server”.

Select “Command (stdio) Run a local command that implements the MCP protocol Manual Install”.

Enter the gateway command:

docker mcp gateway run

Give the server an ID named: 

my-mcp-server

Choose “Workspace Available in this workspace, runs locally”.


Click Trust.

A file named mcp.json is created in a .vscode folder with this content:

Note that the server is running. 

Inside the Github Copilot Chat window, choose Agent and any Claude model. Click on the tools icon to see active MCP servers.

You will find MCP servers that are configured in VS Code. Among them will be the one we enabled in the Docker MCP Toolkit.

You will notice that it only has one tool: “query Run a read-only SQL query”. This is all that is needed to query the northwind database.

Click ok the Ok button to close the tools popup.

Enter this prompt in the GitHub Copilot window:

What are the tables in the PostgreSQL northwind database?

You will be asked to click on the Allow button.

Thereafter, it displays a list of database tables in the northwind database.

Try this other prompt:

What are the products supplied by "Exotic Liquids"?

You will get a similar response to this:

2) Explore Playwright MCP Server

Back in Docker Desktop >> MCP Toolkit, add Playwright.

You now have two MCP servers in our list: Playwright and PostgreSQL.

Back in VS Code, restart the MCP Server in the .vscode/mcp.json file.

In the VS Code GitHub Copilot Chat window, enter this prompt:

Using the tools provided from the Docker MCP Server, navigate to https://www.bbc.com/, find the two most important business stories.

I got the following response at the time of writing this article:

Conclusion

I hope you found this article useful. These are early days of MCP servers. I am sure things will evolve much more in this very important space.

Saturday, September 20, 2025

Build web-based MCP server and client with ASP.NET & GitHub Models

Overview

This article demonstrates how to build a basic MCP server and client using ASP.NET and Server Sent Events (SSE). The MCP server exposes tools that can be discovered and used by LLMs, while the client application connects these tools to an AI service. GitHub AI models are used on the client application.

Server Sent Events (SSE)

SSE transport enables server-to-client streaming with HTTP POST requests for client-to-server communication. This approach facilitates communication between frontend microservices and backend business contexts through the MCP server. 

Companion Video: https://youtu.be/vTBtPU7AnT4

Source Code: https://github.com/medhatelmasry/AspMCP

Prerequisites

  • GitHub account
  • Visual Studio Code
  • .NET 9.0 (or later)

Setup

We will create an .NET solution comprising of an ASP.NET web server project; a WebAPI client project; and add the required packages with these terminal window commands:

mkdir AspMCP
cd AspMCP
dotnet new sln
dotnet new web -n ServerMCP
dotnet sln add ./ServerMCP/ServerMCP.csproj
cd ServerMCP
dotnet add package Azure.AI.OpenAI
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease
dotnet add package ModelContextProtocol --prerelease
dotnet add package ModelContextProtocol.AspNetCore --prerelease
cd ..
dotnet new webapi --use-controllers -n ClientMCP
dotnet sln add ./ClientMCP/ClientMCP.csproj
cd ClientMCP
dotnet add package Azure.AI.OpenAI
dotnet add package Azure.Identity
dotnet add package Microsoft.Extensions.AI
dotnet add package Microsoft.Extensions.AI.OpenAI --prerelease
dotnet add package ModelContextProtocol --prerelease
dotnet add package Swashbuckle.AspNetCore
cd ..

Open the solution in VS Code. To do that, you can enter this command in a terminal a terminal window:

code .

Build ASP.NET Server

In the ServerMCP project, add this service to Program.cs before “var app = builder.Build();”:

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

In the same server Program.cs file, add this code just before “app.Run();”:

// Add MCP middleware
app.MapMcp();

Delete (or comment out) this code in the Program.cs file:

app.MapGet("/", () => "Hello World!");

In the server project create a folder named McpTools folder and add to it a GreetingTool class with this code:

[McpServerToolType]
public sealed class GreetingTool {
  public GreetingTool() { }
  
  [McpServerTool, Description("Says Hello to a user")]
  public static string Echo(string username) {
    return "Hello " + username;
  }
}

Test Server

In a terminal window inside the server project, run this command to start the server:

dotnet watch

Point your browser to https://localhost:????/sse. 

Note : replace ???? with your port number. 

This is what you will see:


Build Client

In the client project......

1) Add these settings to the appsettings.json file.

"AI": {
  "ModelName": "gpt-4o-mini",
  "Endpoint": "https://models.inference.ai.azure.com",
  "ApiKey": "PUT-GITHUB-TOKEN-HERE",
  "MCPServiceUri": "http://localhost:5053/sse"
}

NOTE: adjust values for ApiKey and MCPServiceUri accordingly.

2) Register Services via Dependency Injection. Add the following to your client Program.cs:
var endpoint = builder.Configuration["AI:Endpoint"];
var apiKey = builder.Configuration["AI:ApiKey"];
var model = builder.Configuration["AI:ModelName"];

builder.Services.AddChatClient(services =>
  new ChatClientBuilder(
    (
      !string.IsNullOrEmpty(apiKey)
        ? new AzureOpenAIClient(new Uri(endpoint!), new AzureKeyCredential(apiKey))
        : new AzureOpenAIClient(new Uri(endpoint!), new DefaultAzureCredential())
    ).GetChatClient(model).AsIChatClient()
  )
  .UseFunctionInvocation()
  .Build());
 

3) To view the swagger UI, add this code right below “app.MapOpenApi();”:
app.UseSwaggerUI(options => {
  options.SwaggerEndpoint("/openapi/v1.json", "MCP Server");
  options.RoutePrefix = "";
});
 

4) Edit Properties/launchSettings.json, change launchBrowser to true under http and https. This is so that the app automatically launches in a browser when you run the project with “dotnet watch”.

In the Controllers folder, create a ChatController class with the following code:

[ApiController]
[Route("[controller]")]
public class ChatController : ControllerBase {
  private readonly ILogger<ChatController> _logger;
  private readonly IChatClient _chatClient;

  private readonly IConfiguration? _configuration;
  public ChatController(
    ILogger<ChatController> logger,
    IChatClient chatClient,
    IConfiguration configuration
  ) {
    _logger = logger;
    _chatClient = chatClient;
    _configuration = configuration;
  }

  [HttpPost(Name = "Chat")]
  public async Task<string> Chat([FromBody] string message) {
    // Create MCP client connecting to our MCP server
    var mcpClient = await McpClientFactory.CreateAsync(
      new SseClientTransport(
          new SseClientTransportOptions {
              Endpoint = new Uri(_configuration?["AI:MCPServiceUri"] ?? throw new InvalidOperationException("MCPServiceUri is not configured"))
          }
      )
    );
    // Get available tools from the MCP server
    var tools = await mcpClient.ListToolsAsync();

    // Set up the chat messages
    var messages = new List<ChatMessage> {
      new ChatMessage(ChatRole.System, "You are a helpful assistant.")
    };
    messages.Add(new(ChatRole.User, message));

    // Get streaming response and collect updates
    List<ChatResponseUpdate> updates = [];
    StringBuilder result = new StringBuilder();

    await foreach (var update in _chatClient.GetStreamingResponseAsync(
      messages,
      new() { Tools = [.. tools] }
    )) {
      result.Append(update);
      updates.Add(update);
    }
    
    // Add the assistant's responses to the message history
    messages.AddMessages(updates);
    return result.ToString();
  }
}

Test server and client

1) Start the server in a terminal window with: dotnet watch

2) Start the client in another terminal window also with: dotnet watch

The following swagger page will load in your browser. Click on POST, then “Try it out”.


3)  string with your name, then click on Execute.


4) You will receive a response like below.


A more realistic solution

To develop a more realistic solution, we will add a database to the server project. Thereafter, our MCP client can query the database using natural language. This is very compelling for line-of-business applications.

In the server project, make these changes.

1) Add these packages:
dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.SQLite.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package CsvHelper
 

2) Add this to appsettings.json:

"ConnectionStrings": {
  "DefaultConnection": "DataSource=beverages.sqlite;Cache=Shared"
}

3) Copy CSV data from this link. Then, save the content is a file named beverages.csv and put that file in a wwwroot folder in tour project.

4) In a Models folder, add a class named Beverage with this code:
public class Beverage {
  [Required]
  public int BeverageId { get; set; }

  public string? Name { get; set; }

  public string? Type { get; set; }

  public string? MainIngredient { get; set; }

  public string? Origin { get; set; }

  public int? CaloriesPerServing { get; set; }

  public void DisplayInfo() {
    Console.WriteLine($"{Name} is a {Type} from {Origin} made with {MainIngredient}. It has {CaloriesPerServing} calories per serving.");
  }
}

5) In a Data folder, add this ApplicationDbContext class:
public class ApplicationDbContext : DbContext {
  public DbSet<Beverage> Beverages => Set<Beverage>();

  public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
    : base(options) { }

  protected override void OnModelCreating(ModelBuilder modelBuilder) {
    base.OnModelCreating(modelBuilder);
    modelBuilder.Entity<Beverage>().HasData(GetBeverages());
  }

  private static IEnumerable<Beverage> GetBeverages() {
    string[] p = { Directory.GetCurrentDirectory(), "wwwroot", "beverages.csv" };
    var csvFilePath = Path.Combine(p);

    var config = new CsvConfiguration(CultureInfo.InvariantCulture) {
      Encoding = Encoding.UTF8,
      PrepareHeaderForMatch = args => args.Header.ToLower(),
    };

    var data = new List<Beverage>().AsEnumerable();
    using (var reader = new StreamReader(csvFilePath)) {
      using (var csvReader = new CsvReader(reader, config)) {
        data = csvReader.GetRecords<Beverage>().ToList();
      }
    }

    return data;
  }
}

6) Add this service to Program.cs:
var connStr = builder.Configuration.GetConnectionString("DefaultConnection") 
    ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
    
builder.Services.AddDbContext<ApplicationDbContext>(
	option => option.UseSqlite(connStr)
);

 7) In the same Program.cs file, add this code right before “app.Run();”:
// Apply database migrations on startup
using (var scope = app.Services.CreateScope()) {
    var services = scope.ServiceProvider;

    var context = services.GetRequiredService<ApplicationDbContext>();    
    context.Database.Migrate();
}

8) Now, we can add and apply database migrations with:
dotnet ef migrations add M1 -o Data/Migrations
dotnet ef database update

At this stage, the beverages.sqlite database is populated with real data:


9) Let us add a class that queries the SQLite database. Inside of a Services folder, add a class named BeverageService with this content:

public class BeverageService(ApplicationDbContext db) {
  public async Task<string> GetBeveragesJson() {
    var beverages = await db.Beverages.ToListAsync();
    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }

  public async Task<string> GetBeverageByIdJson(int id) {
    var beverage = await db.Beverages.FindAsync(id);
    return System.Text.Json.JsonSerializer.Serialize(beverage);
  }

  public async Task<string> GetBeveragesContainingNameJson(string name) {
    var beverages = await db.Beverages
      .Where(b => b.Name!.Contains(name))
      .ToListAsync();

    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }


  public async Task<string> GetBeveragesContainingTypeJson(string type) {
    var beverages = await db.Beverages
      .Where(b => b.Type!.Contains(type))
      .ToListAsync();

    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }

  public async Task<string> GetBeveragesByIngredientJson(string ingredient) {
    var beverages = await db.Beverages
      .Where(b => b.MainIngredient!.Contains(ingredient))
      .ToListAsync();

    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }

  public async Task<string> GetBeveragesByCaloriesLessThanOrEqualJson(int calories) {
    var beverages = await db.Beverages
      .Where(b => b.CaloriesPerServing <= calories)
      .ToListAsync();

    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }

  public async Task<string> GetBeveragesByOriginJson(string origin) {
    var beverages = await db.Beverages
      .Where(b => b.Origin!.Contains(origin))
      .ToListAsync();

    return System.Text.Json.JsonSerializer.Serialize(beverages);
  }
}

10) Next, expose MCP tooling that interacts with the SQLite data. In the McpTools folder, add a class named BeverageTools with this code:

[McpServerToolType]
public class BeverageTool
{
    private readonly BeverageService _beverageService;
    private readonly ApplicationDbContext _db;

    public BeverageTool(ApplicationDbContext db)
    {
        _db = db;
        _beverageService = new BeverageService(_db);
    }

    [McpServerTool, Description("Get a list of beverages and return as JSON array")]
    public string GetBeveragesJson()
    {
        var task = _beverageService.GetBeveragesJson();
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get a beverage by ID and return as JSON")]
    public string GetBeverageByIdJson([Description("The ID of the beverage to get details for")] int id)
    {
        var task = _beverageService.GetBeverageByIdJson(id);
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get beverages by name and return as JSON")]
    public string GetBeveragesByNameJson([Description("The name of the beverage to filter by")] string name)
    {
        var task = _beverageService.GetBeveragesContainingNameJson(name);
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get beverages by type and return as JSON")]
    public string GetBeveragesByTypeJson([Description("The type of the beverage to filter by")] string type)
    {
        var task = _beverageService.GetBeveragesContainingTypeJson(type);
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get beverages by ingredient and return as JSON")]
    public string GetBeveragesByIngredientJson([Description("The ingredient of the beverage to filter by")] string ingredient)
    {
        var task = _beverageService.GetBeveragesByIngredientJson(ingredient);
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get beverages by calories less than or equal to and return as JSON")]
    public string GetBeveragesByCaloriesLessThanOrEqualJson([Description("The maximum calories per serving to filter by")] int calories)
    {
        var task = _beverageService.GetBeveragesByCaloriesLessThanOrEqualJson(calories);
        return task.GetAwaiter().GetResult();
    }

    [McpServerTool, Description("Get beverages by origin and return as JSON")]
    public string GetBeveragesByOriginJson([Description("The origin of the beverage to filter by")] string origin)
    {
        var task = _beverageService.GetBeveragesByOriginJson(origin);
        return task.GetAwaiter().GetResult();
    }
}

You can now test the Student MCP service. Start the server project followed by the client project. In the client app, enter prompt: beverages high in calories.

Here is the output:


Conclusion

You are now able to create your own MCP server using .NET. Of course, you can deploy the server to the cloud and use it from a variety of client applications and devices. Go ahead and create MCP servers that do wonderful things.

Friday, August 15, 2025

Using the timer MCP Server in Visual Studio Code

In a previous article, I discussed “Getting started with MCP Server in Visual Studio Code”. I decided to update this article because the process has become much easier with the latest version of VS Code.

In this tutorial, you will learn how to enable and use MCP servers in Visual Studio Code. The simple example we will use is the Timer MCP Server.

Companion Video: https://youtu.be/H14yMdHuyM4

What is MCP?

Model Context Protocol (MCP) is an open standard developed by Anthropic, the company behind Claude models. 

MCP is an open protocol that enables seamless integration between AI apps & agents and your tools & data sources.

Prerequisites

  • Visual Studio Code
  • GitHub account
  • Github Copilot Chat extension
  • Docker Desktop

NOTE:  This article uses Visual Studio Code version 1.103.1 as shown below:

Later versions of Visual Studio Code will likely have minor UI changes that may look different from the screen captures in this article.

Getting Started

We will setup an MCP Server that provides current time information. This will be done in a local workspace, as opposed to making it globally available in VS Code. Also, the MCP server will run in Docker.

Start Docker Desktop.

Create a working directory named time-mcp-server and open VS Code in that directory with:

mkdir time-mcp-server
cd time-mcp-server
code .

In VS Code, click on the GitHub Copilot Chat icon and select “Open Chat”.

In the chat window that opens, choose Agent mode and the latest Claude model. A tools button appears.

When you click on the tools button, a pop-up window will display the tools used by VS-Code GitHub Copilot Chat.

Click on OK to close the pop-up window.

In Visual Studio Code, click on the gear in the bottom-left, then choose settings:

In the filter field, type MCP.

Note that Discovery is enabled. These are early days, so some Mcp features are in Experimental mode.

Click on 'Edit in settings.json'. 

The file will open in the editor. Note that chat.mcp.discovery.enabled is set to true.

You can close the settings.json file in the editor.

Find a suitable MCP Server

Go to https://github.com/modelcontextprotocol/servers. Search for Time under Reference Servers.

Click on the Time link. Then fing Configuration >> Configure for Claude App >> Unsing docker.

Take note of the name of the docker image mcp/time.

Setup a timer MCP Server

In Visual Studio Code, click on View >> Command Palette….

Choose “MCP: Add Server…”.

Choose “Docker Image Install from a Docker image”.

For "Docker Image Name", enter “mcp/time”.

Next, choose Allow.

Accept the default Server ID.

Choose “Workspace Available in this workspace, runs locally”.

When prompted to trust and run the MCP server time, click on Trust.

A file named mcp.json gets created in a folder named .vscode inside your workspace with content:

{
"servers": {
"time": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"mcp/time"
],
"type": "stdio"
}
},
"inputs": []
}

If the MCP server is not yet started, click on Start.

This will start the mcp/time server in Docker.

In the GitHub Copilot Chat window, enter: What is the current time?

 VS Code detects the running MCP server which knows how to handle requests for current time:

Click on Continue and you will get a response like this:

You can ask it to convert the time to another time zone with: What is that in pacific time zone?

Again, it should resort to the time MCP server for assistance.

Click on Continue to get your answer.

You can even ask the time in another country. Example: What time is it in Cairo, Egypt?

Again, it uses our MCP time server:

Click on Continue.

Finally, let us ask it to refresh the current time, with: Refresh the time in Egypt.

Conclusion

The ecosystem for MCP servers is growing rapidly. There are already many servers that you can explore at https://github.com/modelcontextprotocol/servers.