Unity で Microsoft Extensions AI を使って AI エージェントを開発する

はじめに
Microsoft が開発している C# 向けのライブラリである Microsoft.Extensions.AI を使って、Unity で AI エージェントを開発する方法を紹介します。
作ったもの
今回解説する仕組みを応用して以下の動画のような仕組みを作ってみました。
これは、ネットワーク内に存在するデバイスを見つけ、対象デバイスが提供している MCP サーバを介して様々な処理を行う、ということを実現しています。
今回の記事はこの仕組みを実装したときに学んだことをまとめたものになります。
本ライブラリについてはドキュメントから引用すると以下のように説明されています。
.NET 開発者は、増え続けるさまざまな人工知能 (AI) サービスをアプリに統合して対話する必要があります。 Microsoft.Extensions.AI ライブラリは、生成型 AI コンポーネントを表す統合されたアプローチを提供し、さまざまな AI サービスとのシームレスな統合と相互運用性を実現します。
今回はこのライブラリを Unity で使って、AI エージェントを開発する際の実装方法について簡単に解説します。このライブラリを使うことのメリットは、様々な LLM に対する実装がインターフェースを通じて利用可能であり、異なる LLM に対応するためのコードの変更が不要であることです。また、Function Calling や画像を使ったやり取り、さらに MCP の公式実装もこれに対応しているため、MCP を利用したエージェントも開発可能な点が挙げられます。
LLM を用いたちょっとしたモックアプリなどはすぐに開発を始めることができます。プロトタイプ開発にもぜひ役立ててください。
開発環境
今回紹介するのは以下の環境で確認したものです。が、Unity のバージョンなどは多少古いものでも問題なく動くと思います。
- Unity 6000.0.58f2
- Windows 11
- macOS Tahoe 26.1
環境構築
さて、さっそく Unity で動くコードを・・・と行きたいところなのですが、このライブラリは NuGet を用いてインストールする必要があります。Unity 向けの NuGet パッケージマネージャはいくつかありますが、今回は NuGetForUnity を使ってインストールを行います。
NuGetForUnity 自体のインストールについては上記 GitHub の README を参考にしてください。
Package Manager を起動
NuGetForUnity をインストールしたら、ウィンドウメニューから Package Manager を起動します。

必要なパッケージのインストール
マネージャが起動したら、以下のパッケージをインストールしてください。
- Microsoft.Extensions.AI
- Microsoft.Extensions.AI.OpenAI
- Microsoft.Extensions.Configuration
- ModelContextProtocol
すべてインストールすると Assets/Packages フォルダの中に、依存関係も含めて多数のパッケージがインストールされます。
シンプルなチャットクライアントを実装する
まずはどんなふうに実装していくのかをイメージできるように、シンプルなチャットクライアントを実装してみます。
主に利用するのは以下です。
IChatClientOpenAIClientApiKeyCredentialChatMessageChatMessageRoleChatResponseUpdate
あたりです。
公式サンプルに載っている内容を引用しつつ、Unity で動かす前提で実装したのが以下のコードです。長くないので全文掲載します。
using System.ClientModel;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using Microsoft.Extensions.AI;
using OpenAI;
public class SimpleChatClient : MonoBehaviour
{
[SerializeField] private string _openAiApiKey;
[SerializeField] private string _modelName = "gpt-5.1";
private IChatClient _chatClient;
private void Start()
{
ApiKeyCredential credential = new ApiKeyCredential(_openAiApiKey);
_chatClient = new OpenAIClient(credential)
.GetChatClient(_modelName)
.AsIChatClient();
string systemPrompt = "You are a friendly hiking enthusiast who helps people discover fun hikes in their area.\n" +
"You introduce yourself when first saying hello.\n" +
"When helping people out, you always ask them for this information\n" +
"to inform the hiking recommendation you provide:\n" +
"\n" +
"1. The location where they would like to hike\n" +
"2. What hiking intensity they are looking for\n" +
"\n" +
"You will then provide three suggestions for nearby hikes that vary in length\n" +
"after you get that information. You will also share an interesting fact about\n" +
"the local nature on the hikes when making a recommendation. At the end of your\n" +
"response, ask if there is anything else you can help with.\n" +
"Start the conversation with context for the AI model\n";
List<ChatMessage> chatHistory = new();
chatHistory.Add(new ChatMessage(ChatRole.System, systemPrompt));
chatHistory.Add(new ChatMessage(ChatRole.User, "Hi!"));
SendMessage(chatHistory);
}
private async void SendMessage(List<ChatMessage> chatHistory)
{
StringBuilder stringBuilder = new();
await foreach (ChatResponseUpdate item in _chatClient.GetStreamingResponseAsync(chatHistory))
{
Debug.Log(item.Text);
stringBuilder.Append(item.Text);
}
chatHistory.Add(new ChatMessage(ChatRole.Assistant, stringBuilder.ToString()));
Debug.Log(stringBuilder.ToString());
}
}
大まかな流れは以下です。
- API キーを
ApiKeyCredentialを利用して指定する - 上記クレデンシャル情報を用いて OpenAI 向けのクライアントを作成する
- システムプロンプトを記述し、それを
ChatRole.SystemとしてChatMessageリストに格納する -
ChatRole.Userロールにユーザーの入力を指定してChatMessageリストに格納する -
GetStreamingResponseAsyncメソッドを呼び出してストリーミングレスポンスを取得する - ストリーミングで取得したテキストを
StringBuilderに格納し、最後にそれを出力する
API Key についてはご自身で取得して任意の方法で指定してください。今回は [SerializeField] にしてインスペクタで指定する形になっています。
なお、今回は簡単のために OpenAI クライアントをハードコードしていますが、ここを柔軟に切り替えられる設計にしておくことで自由に LLM を切り替えることも可能です。
ストリーミングで結果を受け取る
LLM の呼び出しは非同期処理でストリーミングで内容を受け取ることができます。具体的には以下の部分ですね。
StringBuilder stringBuilder = new();
await foreach (ChatResponseUpdate item in _chatClient.GetStreamingResponseAsync(chatHistory))
{
Debug.Log(item.Text);
stringBuilder.Append(item.Text);
}
await foreach によってストリーミングの内容を非同期に受け取ることができます。今回は StringBuilder に格納して最後にログ出力するだけになっていますが、例えばここをユーザの目に触れるフィールドに表示することで、できるだけ早く LLM からのレスポンスを表示するということもできます。
履歴に保存
最後に、LLM からの応答を ChatRole.Assistant としてリストに保存します。これによってマルチターンの会話も可能となります。
Unity であれば Input Field にテキストを入力してもらい、それを送信する仕組みを作れば LLM と会話することが可能となります。
画像を使ったマルチモーダルエージェントの実装
前段ではテキストを用いた LLM とのやり取りの実装について紹介しました。しかし、最近の LLM はどんどん進化しており、画像を使ったマルチモーダルなエージェントも当たり前になってきています。
そこで次は、画像をリクエストに含めてマルチモーダルなエージェントを実装する方法を解説します。といっても、基本的な流れは前段と同じで、画像を含める部分だけが異なります。
ということで、画像をリクエストに含める方法に焦点を当てて解説します。
画像を含める方法
ChatClient は前段のものとまったく同じです。また、システムプロンプトの指定なども同様です。
唯一の違いは、ユーザの入力をどう渡すかです。が、それも仕組み化されているので実装はシンプルです。
string filePath = Path.Combine(Application.dataPath, _imagePath);
byte[] imageBytes = await File.ReadAllBytesAsync(filePath);
var message = new ChatMessage(ChatRole.User, new AIContent[]
{
new TextContent("この画像に何が写っているか説明してください。"),
new DataContent(imageBytes, "image/jpeg"),
});
前回は ChatMessage クラスのコンストラクタに string を直に指定していました。今回はそこに AIContent の配列を渡すことで画像を含めることができます。
大事な点は、ユーザのテキスト入力は TextContent として指定し、画像は DataContent として、さらに MIME Type を同時に指定することです。
画像を使ったマルチモーダル LLM の利用方法は以上です。今回のサンプルでは画像をファイルから読み込んでそれを送っていますが、最終的にはバイナリ( byte[] )なので、例えば冒頭のアプリの例では WebCamTexture をそのままバイナリ化して送っています。
あるいはゲーム画面をキャプチャしてそれを送る、なんていう使い方もできるでしょう。
C# メソッドを Function Calling させる
最近の LLM の活用では、もちろんマルチモーダルも大きな進化ですが、Function Calling という機能でツールを利用させることもとても重要な機能となりました。
Microsoft.Extensions.AI では C# のメソッドをツール化して指定し、LLM から呼び出すということも可能になっています。今回はツールの指定方法について見ていきます。
クライアントの作成にはビルダーを用いる
今までは OpenAIClient を直に生成していましたが、Function Calling を使う場合はビルダーを使います。
_client = new ChatClientBuilder(
new OpenAIClient(_apiKey)
.GetChatClient(_modelName)
.AsIChatClient())
.UseFunctionInvocation()
.Build();
ビルダーに渡しているクライアントは前段の OpenAIClient です。そのあとに UseFunctionInvocation を呼び出してツール呼び出しを有効にしています。最後に Build を呼び出してクライアントを生成します。
C# メソッドをツール化する
LLM に指定するツールは AITool というクラスが担います。これをリストで渡すことで LLM が適切にツールを判断して呼び出すことができるようになります。AITool の作成は AIFunctionFactory を使います。
AITool tool = AIFunctionFactory.Create(new Action<string>(SampleMethod),
"sample_method",
"This function is sample method.");
ChatOptions chatOptions = new ChatOptions()
{
Tools = new AITool[] { tool, },
};
ChatResponse response = await _chatClient.GetResponseAsync(chatHistory, chatOptions);
// --------------------------------------
private void SampleMethod(string param)
{
Debug.Log(param);
}
AIFunctionFactory.Create メソッドの第一引数は Delegate 型を渡します。第二引数にはメソッドの名前を、第三引数には説明を指定します。これらを指定することで LLM がどのツールを指定したらいいかが分かるというわけなんですね。
あとは作成したツールを ChatOptions の Tools プロパティに渡し、LLM の呼び出しメソッド GetResponseAsync に渡すだけです。
こうしてリクエストを送ることで LLM は、必要であればツールを呼び出してくれるようになります。
MCP サーバを利用する
最後に紹介するのは MCP サーバの利用方法についてです。やはり LLM を使う上で MCP が使えると一気にできることが増えるのでぜひ使いたい機能です。
実は ModelContextProtocol パッケージは Microsoft.Extensions.AI に対応しており、MCP サーバから提供されている機能リストは Microsoft.Extensions.AI 内で定義されている AIFunction インターフェースを実装しています。またこの AIFunction は前段で説明した AITool の拡張となっており、LLM の呼び出し時にそのまま渡すことができます。
ということで、MCP サーバを用いてツール呼び出しを行う実装について解説していきます。
なお、公式ドキュメントにも MCP クライアントに関するものがあるので、合わせてそちらも参考にしてください。
MCP とは
MCP サーバに接続するのに知っておいたほうがいい知識などもあるので軽く触れておきます。
MCP とは Model Context Protocol の略で、Claude を開発している Anthropic が提唱しているプロトコルです。プロトコルなので専用の実装があるわけではありません。しかし、公式が作成している SDK があり、Python と C# での実装があります。今回はこの C# SDK を利用しています。
MCP との接続
MCP はホストとクライアント、サーバという 3 者がいて、クライアントとサーバが 1対1 で対応し、ホストが複数クライアントを束ねて利用する形を取っています。
公式ドキュメントの図を引用すると以下のように説明されています。

ここで言うホストとは、例えば Claude Desktop や Cursor などを指します。もちろん、自作アプリが MCP クライアントを作成して MCP サーバと通信する形にすれば自作アプリも MCP ホストとなります。
詳細についてはドキュメントを参照ください。ここではこの 3 者がいることと、クライアントがサーバと通信することを把握しておいてください。
MCP サーバとの通信方式
サーバとの通信に用いられるトランスポートは大きく 2 種類が規定されています。
- 標準入出力
- Streamable HTTP
MCP は通信方式として JSON-RPC を採用しています。そしてクライアントとサーバとのトランスポートとして上記 2 つが規定されています。それぞれ簡単に解説します。
まず標準入出力を用いたやり取り。これは主に、ローカルに立てた MCP サーバと通信を行うために利用されます。例えば Claude Desktop などで設定するのは主にこちらのローカル MCP サーバです。標準入出力を介してやり取りするだけなのでとてもシンプルです。大体の場合は Python や Node.js で作られたサーバを、コマンドを介してホストが起動する形 を取ります。
もう一方トランスポートは Streamable HTTP です。こちらは SSE ベースのものが初期にありましたが、現状は StremableHTTP を用いるのがスタンダードなようです。
今回は特に Unity アプリなのでモバイルも視野に入れており、冒頭紹介した動画はまさに iOS アプリです。
そうしたことから、ローカルでサーバを起動する方法はむずかしいため、今回はリモートサーバと通信を行う方法について解説します。
トランスポートの詳細についてはドキュメントを参照ください。
MCP サーバとのコネクション
前述したように、MCP ではクライアントとサーバが 1対1 でひも付きます。そこでまず、接続先のサーバのトランスポートの設定を行い、続いてクライアントの作成を行います。
コード例は以下です。
[SerializeField] private string _mcpEndpoint = "http://localhost:8081/mcp";
// ---------------------------
HttpClientTransport transport = new HttpClientTransport(new HttpClientTransportOptions()
{
Name = "McpClientTest",
Endpoint = new Uri(_mcpEndpoint),
TransportMode = HttpTransportMode.StreamableHttp,
});
McpClient mcpClient = await McpClient.CreateAsync(transport);
まず最初に HttpClientTransport クラスのインスタンスを作成します。その際に HttpClientTransportOptions クラスのインスタンスを生成し、設定としてエンドポイントとトランスポートモードを指定します。前述したように以前は SSE も規定されていたため、後方互換のためにモードが残されているのだと思います。
今回は Stremable HTTP を使うため HttpTransportMode.StreamableHttp を指定します。エンドポイントは今回はローカルサーバを指定していますがもちろん、これをリモートサーバに変更することも可能です。スマホ向けにビルドする際はここを PC の IP アドレスにすることで簡単にテストすることができます。
そして作成したトランスポートのインスタンスを引数にして McpClient を作成します。
MCP サーバのツール取得
クライアントが作成できたら、実際にツールを取得します。
コード例は以下です。
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
非常にシンプルです。クライアントの ListToolsAsync メソッドを呼び出すだけです。返されるのは McpClientTool 型のリストです。そしてこの McpClientTool が、前述した AIFunction を実装しているため、そのまま Microsoft.Extensions.AI の仕組みに渡すことができます。
MCP ツールリストを渡して LLM を呼び出す
ただ、もちろん Microsoft.Extensions.AI 側は MCP の実装は知らないためアップキャストして渡す必要があります。
それを行って実際に LLM を呼び出すコード例が以下です。
// MCP のツールをアップキャストしてリスト化する
IList<AITool> aiTools = tools.Select(t => t as AITool).ToList();
// プロンプトを書く
List<ChatMessage> messages = new List<ChatMessage>();
messages.Add(new ChatMessage(ChatRole.System, "MCPサーバのツール利用診断をしてください。"));
messages.Add(new ChatMessage(ChatRole.User, "ライトの電源オン・オフのツールはありますか? ある場合は電源オンリクエストを送ってください。"));
// LLM を呼び出す
StringBuilder sb = new StringBuilder();
List<ChatResponseUpdate> updates = new List<ChatResponseUpdate>();
await foreach (ChatResponseUpdate update in _client.GetStreamingResponseAsync(messages, new ChatOptions()
{
Tools = aiTools
}))
{
sb.Append(update);
updates.Add(update);
}
冒頭の部分がアップキャストしている箇所です。AITool を継承したインターフェースを実装しているため安全にキャストすることができます。
あとは前述の例と同様にプロンプトと書いて、ChatOptions にツールリストを渡して呼び出すだけです。こうすることで、LLM が適切に MCP ツールを呼び出すことができるようになります。
MCP ツールを使って LLM を呼び出すコード全文
長くないので全部も掲載しておきます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Cysharp.Threading.Tasks;
using UnityEngine;
using Microsoft.Extensions.AI;
using ModelContextProtocol.Client;
using OpenAI;
public class McpClientTest : MonoBehaviour
{
[SerializeField] private string _mcpEndpoint = "http://localhost:8081/mcp";
[SerializeField] private string _apiKey;
[SerializeField] private string _modelName;
private IChatClient _client;
private void Start()
{
SetupClient().Forget();
}
private async UniTask SetupClient()
{
_client = new ChatClientBuilder(
new OpenAIClient(_apiKey)
.GetChatClient(_modelName)
.AsIChatClient())
.UseFunctionInvocation()
.Build();
HttpClientTransport transport = new HttpClientTransport(new HttpClientTransportOptions()
{
Name = "McpClientTest",
Endpoint = new Uri(_mcpEndpoint),
TransportMode = HttpTransportMode.StreamableHttp,
});
McpClient mcpClient = await McpClient.CreateAsync(transport);
IList<McpClientTool> tools = await mcpClient.ListToolsAsync();
List<ChatMessage> messages = new List<ChatMessage>();
messages.Add(new ChatMessage(ChatRole.System, "MCPサーバのツール利用診断をしてください。"));
messages.Add(new ChatMessage(ChatRole.User, "ライトの電源オン・オフのツールはありますか? ある場合は電源オンリクエストを送ってください。"));
StringBuilder sb = new StringBuilder();
List<ChatResponseUpdate> updates = new List<ChatResponseUpdate>();
IList<AITool> aiTools = tools.Select(t => t as AITool).ToList();
await foreach (ChatResponseUpdate update in _client.GetStreamingResponseAsync(messages, new ChatOptions()
{
Tools = aiTools
}))
{
sb.Append(update);
updates.Add(update);
}
Debug.Log(sb);
}
}
(余談)ツールの明示的呼び出し
MCP ツールは基本的に LLM が呼び出し、利用についてもライブラリ側で自動実行してくれるため明示的にツールの呼び出しを行うことは多くないかもしれません。が、いちおうプログラムから明示的にツールを呼び出すことも可能になっています。
※ 実は冒頭の動画の例では、MCP ツール自体は直接 LLM から呼び出すのではなく、その選択と引数生成までを担当してもらって、呼び出しはプログラムから行うという仕組みになっています。
// クライアントの作成とツールリストの取得は前述のまま
// 取得したツールのひとつめを選択
McpClientTool tool = tools.First();
// 引数を準備
Dictionary<string, object> args = new Dictionary<string, object>();
args["on"] = true;
// ツールを明示的に呼び出し
CallToolResult result = await mcpClient.CallToolAsync(tool.Name, args);
// 結果を取得
if (result.Content[0] is TextContentBlock textContentBlock)
{
Debug.Log(textContentBlock.Text);
}
ここでは引数の準備をハードコードしていますが、今回自分が作った仕組みではこの引数セットアップ部分は LLM が JSON で知らせてくれるため、それをパースして組み立てるという仕組みになっています。そしてその引数を伴ってツールを呼び出しています。
最後に
以上が、MCP と Microsoft.Extensions.AI の組み合わせを使った基本的な使い方です。非常に簡単に LLM を利用することができることが分かったかと思います。もちろん、プロンプトエンジニアリングに代表されるように、より豊かなユーザ体験を提供するためにはもっと複雑な仕組みや実装が必要でしょう。しかし、LLM を用いた簡単なモックやプロトタイプ開発であればやりたいことをすぐに実装することができると思います。
自分は今、MESON で実装するプロダクト、アプリケーションにこうした AI を積極的に取り入れていきたいと思っています。そして、いわゆるチャットクライアントのようなものではなく、裏方的に利用して「おもてなし」の概念で AI を活用して、より豊かな体験を提供したいと思っています。
エンジニア絶賛募集中!
MESONではUnityエンジニアを絶賛募集中です! XR、空間コンピューティングのプロジェクトに関わってみたい! 開発したい! という方はぜひご応募ください!
MESONのメンバーページからご応募いただくか、TwitterのDMなどでご連絡ください。
書いた人

比留間 和也(あだな:えど)
カヤック時代にWEBエンジニアとしてリーダーを務め、その後VRに出会いコロプラに転職。 コロプラでは仮想現実チームにてXRコンテンツ開発に携わる。 DAYDREAM向けゲーム「NYORO THE SNAKE & SEVEN ISLANDS」をリリース。その後、ARに惹かれてMESONに入社。 MESONではARエンジニアとして活躍中。
またプライベートでもAR/VRの開発をしており、インディー部門でTGSに出展など公私関わらずAR/VRコンテンツ制作に精を出す。プライベートな時間でも開発しているように、新しいことを学ぶことが趣味で、最近は英語を学んでいる。
MESON Works
MESONの制作実績一覧もあります。ご興味ある方はぜひ見てみてください。

Discussion