Skip to main content

MCP

Setup

  1. Create directory

    mkdir HeroesMcp
    cd HeroesMcp
  2. Create project

    dotnet new ssw-ca
  3. Create MCP directory

    cd src
    mkdir Mcp
    cd Mcp
  4. Create new project

    dotnet new console --framework net9.0
  5. Add MCP to the solution

    dotnet sln ../.. add Mcp.csproj
  6. Add packages

    dotnet add package Microsoft.Extensions.Hosting
    dotnet add package Microsoft.Kiota.Bundle
    dotnet add package ModelContextProtocol --prerelease
  7. Open Code in Rider

    rider .
  8. Add DI

    using ApiSdk;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Hosting;
    using Microsoft.Extensions.Logging;
    using Microsoft.Kiota.Abstractions;
    using Microsoft.Kiota.Abstractions.Authentication;
    using Microsoft.Kiota.Http.HttpClientLibrary;

    var builder = Host.CreateApplicationBuilder(args);
    builder.Logging.AddConsole(consoleLogOptions =>
    {
    // Configure all logs to go to stderr
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
    });

    await builder.Build().RunAsync();
  9. Update port in AppHost/Program.cs to be random

Generate API Client

  1. Create a new directory for the API client

    cd src/Mcp
    mkdir ApiClient
    cd ApiClient
  2. Run the Api

    aspire run
  3. Ensure kiota is installed

    cd {your-project-root}
    dotnet new tool-manifest
    dotnet tool install Microsoft.OpenApi.Kiota
  4. Generate API Client

    cd src/Mcp
    dotnet kiota generate --openapi https://localhost:7255/openapi/v1.json --language csharp --class-name HeroClient --clean-output --additional-data false
  5. Add DI for API Client

    Program.cs
    var authProvider = new AnonymousAuthenticationProvider();
    var adapter = new HttpClientRequestAdapter(authProvider);
    builder.Services.AddSingleton<IRequestAdapter>(adapter);
    builder.Services.AddSingleton<HeroClient>();

Create MCP Server

  1. Add MCP DI

    Program.cs
    builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly();

Create Tools

  1. Create a new directory for the tools

    mkdir Tools
    cd Tools
  2. Add CreateHeroTool

    CreateHeroTool.cs
    using ApiSdk;
    using ApiSdk.Models;
    using ModelContextProtocol.Server;
    using System.ComponentModel;

    namespace Mcp.Tools;

    [McpServerToolType]
    public static class CreateHeroTool
    {
    [McpServerTool, Description("Create a new hero.")]
    public static async Task<string> CreateHero(
    [Description("hero name")]string name,
    [Description("hero alias")]string alias,
    [Description("power name")]string powerName,
    [Description("power level")]int powerLevel,
    HeroClient client)
    {
    try
    {
    var command = new CreateHeroCommand
    {
    Name = name,
    Alias = alias,
    Powers = [new CreateHeroPowerDto { Name = powerName, PowerLevel = powerLevel }]
    };

    await client.Api.Heroes.PostAsync(command);
    return $"Hero {name} ({alias}) successfully created with power level {powerLevel}";
    }
    catch (Exception ex)
    {
    return $"Error creating hero: {ex.Message}";
    }
    }
    }
    info

    There are other things MCP can do, such as 'sampling. This allows the MCP server to call back to the MCP client to execute a prompt. This gives you access to an LLM without needing to set one up or configure any keys.

  3. Add GetHeroesTool

    GetHeroesTool.cs
    using ApiSdk;
    using ModelContextProtocol.Server;
    using System.ComponentModel;
    using System.Text.Json;

    namespace Mcp.Tools;

    [McpServerToolType]
    public static class GetHeroesTool
    {
    [McpServerTool, Description("Get all heroes from the API.")]
    public static async Task<string> GetHeroes(HeroClient client)
    {
    try
    {
    var heroes = await client.Api.Heroes.GetAsync();

    if (heroes == null || heroes.Count == 0)
    return "No heroes found.";

    return JsonSerializer.Serialize(heroes);
    }
    catch (Exception ex)
    {
    return $"Error retrieving heroes: {ex.Message}";
    }
    }
    }
  4. Add Remaining Tools for Completeness

    RemainingTools.cs
    [McpServerToolType]
    public static class AddHeroToTeamTool
    {
    [McpServerTool, Description("Add a hero to a team.")]
    public static async Task<string> AddHeroToTeam(
    [Description("ID of the team")]string teamId,
    [Description("ID of the hero to add")]string heroId,
    HeroClient client)
    {
    if (string.IsNullOrEmpty(teamId))
    return "Error: Team ID is required";

    if (string.IsNullOrEmpty(heroId))
    return "Error: Hero ID is required";

    try
    {
    if (!Guid.TryParse(teamId, out Guid teamGuid))
    return "Error: Invalid Team ID format";

    if (!Guid.TryParse(heroId, out Guid heroGuid))
    return "Error: Invalid Hero ID format";

    await client.Api.Teams[teamGuid].Heroes[heroGuid].PostAsync();

    return $"Hero with ID {heroId} successfully added to team with ID {teamId}";
    }
    catch (Exception ex)
    {
    return $"Error adding hero to team: {ex.Message}";
    }
    }
    }

    [McpServerToolType]
    public static class CompleteMissionTool
    {
    [McpServerTool, Description("Complete a mission with a team.")]
    public static async Task<string> CompleteMission(
    [Description("ID of the team")]string teamId,
    HeroClient client)
    {
    if (string.IsNullOrEmpty(teamId))
    return "Error: Team ID is required";

    try
    {
    if (!Guid.TryParse(teamId, out Guid teamGuid))
    return "Error: Invalid Team ID format";

    // Send the request to complete the mission
    await client.Api.Teams[teamGuid].CompleteMission.PostAsync();

    return $"Mission completed successfully by team {teamId}";
    }
    catch (Exception ex)
    {
    return $"Error completing mission: {ex.Message}";
    }
    }
    }

    [McpServerToolType]
    public static class CreateTeamTool
    {
    [McpServerTool, Description("Create a new team.")]
    public static async Task<string> CreateTeam(
    [Description("name of the team")]string name,
    HeroClient client,
    IMcpServer server)
    {
    if (string.IsNullOrEmpty(name))
    return "Error: Team name is required";

    try
    {
    var command = new CreateTeamCommand
    {
    Name = name
    };

    await client.Api.Teams.PostAsync(command);
    return $"Team '{name}' successfully created!";
    }
    catch (Exception ex)
    {
    return $"Error creating team: {ex.Message}";
    }
    }
    }

    [McpServerToolType]
    public static class ExecuteMissionTool
    {
    [McpServerTool, Description("Execute a mission with a team.")]
    public static async Task<string> ExecuteMission(
    [Description("ID of the team")]string teamId,
    [Description("Description of the mission")]string missionDescription,
    HeroClient client)
    {
    try
    {
    if (!Guid.TryParse(teamId, out Guid teamGuid))
    return "Error: Invalid Team ID format";

    // Create the execute mission command with the description
    var command = new ExecuteMissionCommand
    {
    Description = missionDescription
    };

    // Send the request to the server
    await client.Api.Teams[teamGuid].ExecuteMission.PostAsync(command);

    return $"Team {teamId} has been sent on mission: '{missionDescription}'";
    }
    catch (Exception ex)
    {
    return $"Error executing mission: {ex.Message}";
    }
    }
    }

    [McpServerToolType]
    public static class GetTeamsTool
    {
    [McpServerTool, Description("Get all teams from the API.")]
    public static async Task<string> GetTeams(HeroClient client)
    {
    try
    {
    var teams = await client.Api.Teams.GetAsync();

    if (teams == null || teams.Count == 0)
    return "No teams found.";

    return JsonSerializer.Serialize(teams);
    }
    catch (Exception ex)
    {
    return $"Error retrieving teams: {ex.Message}";
    }
    }
    }

    [McpServerToolType]
    public static class GetTeamTool
    {
    [McpServerTool, Description("Get a specific team by ID.")]
    public static async Task<string> GetTeam(
    [Description("ID of the team to retrieve")]string teamId,
    HeroClient client)
    {
    if (string.IsNullOrEmpty(teamId))
    return "Error: Team ID is required";

    try
    {
    if (!Guid.TryParse(teamId, out Guid teamGuid))
    return "Error: Invalid Team ID format";

    var team = await client.Api.Teams[teamGuid].GetAsync();

    if (team == null)
    return $"No team found with ID {teamId}";

    return JsonSerializer.Serialize(team);
    }
    catch (Exception ex)
    {
    return $"Error retrieving team: {ex.Message}";
    }
    }
    }

Add MCP Server to VS Code

  1. Navigate to MCP servers in settings.json in VS Code

  2. Add the following configuration:

      "heroes": {
    "type": "stdio",
    "command": "dotnet",
    "args": [
    "run",
    "--project",
    "/Users/daniel/Code/Scratch/dotnet-superpowers/test-02/HeroesMcp/src/Mcp/Mcp.csproj"
    ]
    },
    note

    Actual path may vary based on your setup.

  3. Check the server is started and tools are detected

Test

  1. Run the API

    cd src/Api/tools/AppHost
    dotnet run
  2. What heroes are there?

  3. What teams are there?

  4. Create a hero called 'Dan Dan the Solution Man'

  5. Add a hero to the XXX team

  6. Send the XXX team on a mission to save a cat

  7. Send the XXX team on the same mission again (should fail)