🥷

C#でMCP Serverを作る

に公開

MCP とは

と、大上段に書いてみましたが、例によって例のごとく、MCP(ModelContextProtocol)については本人の認識がまだ薄いため、解説はほかのサイトで!ということでお願いします。

うっすらとした認識でいうと、 AIモデルから使用されるユーティリティ関数群 というところです。
AIが「知らない」話題に関して、ユーティリティ関数とやり取りして情報を得、次の動作につなげていく機能のカケラ、でしょうか。

MCPサーバーの種類

MCPサーバーには以下の種類があります。

  • 標準入出力型
  • Webサーバー型

今回は両方とも作り方を説明します。

サンプルは こちら

サーバー用共通部分

まずは、サーバー用の共通部分を作ります。
こちらは上記の ユーティリティ関数 になります。

作成

$ mkdir McpServerCommon ; cd McpServerCommon
$ dotnet new classlib
$ dotnet add package ModelContextProtocol --prerelease

コードの修正

できた Class1.csBookTool.cs に変更し、内容を以下にします。

BookTool.cs
using ModelContextProtocol.Server;
using System.ComponentModel;

namespace McpServerCommon;

[McpServerToolType]
public class BookTool
{
    [McpServerTool, Description("注目の漫画の情報を取得します")]
    public static BookData GetFeaturedBook()
    {
        return new BookData
        {
            ISBN = "9784046818782",
            Title = "魔術師クノンは見えている",
            Volume = 1,
        };
    }
}

public class BookData
{
    [Description("ISBNコード")]
    public string ISBN { get; set; } = string.Empty;

    [Description("タイトル")]
    public string Title { get; set; } = string.Empty;

    [Description("巻数")]
    public int Volume { get; set; } = 1;
}

標準入出力型のMCPサーバーアプリケーション

基本的には、 .NET を使用して最小限の MCP サーバーを作成して接続する に書かれていることそのままです。

作成

$ mkdir McpServerStdio ; cd McpServerStdio
$ dotnet new console
$ dotnet add package ModelContextProtocol --prerelease
$ dotnet add package Microsoft.Extensions.Hosting
$ dotnet add reference ../McpServerCommon

コードの修正

Program.cs を以下の内容に変更します。

Program.cs
using McpServerCommon;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using ModelContextProtocol.Server;
using System.ComponentModel;

// Create a generic host builder for
// dependency injection, logging, and configuration.
var builder = Host.CreateApplicationBuilder(args);

// Configure logging for better integration with MCP clients.
builder.Logging.AddConsole(consoleLogOptions =>
{
    consoleLogOptions.LogToStandardErrorThreshold = LogLevel.Trace;
});

// Register the MCP server and configure it to use stdio transport.
// Scan the assembly for tool definitions.
builder.Services
    .AddMcpServer()
    .WithStdioServerTransport()
    .WithToolsFromAssembly(typeof(BookTool).Assembly);

// Build and run the host. This starts the MCP server.
await builder.Build().RunAsync();

上記のページほとんどそのままです。

試してみる

Visual Studio Code の GitHub Copilot Chat を使って動作を確認します。
McpServerStdio ディレクトリに移動し、下記ディレクトリとファイルを作成したのち、Visual Studio Codeを起動します。

$ mkdir .vscode
$ touch .vscode/mcp.json
$ code .

.vscode/mcp.json を以下の内容にします。

mcp.json
{
  "inputs": [],
  "servers": {
    "McpServerStdio": {
      "type": "stdio",
      "command": "dotnet",
      "args": [
        "run"
      ]
    }
  }
}

Ctrl + Alt + i を押し、Copilot Chat を起動して 注目の漫画の情報を と入力します。

上記 共通部分 で作った関数が読み込まれて実行されていることがわかります。

Webサーバー型MCPサーバーアプリケーション

こちらは ASP.NET Core extensions for the MCP C# SDK に書かれていることそのままです。

作成

$ mkdir McpServerSse ; cd McpServerSse
$ dotnet new web
$ dotnet add package ModelContextProtocol.AspNetCore --prerelease
$ dotnet add reference ../McpServerCommon

コードの修正

Program.cs を以下の内容に変更します。

Program.cs
using McpServerCommon;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly(typeof(BookTool).Assembly);
var app = builder.Build();

app.MapMcp();

app.Run("http://localhost:3001");

上記のページほとんどそのままです。

試してみる

標準入出力型と同じようにVSCodeで動作確認です。

$ mkdir .vscode
$ touch .vscode/mcp.json
$ code .
$ dotnet run

.vscode/mcp.json を以下の内容にします。

mcp.json
{
  "inputs": [],
  "servers": {
    "McpServerSse": {
      "type": "sse",
      "url": "http://localhost:3001/"
    }
  }
}

Ctrl + Alt + i を押し、Copilot Chat を起動して 注目の漫画の情報 と入力します。

Webサーバー型MCPサーバーアプリケーション with DI

せっかくC#でプログラムしているので、Webサーバー型のものに少し手を入れてDIしてみましょう。
ここでは MySql.EntityFrameworkCore を使ってDBからデータを取得できるようにしてみます。

Program.cs
using McpServerSseEF;
using Microsoft.EntityFrameworkCore;
using ModelContextProtocol.Server;
using MySql.Data.MySqlClient;
using System.ComponentModel;

var builder = WebApplication.CreateBuilder(args);
builder.Services
    .AddDbContext<BookContext>(options =>
        options.UseMySQL(ShadowContent.ConnectionString));
builder.Services.AddMcpServer()
    .WithHttpTransport()
    .WithToolsFromAssembly();
var app = builder.Build();

app.MapMcp();

app.Run("http://localhost:3001");

[McpServerToolType]
public class BookTool
{
    [McpServerTool, Description("ISBNコードから漫画情報を取得")]
    public static BookData GetBookDataFromIsbn(
        BookContext db,
        [Description("ISBNコード")] string isbn)
    {
        return db
            .Database
            .SqlQueryRaw<BookData>(ShadowContent.SQL_GetBookDataFromCode, new[] {
                new MySqlParameter("isbn", isbn)
            })
            .FirstOrDefault() ?? new BookData
            {
                ISBN = isbn,
                Title = "Unknown",
                Volume = 0
            };
    }
}

見てもらえば一目瞭然なのですが、 builder.Services.AddDbContext<>() で普通にDI登録しています。
そして、 GetBookDataFromIsbn(BookContext db, という風にMCPの関数の引数でDI取得をしています。

試してみる

上記のWebサーバー型のものと同じように試してみると。

Continueを押して続けてみると、

データが取得できました!

Webサーバー型に関してその他

通常のWebサーバーにMCPサーバーを混ぜる

何やら、 .MapMcp() をすると、ルートが取られるようで。

app.MapMcp();
app.MapGet("/", () => "MCP Server is running!");

こういうコードにすると、

となってしまいます。
ルートを外してパスを構築しましょう

app.MapMcp();
app.MapGet("/home", () => "MCP Server is running!");

まとめ

AIに関しては迷走感があるC#ですが、MCPに関しては対応が早いですね。
これだけの短いコードで構築できるのは楽です。

Discussion