8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Playwright MCPとAWS MCPのクライアントをVercel AI SDKで構築する

Last updated at Posted at 2025-04-10

はじめに

昨今 AI エージェントの流れが本当に激しいですね。
特に最近は MCP 関連の話題に溢れているなと感じているところです。

先週末には Azure Functions で MCP サーバーを作成できるようになりましたね。
私は普段 Azure を触ることが多く、このニュースがきっかけで MCP を触ろうと思ったので、非常に大きな出来事でした。

「MCP」とは「Model Context Protocol」の略で、生成 AI モデルに文脈情報を渡しやすくするための規格です。生成 AI 界の USB-C とも言われていますね。
すでに見られている方も多いかと思いますが、みのるんさんが以下のスライドで大変わかりやすくまとめてくださってます。私もこれで勉強しましたので、非常におすすめです!

ずっと MCP や AI エージェントを触りたいと思っては実行に移せていなかったのですが、今週からようやく触れることができましたので、備忘録も兼ねながら少しずつ記事にしたいと思います。

今回やりたいこと

今回は以下のようなことをやっていこうと思います。

  • (サブ)MCP サーバーを Azure Functions で作成する
  • (メイン)Vercel AI SDK を MCP クライアントツールとして使い、様々な MCP サーバーを呼び出してみる

Vercel AI SDK は、3/21 にバージョン 4.2 がリリースされ、MCP がサポートされました。
実験的な機能もまだまだございますが、これにより Vercel AI SDK を利用する方は、MCP のクライアント機能を簡単にアプリケーションに組み込み、MCP サーバーが提供するツールを活用できることになります。

今回実装したアプリケーションは GitHub で公開しております。
突貫で作ったので参考にする程度に見ていただけると幸いです...

今回 Vercel AI SDK で作成した MCP クライアントから、自作した Azure Functions の MCP サーバーは実行できておりません。

GitHub Copilot や MCP Inspector からは接続できているのですが、アプリケーションからだと実行時にフリーズしてしまいます...

もしその部分が今後完成したらこちらの記事も更新したいと思っておりますので、この点についてはご容赦いただければと思います。

完成したもの

今回完成したものは以下のようなチャットアプリとなります。
メッセージを送信し、AI から返事が返ってくるシンプルなアプリケーションです。今回はこのアプリケーションに MCP クライアントを構築しています。
以下の画像だと、playwright-mcp を呼び出して特定の Web サイトの情報を抽出させて AI に回答させています。

image.png

MCP サーバーを Azure Functions で作成する

本題に入る前に、Azure Functions で MCP サーバーを作成したので、こちらを紹介します。
すでにたくさんの方が記事にされていますが、私も現在時刻を取得する簡単な MCP サーバーを作成します。

Azure Functions プロジェクトの作成 & 各種インストール

まずは Azure Functions のプロジェクトを作成します。公式ドキュメントに手順が記載されておりますので、それに沿って作成します。
今回私は Visual Studio Code で作成し、言語は C#を選択しています。

MCP サーバーは .NET8 以降でないと動作しないため、それより下位のバージョンは使用できません。
また、プロジェクト作成時にどのトリガーで作るか迷われる方もいらっしゃるかと思いますが、ひとまずHTTP トリガーで作っておけば特段問題はございません。

続いて、MCP 拡張機能をインストールします。現在はプレビュー版となっています。

Terminal
dotnet add package Microsoft.Azure.Functions.Worker.Extensions.Mcp --version 1.0.0-preview.1

MCP 拡張機能は Blob ストレージを使用するため、Azurite をローカル開発用にインストールします。Visual Studio Code の拡張機能から Azurite をインストールし、コマンドパレットから「Azurite: Start」で起動します。

また、local.settings.json を以下のように書き換えます。ポイントは AzureWebJobsStorage の部分です。

local.settings.json
{
  "IsEncrypted": false,
  "Values": {
+   "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated"
  }
}

また、ここで <プロジェクト名>.csproj の一部を変更します。
私の環境だと、公式で作成した Azure Functions のテンプレートの Microsoft.Azure.Functions.Worker.Sdkv2.0.0 だと動作しませんでした。そのため、以下のようにバージョンを v2.0.2 にあげる必要があります。

この部分については他の方の記事には存在しなかったため、動作環境起因の可能性があります。

<プロジェクト名>.csproj
-    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.0" />
+    <PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.2" />

Azure Functions MCP サーバーの開発

続いて、現在時刻を取得するための MCP ツールを作成します。

MyMcpTools.cs
namespace AzureFunctionsMcp
{
    public class MyMcpTools
    {
        [Function(nameof(GetCurrentTime))]
        public string GetCurrentTime(
        [McpToolTrigger("getcurrenttime", "Gets the current time. If no timezone is specified, the tool will return the time in UTC.")] ToolInvocationContext context,
        [McpToolProperty("timezone", "string", "The name of the timezone.")] string timezone = "UTC"
        )
        {
            try
            {
                TimeZoneInfo timeZoneInfo = TimeZoneInfo.FindSystemTimeZoneById(timezone);
                DateTime currentTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.UtcNow, timeZoneInfo);
                var response = new
                {
                    timezone = timeZoneInfo.StandardName,
                    time = currentTime.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture),
                    displayName = timeZoneInfo.DisplayName
                };
                return JsonSerializer.Serialize(response);
            }
            catch (TimeZoneNotFoundException)
            {
                return $"The timezone '{timezone}' was not found.";
            }
            catch (InvalidTimeZoneException)
            {
                return $"The timezone '{timezone}' is invalid.";
            }
            catch
            {
                return "Could not get the current time.";
            }
        }
    }
}

次に、分離ワーカーモデルのエントリーポイントとなる Program.csに MCP ツールを登録します。

Program.cs
var builder = FunctionsApplication.CreateBuilder(args);

builder.ConfigureFunctionsWebApplication();
+ builder.EnableMcpToolMetadata();

+ builder.ConfigureMcpTool(nameof(MyMcpTools.GetCurrentTime))
+      .WithProperty("timezone", "string", "The timezone.");

builder.Build().Run();

MCP サーバーの起動

実行環境の準備が整いましたら、MCP サーバーを起動します。以下のコマンドで関数を起動します。

Terminal
func start

しばらくすると以下のような出力結果となれば起動成功です。MCP サーバーは次のアドレスでアクセス可能になります。
http://localhost:7071/runtime/webhooks/mcp/sse

Terminal
Functions:

	GetCurrentTime: mcpToolTrigger

きちんと MCP サーバーが動作しているのか確認したい場合は、GitHub Copilot Agent から呼び出すか、以下の MCP Inspector を利用すると良いかと思います。
https://github.com/modelcontextprotocol/inspector

Terminal
npx @modelcontextprotocol/inspector

image.png

MCP サーバーを Azure にデプロイ

それでは、作成した MCP サーバーを Azure にデプロイします。Azure ポータルで Functions を作成下のち、以下のコマンドを実行します。

MCP サーバーの Azure Functions は現在 Windows で動作しないという情報が散見されますので、作成する際は Linux で作成しましょう。

https://github.com/Azure-Samples/remote-mcp-functions-dotnet/issues/8

Terminal
func azure functionapp publish <your-function-app-name>

Azure Functions で起動後は次のアドレスでアクセス可能になります。
https://<your-function-app-name>.azurewebsites.net/runtime/webhooks/mcp/sse

image.png

Azure Functions の MCP サーバーを実行する際は、mcp_extensionシステムキーが必要になります。「アプリ キー」のタブから忘れずに取得しておいてください。

これにて、前半の MCP サーバーを Azure Functions で作成するフェーズは終了です。

Vercel AI SDK を MCP クライアントツールとして使い、様々な MCP サーバーを呼び出してみる

それではメインの方になります。MCP の話題だとどうしてもサーバー側の話が多いかと思いますが、それを呼び出すためのクライアントが今後はどんどん出てくると思っています。

プロジェクトのセットアップ & 各種インストール

Vercel ということで、Next.js でセットアップを行います。
詳しい手順は以下の公式ドキュメントを参照ください。

Terminal
npx create-next-app@latest

次に、今回は Vercel AI SDK を OpenAI で使用するため、必要なライブラリをインストールしておきます。

Terminal
npm i ai @ai-sdk/react @ai-sdk/openai zod

OpenAI で LLM を利用するためには API キーが必要です。事前に以下の URL から API キーを取得しておきます。選択するモデルにもよりますが、LLM の使用には料金が発生する場合があるため利用する際はご注意ください。

API キーを取得できたら、.env に環境変数として入れておきます。Vercel AI SDK の OpenAI Provider を利用するため、デフォルトの OPENAI_API_KEY というキーで値を入れておきます。

くれぐれも API キーは GitHub や他クラウドに Upload しないでください。

.env
OPENAI_API_KEY=<取得したキー>

利用する MCP サーバーの起動

ここまででプロジェクトのセットアップと各種インストールが終了したので、利用する MCP サーバーを起動しておきます。

今回起動する MCP サーバーは以下の 3 つとなります。

AWS Documentation MCP Server の利用

AWS Documentation MCP Server は公開されているものになるため、誰でも利用可能です。ですが、利用するためには以下のコマンドから uvPython3.10 以上をインストールしておく必要があります。詳細は公式ドキュメントを参照ください。

Terminal
curl -LsSf https://astral.sh/uv/install.sh | sh
uv python install 3.10

これだけで AWS Documentation MCP Server を利用する準備が整いました。簡単ですね!!

Playwright MCP サーバーの起動

続いて、Playwright MCP サーバーの起動します。今回はローカルで起動した Playwright MCP サーバーと SSE で通信することとします。
まず、以下のコマンドを実行して Playwright MCP サーバーを起動します。コマンドの詳細は公式ページを参照ください。

Terminal
npx @playwright/mcp@latest --port 8931 --headless

起動すると http://localhost:8931/sse が SSE のエンドポイントとなります。

ルートハンドラーの開発

MCP サーバーが起動できたため、それに接続するルートハンドラーを作成します。
今回は Vercel AI SDK の useChat を利用するため、デフォルトのエンドポイントである api/chat に相当する app/api/chat/route.ts を作成します。

ここでポイントとなるのが以下の点です。

  • MCP クライアントはサーバーとの接続方法(トランスポート)として、SSE (Server-Sent Events)stdioカスタムトランスポートを提供しているため、使用する MCP サーバーによって使い分ける
  • 複数の MCP Tools を登録するように tools オブジェクトを構成する
  • ストリーミング通信は Vercel AI SDK の streamText を利用する
  • ストリーミング応答が完了したら、MCP クライアントの接続を閉じる
app/api/chat/route.ts
import { streamText } from 'ai';
import { experimental_createMCPClient as createMCPClient } from "ai";
import { Experimental_StdioMCPTransport as StdioMCPTransport } from 'ai/mcp-stdio';
import { openai } from '@ai-sdk/openai';

export async function POST(req: Request) {
  try {
    // AWS MCP クライアントの作成
    const awsMcpClient = await createMCPClient({
      transport: new StdioMCPTransport({
        command: "uvx",
        args: ["awslabs.aws-documentation-mcp-server@latest"],
        env: {
          FASTMCP_LOG_LEVEL: "ERROR"
        },
      }),
    })

    // Azure Functions MCP クライアントの作成
    const azMcpClient = await createMCPClient({
       transport: {
         type: "sse",
         url: "https://<your-function-app-name>.azurewebsites.net/runtime/webhooks/mcp/sse",
         headers: {
           "x-functions-key": process.env.AZURE_FUNCTIONS_KEY || "",
         },
       },
     });

    // Playwright MCP クライアントの作成
    const playwrightMcpClient = await createMCPClient({
      transport: {
        type: "sse",
        url: "http://localhost:8931/sse"
      },
    });

    const { messages } = await req.json();

    // MCP サーバーからツール定義を取得
    const awsMcpTool = await awsMcpClient.tools();
    const azMcpTool = await azMcpClient.tools();
    const playwrightMcpTool = await playwrightMcpClient.tools();

    const tools = {
      ...awsMcpTool,
      ...azMcpTool,
      ...playwrightMcpTool,
    }

    // Vercel AI SDK の streamText 関数を使用して LLM とのストリーミング通信を開始
    const result = streamText({
      model: openai('gpt-4o'),
      messages,
      tools,
      onFinish: async () => {
        // ストリーミング応答が完了したら、MCP クライアントの接続を閉じる
        await awsMcpClient.close();
        await azMcpClient.close();
        await playwrightMcpClient.close();
      },
    });

    return result.toDataStreamResponse();

  } catch (error) {
    console.error('Error: ', error);
  }
}

チャット画面の開発

続いて、上記のルートハンドラーを呼び出すためのチャット画面を開発します。今回は他に画面はないため、app/page.tsxを編集します。
ポイントとなる点は以下の点です。

  • インタラクティブな画面のため、use client でクライアントサイドで実行する
  • @ai-sdk/reactuseChat フックの handleSubmit を実行することでルートハンドラーを呼び出す
app/page.tsx
'use client';

import { useChat } from '@ai-sdk/react';

export default function Chat() {
  const { messages, input, handleInputChange, handleSubmit, status } =
    useChat();
  return (
    <div className='flex flex-col items-center w-full min-h-screen bg-gray-100 dark:bg-gray-900'>
      <div className='flex flex-col w-full max-w-5xl p-6 mt-12 bg-white rounded-lg shadow-lg dark:bg-gray-800'>
        <h1 className='text-2xl font-bold text-center text-gray-800 dark:text-gray-100'>
          Chat with Open AI
        </h1>
        <div className='flex flex-col gap-4 mt-4 overflow-y-auto max-h-[40rem]'>
          {messages
            .filter((message) =>
              message.parts.some((part) => part.type === 'text' && part.text.trim() !== '')
            )
            .map((message) => (
              <div
                key={message.id}
                className={`p-3 rounded-lg ${
                  message.role === 'user'
                    ? 'bg-blue-100 text-blue-900 self-end'
                    : 'bg-gray-200 text-gray-900 self-start'
                }`}
              >
                <span className='font-semibold'>
                  {message.role === 'user' ? 'User: ' : 'AI: '}
                </span>
                {message.parts.map((part, i) => {
                  switch (part.type) {
                    case 'text':
                      return (
                        <span key={`${message.id}-${i}`} className='block'>
                          {part.text}
                        </span>
                      );
                  }
                })}
              </div>
            ))}
        </div>
        <form
          onSubmit={handleSubmit}
          className='flex flex-col gap-2 mt-4'
        >
          <input
            type='text'
            value={input}
            onChange={handleInputChange}
            className='w-full p-3 border border-gray-300 rounded-lg dark:bg-gray-700 dark:text-gray-100 dark:border-gray-600'
            placeholder='メッセージを入力してください'
            disabled={status !== 'ready'}
          />
          <button
            type='submit'
            className='px-4 py-2 text-white bg-blue-500 rounded-lg hover:bg-blue-600 disabled:bg-gray-400 disabled:cursor-not-allowed'
            disabled={status !== 'ready'}
          >
            {status !== 'ready' ? '・・・' : '送信'}
          </button>
        </form>
      </div>
    </div>
  );
}

これで実装の全てが完了しました!お疲れ様でした。

Next.js サーバーの起動

実装が完了したため、以下のコマンドで起動します。起動すると、http://localhost:3000 で画面を表示することができます。

Terminal
npm run dev

すると、以下のような画面が表示されます。
image.png

試しに
https://news.yahoo.co.jp/ にアクセスして、今日のニュースでインパクトのあるものを 3 つ挙げてください」
と入力すると、Playwright MCP が呼び出されその Web 情報を出力してくれます。

image.png

本当に MCP サーバーが呼ばれているのか分かりにくいため、AWS Documentation MCP でも試してみます。
「AWS Lambda とはどんなサービスですか?」
と質問し、開発者ツールを確認したところ、AWS Documentation MCP の search_documentation が呼ばれていることが確認できました!!

image.png

冒頭にも記載しましたが、Azure Functions の MCP サーバーに接続しようとすると、MCP クライアント初期化時に止まってしまい、実行できていません。
ログからは接続できているように見えるため、設定不備はなさそうですが、この点は今後の課題とし、改善できたらこの記事を更新する予定です。

おわりに

いかがでしたでしょうか。今回は Azure Functions で MCP サーバーを作成した話と、Vercel AI SDK で MCP クライアントを構築し、MCP サーバーを呼び出す方法を紹介しました。

AI エージェントが台頭し、技術の進化が留まるところを知りません。
どうしても触ってみないとキャッチアップが難しいものもありますが、この波に乗り遅れないよう頑張りましょう!

またアップデートがあればお伝えしようと思います。最後まで読んでいただきありがとうございました。

8
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?