C#でMCPサーバーを構築する方法

Model Context Protocol（MCP）は、AI アシスタントが外部のアプリケーションやサービスと通信するための標準化されたプロトコルです。このチュートリアルでは、C#を使用してカスタムMCPサーバーを構築する方法を詳しく説明します。完成したMCPサーバーは、Claude、GPT、その他のAIアシスタントと連携し、データベースへのアクセスやAPIの呼び出しなど、さまざまな機能を実行できるようになります。

MCPサーバーの実装例については、以下のGitHubリポジトリを参考にしてください：
https://github.com/microsoft/TypeChat/tree/main/samples/mcp/csharp

## 目次

1. 前提条件
2. MCPプロトコルの基本概念
3. プロジェクトのセットアップ
4. MCPサーバーの基本構造の実装
5. ルーティングとエンドポイントの作成
6. ツール定義の実装
7. リクエスト処理とレスポンス生成
8. エラーハンドリング
9. MCPサーバーのテスト
10. AIアシスタントとの連携設定
11. 実用例とユースケース

## 1. 前提条件

- Visual Studio または Visual Studio Code
- .NET 6.0 以上
- C#の基本的な理解
- HTTPとJSON形式に関する知識
- MCPの仕様に関する基本的な理解

## 2. MCPプロトコルの基本概念

Model Context Protocol（MCP）は、AIモデルが外部システムとインタラクションするための標準化されたプロトコルです。MCPサーバーの主な役割は次の通りです：

- ツール定義：AIが使用できる機能やコマンドを定義します
- リクエスト処理：AIからのリクエストを受け取り、適切なロジックを実行します
- レスポンス生成：処理結果をAIに返します

基本的なMCP通信の流れは以下の通りです：

1. AIがMCPサーバーにツールのリストをリクエストします
2. MCPサーバーが使用可能なツールの定義を返します
3. AIがツール実行のリクエストを送信します
4. MCPサーバーがツールを実行し、結果を返します

## 3. プロジェクトのセットアップ

まず、新しいASP.NET Core Webアプリケーションを作成します：

1. Visual Studioを開き、「新しいプロジェクト」を選択します
2. 「ASP.NET Core Webアプリケーション」を選択します
3. プロジェクト名を「MCPServer」に設定し、「作成」をクリックします
4. 「空」テンプレートを選択し、「作成」をクリックします

必要なパッケージをインストールします：

```bash
dotnet add package Microsoft.AspNetCore.Mvc.NewtonsoftJson
dotnet add package Newtonsoft.Json
```

## 4. MCPサーバーの基本構造の実装

まず、MCPプロトコルで使用するモデルクラスを作成します。`Models`フォルダを作成し、以下のクラスを追加します：

### MCPTool.cs
```csharp
using System.Collections.Generic;
using Newtonsoft.Json;

namespace MCPServer.Models
{
    public class MCPTool
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("description")]
        public string Description { get; set; }

        [JsonProperty("parameters")]
        public MCPParameters Parameters { get; set; }
    }

    public class MCPParameters
    {
        [JsonProperty("type")]
        public string Type { get; set; } = "object";

        [JsonProperty("properties")]
        public Dictionary<string, MCPProperty> Properties { get; set; }

        [JsonProperty("required")]
        public List<string> Required { get; set; }
    }

    public class MCPProperty
    {
        [JsonProperty("type")]
        public string Type { get; set; }

        [JsonProperty("description")]
        public string Description { get; set; }
    }
}
```

### MCPRequest.cs
```csharp
using System.Collections.Generic;
using Newtonsoft.Json;

namespace MCPServer.Models
{
    public class MCPRequest
    {
        [JsonProperty("name")]
        public string Name { get; set; }

        [JsonProperty("parameters")]
        public Dictionary<string, object> Parameters { get; set; }
    }
}
```

### MCPResponse.cs
```csharp
using Newtonsoft.Json;

namespace MCPServer.Models
{
    public class MCPResponse
    {
        [JsonProperty("content")]
        public string Content { get; set; }

        [JsonProperty("status")]
        public string Status { get; set; } = "success";

        [JsonProperty("error")]
        public string Error { get; set; }
    }
}
```

## 5. ルーティングとエンドポイントの作成

次に、MCPサーバーのエンドポイントを実装します。`Controllers`フォルダを作成し、`MCPController.cs`ファイルを追加します：

```csharp
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MCPServer.Models;
using Microsoft.AspNetCore.Mvc;

namespace MCPServer.Controllers
{
    [ApiController]
    [Route("mcp")]
    public class MCPController : ControllerBase
    {
        [HttpGet("tools")]
        public IActionResult GetTools()
        {
            var tools = GetAvailableTools();
            return Ok(tools);
        }

        [HttpPost("execute")]
        public async Task<IActionResult> ExecuteTool([FromBody] MCPRequest request)
        {
            try
            {
                var response = await ExecuteToolAsync(request);
                return Ok(response);
            }
            catch (Exception ex)
            {
                return BadRequest(new MCPResponse
                {
                    Status = "error",
                    Error = ex.Message
                });
            }
        }

        private List<MCPTool> GetAvailableTools()
        {
            // ここでツールの定義を実装します（次のセクションで実装）
            return new List<MCPTool>();
        }

        private async Task<MCPResponse> ExecuteToolAsync(MCPRequest request)
        {
            // ここでツールの実行ロジックを実装します（セクション7で実装）
            return new MCPResponse();
        }
    }
}
```

次に、`Startup.cs`ファイル（.NET 6以降の場合は`Program.cs`）を編集して、必要なサービスとミドルウェアを設定します：

```csharp
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;

namespace MCPServer
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddControllers().AddNewtonsoftJson();
            services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder.AllowAnyOrigin()
                        .AllowAnyMethod()
                        .AllowAnyHeader();
                });
            });
        }

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseRouting();
            app.UseCors();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });
        }
    }
}
```

## 6. ツール定義の実装

ここでは、AIが使用できるツールを定義します。`MCPController.cs`の`GetAvailableTools`メソッドを実装します：

```csharp
private List<MCPTool> GetAvailableTools()
{
    return new List<MCPTool>
    {
        new MCPTool
        {
            Name = "getCurrentWeather",
            Description = "現在の天気情報を取得します",
            Parameters = new MCPParameters
            {
                Properties = new Dictionary<string, MCPProperty>
                {
                    ["location"] = new MCPProperty
                    {
                        Type = "string",
                        Description = "天気を知りたい場所（例：東京、大阪）"
                    }
                },
                Required = new List<string> { "location" }
            }
        },
        new MCPTool
        {
            Name = "searchDatabase",
            Description = "データベースから情報を検索します",
            Parameters = new MCPParameters
            {
                Properties = new Dictionary<string, MCPProperty>
                {
                    ["query"] = new MCPProperty
                    {
                        Type = "string",
                        Description = "検索クエリ"
                    },
                    ["limit"] = new MCPProperty
                    {
                        Type = "integer",
                        Description = "返す結果の最大数（オプション）"
                    }
                },
                Required = new List<string> { "query" }
            }
        }
    };
}
```

## 7. リクエスト処理とレスポンス生成

次に、AIからのリクエストを処理し、適切なレスポンスを生成するロジックを実装します。`MCPController.cs`の`ExecuteToolAsync`メソッドを実装します：

```csharp
private async Task<MCPResponse> ExecuteToolAsync(MCPRequest request)
{
    switch (request.Name)
    {
        case "getCurrentWeather":
            return await GetWeatherAsync(request.Parameters);
        case "searchDatabase":
            return await SearchDatabaseAsync(request.Parameters);
        default:
            throw new Exception($"不明なツール: {request.Name}");
    }
}

private async Task<MCPResponse> GetWeatherAsync(Dictionary<string, object> parameters)
{
    if (!parameters.TryGetValue("location", out var locationObj) || locationObj == null)
    {
        throw new Exception("場所パラメータが必要です");
    }

    string location = locationObj.ToString();
    
    // ここでは、実際の天気APIの呼び出しをシミュレートしています
    // 実際のアプリケーションでは、外部APIを呼び出すコードを実装します
    await Task.Delay(500); // API呼び出しの遅延をシミュレート
    
    string weatherInfo = location switch
    {
        "東京" => "晴れ、気温25°C",
        "大阪" => "曇り、気温23°C",
        "札幌" => "雨、気温18°C",
        _ => $"{location}の天気データは利用できません"
    };

    return new MCPResponse
    {
        Content = $"{location}の現在の天気: {weatherInfo}"
    };
}

private async Task<MCPResponse> SearchDatabaseAsync(Dictionary<string, object> parameters)
{
    if (!parameters.TryGetValue("query", out var queryObj) || queryObj == null)
    {
        throw new Exception("検索クエリが必要です");
    }

    string query = queryObj.ToString();
    int limit = 10; // デフォルト値

    if (parameters.TryGetValue("limit", out var limitObj) && limitObj != null)
    {
        if (int.TryParse(limitObj.ToString(), out var parsedLimit))
        {
            limit = parsedLimit;
        }
    }

    // ここでは、実際のデータベース検索をシミュレートしています
    // 実際のアプリケーションでは、データベースクエリを実行するコードを実装します
    await Task.Delay(500); // データベース操作の遅延をシミュレート

    var results = new List<string>
    {
        $"検索結果1: {query}に関連する情報",
        $"検索結果2: {query}についての詳細",
        $"検索結果3: {query}の使用例"
    };

    var limitedResults = results.Take(limit);
    string content = string.Join("\n", limitedResults);

    return new MCPResponse
    {
        Content = $"「{query}」の検索結果（最大{limit}件）:\n{content}"
    };
}
```

## 8. エラーハンドリング

より堅牢なエラーハンドリングを実装するために、グローバル例外ハンドラーを追加します。`Middleware`フォルダを作成し、`ErrorHandlingMiddleware.cs`ファイルを追加します：

```csharp
using System;
using System.Net;
using System.Text.Json;
using System.Threading.Tasks;
using MCPServer.Models;
using Microsoft.AspNetCore.Http;

namespace MCPServer.Middleware
{
    public class ErrorHandlingMiddleware
    {
        private readonly RequestDelegate _next;

        public ErrorHandlingMiddleware(RequestDelegate next)
        {
            _next = next;
        }

        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _next(context);
            }
            catch (Exception ex)
            {
                await HandleExceptionAsync(context, ex);
            }
        }

        private static Task HandleExceptionAsync(HttpContext context, Exception exception)
        {
            var response = new MCPResponse
            {
                Status = "error",
                Error = exception.Message
            };

            var result = JsonSerializer.Serialize(response);
            context.Response.ContentType = "application/json";
            context.Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return context.Response.WriteAsync(result);
        }
    }
}
```

そして、このミドルウェアを`Startup.cs`の`Configure`メソッドに追加します：

```csharp
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app.UseMiddleware<ErrorHandlingMiddleware>();
    
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseRouting();
    app.UseCors();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}
```

## 9. MCPサーバーのテスト

MCPサーバーをテストするには、以下の手順に従います：

1. プロジェクトをビルドして実行します：
```bash
dotnet run
```

2. ブラウザやPostmanを使用して、使用可能なツールのリストを取得します：
```
GET http://localhost:5000/mcp/tools
```

3. ツール実行のリクエストを送信します：
```
POST http://localhost:5000/mcp/execute
Content-Type: application/json

{
  "name": "getCurrentWeather",
  "parameters": {
    "location": "東京"
  }
}
```

正常に動作していれば、以下のようなレスポンスが返ってきます：
```json
{
  "content": "東京の現在の天気: 晴れ、気温25°C",
  "status": "success",
  "error": null
}
```

## 10. AIアシスタントとの連携設定

AIアシスタント（Claude、GPTなど）とMCPサーバーを連携させるには、以下の設定が必要です：

### Claude Desktop の場合

`claude_desktop_config.json`ファイルを編集します（macOSの場合は通常`~/Library/Application\ Support/Claude/claude_desktop_config.json`にあります）：

```json
{
  "mcpServers": {
    "customMcp": {
      "command": "dotnet",
      "args": [
        "run",
        "--project",
        "/path/to/your/MCPServer"
      ],
      "env": {}
    }
  }
}
```

### Cursor の場合

`.cursor/mcp.json`ファイルを編集します：

```json
{
  "mcpServers": {
    "customMcp": {
      "command": "dotnet",
      "args": [
        "run",
        "--project",
        "/path/to/your/MCPServer"
      ],
      "env": {}
    }
  }
}
```

## 11. 実用例とユースケース

このMCPサーバーを拡張して、以下のような機能を実装することができます：

### データベース操作

```csharp
private async Task<MCPResponse> QueryDatabaseAsync(Dictionary<string, object> parameters)
{
    // パラメータの検証
    if (!parameters.TryGetValue("sql", out var sqlObj) || sqlObj == null)
    {
        throw new Exception("SQLクエリが必要です");
    }

    string sql = sqlObj.ToString();
    
    // 実際のデータベース接続と操作を実装
    using (var connection = new SqlConnection(_connectionString))
    {
        await connection.OpenAsync();
        using (var command = new SqlCommand(sql, connection))
        {
            using (var reader = await command.ExecuteReaderAsync())
            {
                // 結果をJSONに変換
                var result = new List<Dictionary<string, object>>();
                while (await reader.ReadAsync())
                {
                    var row = new Dictionary<string, object>();
                    for (int i = 0; i < reader.FieldCount; i++)
                    {
                        row[reader.GetName(i)] = reader.GetValue(i);
                    }
                    result.Add(row);
                }
                
                string jsonResult = JsonConvert.SerializeObject(result, Formatting.Indented);
                return new MCPResponse { Content = jsonResult };
            }
        }
    }
}
```

### ファイル操作

```csharp
private MCPResponse ReadFile(Dictionary<string, object> parameters)
{
    if (!parameters.TryGetValue("path", out var pathObj) || pathObj == null)
    {
        throw new Exception("ファイルパスが必要です");
    }

    string path = pathObj.ToString();
    
    // セキュリティ上の考慮: 許可されたディレクトリのみアクセスを許可
    if (!IsPathSafe(path))
    {
        throw new Exception("このパスへのアクセスは許可されていません");
    }
    
    if (!System.IO.File.Exists(path))
    {
        throw new Exception($"ファイルが見つかりません: {path}");
    }
    
    string content = System.IO.File.ReadAllText(path);
    return new MCPResponse { Content = content };
}
```

## まとめ

このチュートリアルでは、C#を使用してカスタムMCPサーバーを構築する方法を説明しました。基本的なMCPプロトコルの実装、ツール定義、リクエスト処理、レスポンス生成、エラーハンドリング、そしてAIアシスタントとの連携方法について学びました。

このMCPサーバーの実装を拡張して、データベース操作、ファイル操作、外部APIとの連携など、さまざまな機能を追加することができます。また、セキュリティ対策やパフォーマンス最適化も検討することが重要です。

C#でMCPサーバーを構築することで、AIアシスタントとあなたのアプリケーションやサービスを連携させる強力なインターフェースを作成できます。これにより、AIの能力を活用した革新的なアプリケーションの開発が可能になります。

MCPプロトコルは発展を続けているため、最新の仕様に常に注目し、必要に応じてサーバーを更新することをお勧めします。

このチュートリアルが、AIとのインテグレーションの第一歩として役立つことを願っています。