MCP
Setup
-
Create directory
mkdir HeroesMcp
cd HeroesMcp -
Create project
dotnet new ssw-ca -
Create MCP directory
cd src
mkdir Mcp
cd Mcp -
Create new project
dotnet new console --framework net9.0 -
Add MCP to the solution
dotnet sln ../.. add Mcp.csproj -
Add packages
dotnet add package Microsoft.Extensions.Hosting
dotnet add package Microsoft.Kiota.Bundle
dotnet add package ModelContextProtocol --prerelease -
Open Code in Rider
rider . -
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(); -
Update port in
AppHost/Program.csto be random
Generate API Client
-
Create a new directory for the API client
cd src/Mcp
mkdir ApiClient
cd ApiClient -
Run the Api
aspire run -
Ensure kiota is installed
cd {your-project-root}dotnet new tool-manifest
dotnet tool install Microsoft.OpenApi.Kiota -
Generate API Client
cd src/Mcpdotnet kiota generate --openapi https://localhost:7255/openapi/v1.json --language csharp --class-name HeroClient --clean-output --additional-data false -
Add DI for API Client
Program.csvar authProvider = new AnonymousAuthenticationProvider();
var adapter = new HttpClientRequestAdapter(authProvider);
builder.Services.AddSingleton<IRequestAdapter>(adapter);
builder.Services.AddSingleton<HeroClient>();
Create MCP Server
-
Add MCP DI
Program.csbuilder.Services
.AddMcpServer()
.WithStdioServerTransport()
.WithToolsFromAssembly();
Create Tools
-
Create a new directory for the tools
mkdir Tools
cd Tools -
Add CreateHeroTool
CreateHeroTool.csusing 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}";
}
}
}infoThere 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.
-
Add GetHeroesTool
GetHeroesTool.csusing 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}";
}
}
} -
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
-
Navigate to MCP servers in
settings.jsonin VS Code -
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"
]
},noteActual path may vary based on your setup.
-
Check the server is started and tools are detected
Test
-
Run the API
cd src/Api/tools/AppHost
dotnet run -
What heroes are there?
-
What teams are there?
-
Create a hero called 'Dan Dan the Solution Man'
-
Add a hero to the XXX team
-
Send the XXX team on a mission to save a cat
-
Send the XXX team on the same mission again (should fail)