しばやん雑記

Azure とメイドさんが大好きなフリーランスのプログラマーのブログ

Streamable HTTP に対応した Azure Functions の MCP Extension がリリースされたので試した

正直なところタイトルの通りですが、今年一番の注目 Azure Functions の機能である MCP Extension (MCP Tool Trigger) の Preview 7 がリリースされ、待望の Streamable HTTP がサポートされたので試していきます。

リリースノートは公開されていませんが、コミットを追えば何が追加されたのか確認出来ます。

Streamable HTTP のサポート関連ではありますが、Preview 7 から MCP の実装が独自から公式 MCP C# SDK の実装に切り替えられたので、互換性という観点でも改善されているはずです。

AOAI Dev Day 2025 でも話しましたが、これまでの Azure Functions の MCP Extension は Streamable HTTP ではなく HTTP + SSE のみのサポートだったので、常時接続が必要だったので実装面でもスケーラビリティ面で扱いにくいものでした。セッション資料は以下で公開されているので適宜参照してください。

噂によると MCP Extension の GA は 8 月だったはずですが、多少ずれている可能性がありそうです。遅くとも 9 月中には GA するとありがたいです。

既に以下の公式ドキュメントは Preview 7 の機能が反映されているので、まずはこちらにも目を通しておくと安心です。リリースとほぼ同時にドキュメントの更新も行われているのは安心感があります。

ちなみにこのエントリを書いている時点では Extension Bundles に MCP Extension の Preview 7 が含まれていないため、C# 以外の言語では func extensions installextensions.csproj を作って、明示的に拡張機能をインストール必要があります。

数日中には Extension Bundles のアップデートが行われて Preview 7 が含まれると思いますが、意外に拡張機能を明示的にインストールする方法は知られていないので、これを機に知っておいても損はありません。

ここから先は実際に Preview 7 の機能を試した結果を書いていきます。後述しますがバインドされたプロパティの型変換で一部挙動が変わっているのを確認したので注意が必要です。

ステートレスな Streamable HTTP

今回の Preview 7 での最重要アップデートになる Streamable HTTP ですが、現時点ではステートレスのみがサポートされていますが、ステートフルが必要となるケースはかなり少ないと思うので、大きな問題にはならないと考えています。

拡張機能のアップデートを行うと自動的に Streamable HTTP が有効になるので、デバッグ実行を行うと以下のように Streamable HTTP と SSE のエンドポイントの両方が表示されます。

Streamable HTTP がサポートされたので Flex Consumption へデプロイすることが容易になりました。

これまでの HTTP + SSE の場合は常時接続が必要になるので、ゼロスケールが行われる Flex Consumption との相性があまり良くなかったです。

プロパティの POCO バインディング

個人的には Streamable HTTP よりも便利だと思っているのが POCO へのバインディングがサポートされたことです。これまで MCP Tool Trigger でプロパティを受け取る場合には、以下のようにメソッドの引数に属性とセットで並べていく必要があり、受け取るプロパティが増えると可読性が急に悪化していました。

[Function(nameof(Echo))]
public object Echo([McpToolTrigger(nameof(Echo), "指定された人にあいさつします")] ToolInvocationContext context,
                   [McpToolProperty(nameof(name), "string", "人の名前", true)] string name)
{
    return $"Hello, {name}";
}

その問題を解決できる機能が POCO バインディングです。ドキュメントが公開されているので詳細はこちらを参照貰えれば良いですが、C# でよくある実装パターンに MCP Tool Binding を持ち込めるのが便利です。

実際に最初の MCP Tool 実装を POCO バインディングを使うように書き換えると、以下のように受け取るプロパティを別のクラスとして定義する形になります。これまでは McpToolProperty 属性を使っていましたが、C# の機能である required キーワードや Description 属性を使って定義出来るようになりました。

[Function(nameof(EchoPoco))]
public object EchoPoco([McpToolTrigger(nameof(EchoPoco), "指定された人にあいさつします")] EchoRequest request, ToolInvocationContext context)
{
    return $"Hello, {request.Name}";
}

public class EchoRequest
{
    [Description("人の名前")]
    public required string Name { get; set; }
}

明示的に名前やプロパティの型を定義する必要がなく、自動的にプロパティの型からマッピングを行ってくれるので非常に定義しやすくなりました。

ローカルでデバッグ実行しながらテストとして Copilot Chat を使って実行してみると、以下のように定義したクラスに値がマッピングされていることが確認出来ます。この辺りは C# を使って開発したことがある方なら馴染み深い挙動でしょう。

もちろん実行結果も以下の通り、意図した通りの挙動となっています。注意点としてはプロパティ名が JSON ですが PascalCase になることです。古い方法では大体 camelCase になっているはずなので、MCP クライアント側にキャッシュが残っていると、プロパティのマッピングが正しく行われないことがありました。

これは C# の JsonSerializer 実装によるものなのでカスタマイズ可能かもしれませんが、POCO バインディングへの移行タイミングでは気を付けましょう。

非常に便利な機能なのですが、既に実装して利用していた MCP サーバーの実装を Preview 7 にアップデートしたところ型変換のエラーが発生して、正しく動作しないケースがいくつかありました。具体的には以下のような MCP Tool を実装していました。

[Function(nameof(GetUser))]
public object GetUser([McpToolTrigger(nameof(GetUser), "ユーザー ID からユーザーの詳細を返します")] ToolInvocationContext context,
                      [McpToolProperty(nameof(userId), "string", "ユーザー ID")] string userId)
{
    return $"Id: {userId}";
}

これはよくある ID を受け取って詳細を返すという MCP Tool ですが、ID のフォーマットによって余計な型変換を行ってしまうことがありました。

例えば以下のように数字ベースの ID であれば文字列として扱われるので問題ありませんでした。

次に userId として GUID ベースの文字列を渡そうとしてみます。全ての型を string として定義しているので、Preview 6 までは問題なく動作していました。

しかし Preview 7 では 受け取る Function の引数の型が string になっていても内部的に GUID として扱われて、Function 呼び出し時にキャスト不可になり失敗してしまいました。

どうも内部で使われているバインディングの処理が、引数やプロパティの型を考慮していないようです。今回の場合は引数の型を Guid に変更すれば動くようになりますが、意図しない型変換が発生しているので不具合として報告するつもりです。