Zenn
👻

MCP サーバーを自作して GitHub Copilot の Agent に可読性の低いクラス名を作ってもらう

に公開
17

はじめに

今まで Model Context Protocol (MCP) の C# SDK を調べて記事を書いてきました。

今回は MCP サーバーを自作して Visual Studio Code の GitHub Copilot の Agent から使ってもらおうと思います。

プロジェクトの作成と下準備

ASP.NET Core の Web API のプロジェクト(Minimal API) を作成して、そこに以下のパッケージを追加します。

  • ModelContextProtocol.AspNetCore v0.1.0-preview.4

余談ですが 2 日前にはこのパッケージは無くて ModelContextProtocol パッケージの v0.1.0-preview.2 があるだけだったのですが…進化が早いですね。

そして Program.cs を以下のように変更します。

Program.cs
using ModelContextProtocol.AspNetCore;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMcpServer()
    .WithToolsFromAssembly();

var app = builder.Build();
app.MapMcp();

app.Run();

プルリクを見ている限り次の preview 版では先頭の using は不要になるようです。MapMcp の拡張メソッドの名前空間が ModelContextProtocol.AspNetCore から Microsoft.AspNetCore.Builder に変更されています。より使いやすくなりますね。

ツールの準備

これで MCP サーバーは起動するのですが、何もツールが無いので何もできません。そこで、MCP サーバーにツールを追加します。今回は SIer 案件でありがちな業務コードをクラス名にしているクラス名をつけるツールを追加します!
従うべきルールの概要を提供する GetNamingRules メソッドと、業務カテゴリに応じたクラス名を生成する GenerateClassNamingConvention メソッド、クラス名から業務カテゴリを取得する DetermineBusinessCategory メソッドを追加します。

Program.cs
using ModelContextProtocol.Server;
using System.ComponentModel;
using System.Text.Json.Serialization;

namespace McpServerSse.Tools;

[McpServerToolType]
[Description()]
public class NamingConventionManager
{
    // 連番管理用のカウンタ
    // お試しなので static 変数にするけど本番でちゃんとやるなら DB に保存するなどの対応が必要
    static int _counter = 0;

    [McpServerTool, Description("""
        プログラムを追加や変更する際に従う必要があるルールを提供します。必ずこのルールを守る必要があるので参照してください。
        """)]
    public string GetNamingRules()
    {
        return """
            このプロジェクトでは管理を容易にするためにクラス名に業務カテゴリに準じた命名規約に従う必要があります。
            この業務カテゴリに応じたクラス名は `GenerateClassNamingConvention` ツールによって提供されます。
            ここで提供された名前でクラスを定義してください。他の名前でクラスは絶対に定義しないでください。

            クラス名から何を行うのかわからない場合は `DetermineBusinessCategory` ツールを使用して業務カテゴリを取得してください。
            """;
    }

    [McpServerTool, Description("""
        指定した業務のカテゴリのクラスを作成際に作成すべき一連のクラスと名前空間を取得します。
        ここで提案されたクラス名を使用したクラスを作成する必要があります。
        """)]
    public ClassNamingConvention GenerateClassNamingConvention(
        [Description("作成するクラスのカテゴリ")]
        BusinessCategory businessCategory)
    {
        var number = _counter++;
        var prefix = businessCategory switch
        {
            BusinessCategory.Order => "A",
            BusinessCategory.Product => "B",
            BusinessCategory.Customer => "C",
            BusinessCategory.Supplier => "D",
            BusinessCategory.Employee => "E",
            _ => throw new ArgumentException("不明なカテゴリです。"),
        };

        var name = $"{prefix}{number:D4}";
        return new ClassNamingConvention(
            ServiceNamespace: "{YourRootNamespace}.Services",
            ServiceClassName: $"{name}Service",
            UsecaseNamespace: "{YourRootNamespace}.Usecases",
            UsecaseClassName: $"{name}Usecase",
            DtoNamespace: "{YourRootNamespace}.Dtos",
            DtoClassName: $"{name}Dto");
    }

    [McpServerTool, Description("クラス名から業務カテゴリがわからない場合に命名規約を確認してクラスが所属する業務カテゴリを取得します。")]
    public BusinessCategory DetermineBusinessCategory(
        [Description("クラス名")]
        string className)
    {
        ArgumentException.ThrowIfNullOrEmpty(className);

        var prefix = className[0];
        return prefix switch
        {
            'A' => BusinessCategory.Order,
            'B' => BusinessCategory.Product,
            'C' => BusinessCategory.Customer,
            'D' => BusinessCategory.Supplier,
            'E' => BusinessCategory.Employee,
            _ => throw new ArgumentException("不明なクラス名です。"),
        };
    }
}

[Description("使用するクラス名")]
public record ClassNamingConvention(
    [Description("サービス名前空間")]
    string ServiceNamespace,
    [Description("サービスレイヤーの場合に使用するクラス名")]
    string ServiceClassName,
    [Description("ユースケース名前空間")]
    string UsecaseNamespace,
    [Description("ユースケースレイヤーの場合に使用するクラス名")]
    string UsecaseClassName,
    [Description("DTO名前空間")]
    string DtoNamespace,
    [Description("DTOに使用するクラス名")]
    string DtoClassName);

[JsonConverter(typeof(JsonStringEnumConverter))]
public enum BusinessCategory
{
    Order,
    Product,
    Customer,
    Supplier,
    Employee,
}

そして、プロジェクトを実行してサーバーを起動しておきます。http のエンドポイント (https は失敗しました…) を控えておきます。

GitHub Copilot の Agent を MCP サーバーに接続する

次に GitHub Copilot の Agent を MCP サーバーに接続します。GitHub Copilot の Agent は MCP サーバーのエンドポイントを指定するだけで接続できます。
以下のように Agent モードを選択してスパナのアイコンをクリックすることでサーバーが追加できます。

Add MCP Server から HTTP を選んで http://localhost:<<PORT>>/sse を指定します。名前を適当に設定して、MCP サーバーの設定の保存場所(User Settings か Workspace Settings) を選択します。自分で使うだけなら User Settings で良いと思います。Workspace Settings にするとチームで共有するときなどに便利です。今回はお試しで、このワークスペースだけで使いたいので Workspace Settings にしました。

そうすると .vscode/mcp.json が以下のような内容で作成されます。

mcp.json
{
    "servers": {
        "my-mcp-server-5b3bb2a1": {
            "type": "sse",
            "url": "http://localhost:5048/sse"
        }
    }
}

この JSON 上に Start アイコンがあるので、そこから MCP サーバーを起動します。すると、以下のように MCP サーバーが起動します。他にも GitHub Copilot の Chat ウィンドウからも起動したりできます。

起動して GitHub Copilot の Chat ウィンドウのスパナのアイコンを選択すると、接続した MCP サーバーにあるツールの一覧が出てきます。ばっちり。

ツールを使ってみる

では実際に使ってみましょう。.NET のコンソールアプリのプロジェクトのフォルダーを VS Code で開いて以下のようなお願いを Agent にしてみました。

いい感じにルールを確認しようとしてくれていますね。続けて以下のように実際に作成するクラス名などを調べようとしてくれます。

そして命名規約に従って名前空間を調べたりフォルダーを作ろうとしてくれます。いい感じ。

フォルダーの作成が終わると以下のように、ちゃんと指定したクラス名でクラスが作成されました。

業務カテゴリを質問した場合も、ちゃんとツールを使って業務カテゴリを調べてくれます。

まとめ

今回は MCP サーバーを自作して GitHub Copilot の Agent から使ってもらう方法を紹介しました。
MCP サーバーでツールをうまく追加することで Agent の挙動をある程度制御することが出来そうです。

今回使用したソースコードは以下の GitHub リポジトリに置いてあります。まさしく日進月歩でライブラリの開発が進んでいるので明日には動かなくなるかもしれないですが興味のある方は参考にしてみてください。

https://github.com/runceel/McpServerSse

真面目な活用方法などの記事については、こちらもあわせて参考するといいと思います。

Visual Studio Code Insider と GitHub Copilot agent mode (preview) を使って MCP サーバーを活用する

17
Microsoft (有志)

Discussion

ログインするとコメントできます