🧠

MCP Serverを呼び出すAIエージェントをMastra、Next.js、AWS CDKで実装してみた

に公開
2

こんにちは、つくぼし(tsukuboshi0755)です!

前回MCP Serverを呼び出すMastraのAIエージェントを、ECS Fargate上のMastraコンテナで動かすシステムを構築しました。

https://zenn.dev/tsukuboshi/articles/mastra-mcp-with-ecs-bedrock

しかし本番環境では、MastraをNext.jsと統合し動かす事で、UI画面をカスタマイズした上でより実用的なAIエージェントを作成する事ができます。

そこで今回は応用として、Mastra、Next.js、AWS CDKを用いてMCP Serverを呼び出すAIエージェントのテンプレートリポジトリを作成してみたので紹介します!

システムの概要

リポジトリ

今回のシステムは以下のGitHubリポジトリで公開しています。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws

構成図

本システムは以下の構成で動作します。

ALBからのリクエストを受け取ったECS Fargate タスクであるMastraエージェントが、Bedrockモデルを呼び出します。

呼び出されたBedrockモデルは事前に登録されているPlaywright MCP Serverを通じて、リクエスト内のプロンプトを元にブラウザ操作のツールを呼び出し操作した上で、結果を返す仕組みとなっています。

ここまでは前回の記事と同様です。

さらに今回の記事の追加要素として、後述するallowIpにIPアドレスを指定する事でWAFが新規作成され、特定のIPアドレスからのアクセスのみを許可します。

またmcpEnvを指定する事でSecret Managerが新規作成され、ECS Fargateタスクに環境変数として渡されます。

本システムの特徴

基本的には前回の記事と同じCDKの採用/Bedrockの呼び出し/MCP Serverの登録の3つ特徴を持つ構成になっていますが、加えて今回は以下の特徴を持つシステムになっています。

Next.jsの直接統合

今回MastraにNext.jsを直接統合する事で、Mastraのエージェント画面をカスタマイズしたり、Next.jsの機能を用いてMastraエージェントを拡張する事ができます。

Mastraの公式ドキュメントにも、Next.js直接統合する際の手順が紹介されています。

https://mastra.ai/ja/docs/frameworks/next-js#2-直接統合

まずはNext.jsの設定から行います。

serverExternalに@mastra/*を指定し、MastraのモジュールをNext.jsにおけるServer Componentsのバンドリングから除外します。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/next.config.ts

続いてNext.jsのサーバーアクションを定義します。

今回はなるべくテンプレート的な形で流用できるようにしたかったため、プロンプト以外にもMastraのmaxStepsをUIから指定できるようにする事で、簡単に最大ステップ数を変更できるようにしました。

また回答はデフォルトでonStepFinishのtext及びツール名を全て返すようにする事で、使用されたツール情報及び途中の処理結果を合わせ全て回答として表示できるようにしています。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/app/actions.ts

さらにNext.jsのページクライアントを定義します。

今回はエージェント名、使用モデル、命令文を画面表示する事で、どのようなエージェントが動作しているか一目で分かるようにしました。

そしてプロンプトを入力するテキストボックスと、実行結果を表示するためのテキストエリアを用意しています。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/app/page.client.tsx

このページクライアントをチャットインターフェースとして呼び出す事で、ページの画面表示を行います。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/app/page.tsx

上記で作成したNext.js & MastraのAIエージェントを、CDKデプロイ時に以下のDockerfileを用いてイメージとしてビルドし、ECS Fargate上で動作させる流れになります。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/Dockerfile

パラメータ指定

パラメータをTypeScriptで指定する事ができ、デプロイ時に自動でCDKが読み取り、システムの仕様を柔軟に変更する事ができます。

まずアプリケーション(Next.js/Mastra)川のパラメータは、以下のファイルで指定可能です。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/parameter.ts

region及びbedrockModelには、Bedrockモデルのリージョン及びモデル名を指定する事で、呼び出すBedrockモデルを変更する事ができます。

ここで指定されたBedrockの設定値は、以下のBedrockクライアントの初期化で使用されます。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/lib/bedrock-client.ts

続いてagentName及びagentInstructionsにはMastraにおけるのエージェント名及び指示文を指定する事で、Mastraエージェントの振る舞いを変更する事ができます。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/mastra/agents/index.ts

そしてmcpName/mcpCommand/mcpArgs/mcpEnvには、MCP Serverで対応するコマンドや引数、環境変数を指定する事ができ、MCP Serverの振る舞いを変更する事ができます。

例えば、以下の設定を持つGitHub MCP Serverを呼び出したいとします。

{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["-y", "@modelcontextprotocol/server-github"],
      "env": {
        "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
      }
    },
  }

この場合、各変数に応じて以下のように指定します。

export const mcpName = "github";
export const mcpCommand = "npx";
export const mcpArgs = ["-y", "@modelcontextprotocol/server-github"];
export const mcpEnv = {
  "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}

ここで指定された値が以下のファイルでMastraのMCP Serverとして定義されtoolで呼び出される事で、MCP Serverの種類に応じてコマンド/引数/環境変数を変更する事ができます。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/web/src/mastra/mcp/index.ts

なおデフォルトではPlaywright MCP Serverを呼び出すように設定されているため、特に変更しなくても問題ありません。

次にインフラ(CDK)については、以下のファイルでパラメータを指定可能です。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/infra/parameter.ts

allowIpにIPアドレスを配列形式で指定すると、WAFが新規作成されると共にALBに紐づけられ、特定のIPアドレスからのアクセスのみを許可する事ができます。

まだ認証機能が実装されていないAIエージェントへのアクセスを制限する際に、クライアンドのグローバルIPを登録しておくと便利です。

mcpEnvにMCP Serverで必要なシークレット情報をjson形式で指定する事で、Secret Managers及びECS Fargateタスクの環境変数として渡されます。

指定方法はアプリケーション側のmcpEnvと同じく、この場合、env以降のjson内容を以下のようにmcpEnvで指定します。

export const mcpEnv = {
  "GITHUB_PERSONAL_ACCESS_TOKEN": "<YOUR_TOKEN>"
}

uniqueIdには、スタック名及び各サービスのリソース名に付与される値を指定できます。

これにより、以下のように同じAWSアカウント内で複数のAIエージェントのスタックをデプロイする事ができ、各リソース名が重複する事を防ぐ事ができます。

なお今回のCDKファイルの全体像は以下になります。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/infra/lib/mastra-mcp-on-ecs-stack.ts

検証手順

基本的にはREADMEに記載されているGetting Startedの手順に従います。

https://github.com/tsukuboshi/nextjs-mastra-mcp-with-aws/blob/main/README.md

環境構築/ローカル環境起動

事前にルートディレクトリでnpm run install:allを実行しておくと、webとinfraの両方のモジュールがインストールされます。

またweb/.env.developmentファイルを作成しAWS認証情報の登録する事で、ローカル環境でBedrockモデルを呼び出す事ができます。

ACCESS_KEY_ID=XXXXXXXXXXXXXXXXXXXX
SECRET_ACCESS_KEY=XXXXXXXXXXXXXXXXXXXX
SESSION_TOKEN=XXXXXXXXXXXXXXXXXXXX

npm run mastra:devで、ローカル環境で以下のようなMastraのみを起動しバックエンド側の動きを確認できます。

npm run next:devで、ローカル環境で以下のようなMastraと統合したNext.jsを起動しフロントエンド側の動きを確認できます。

AWSデプロイ手順

必要に応じて、事前にweb/parameter.ts及びinfra/parameter.tsのファイル内容を変更し、パラメータを指定してください。

npm run cdk:deployを実行すると、CDKが上記のパラメータを用いてAWS上にインフラをデプロイします。

(中略)
Outputs:
MastraMcpOnEcsStack.FargateServiceLoadBalancerDNSXXXXXXXX = mastra-alb-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com
MastraMcpOnEcsStack.FargateServiceServiceURLXXXXXXXX = http://mastra-alb-XXXXXXXXXX.ap-northeast-1.elb.amazonaws.com
(中略)

OutPutsに表示された該当URLをクリックすると、Mastraのエージェント画面が表示されます。

例えば以下のように質問を投げると、ツールを用いた検索を行った上で、返答が返ってきます。

(なお記事にアップロードできるGifが3MBに制限されているため、途中の待機時間を一部カットしているのでご了承下さい)

なお初回のみコンテナ内にブラウザがインストールされていないため、エージェントがMCP Serverを通じてplaywright_browser_installツールを使用しインストールした後に、ブラウザを用いてエージェントが作業を実施します。

このような形で、AWS上でNext.jsと統合したMastraで作成したAIエージェントを動作させる事ができました!

なお環境削除したい場合は、npm run cdk:deployを実行する事で、CDKを用いてAWS上のインフラを一括で削除できます。

余談:Lambdalish構成の検討

今回MastraエージェントをLambda Web Adapterで動作させると共に、CloudFront OAC + Lambda Functions URLの構成を用いる事で、自動でzero-scaleできるようなアーキテクチャを途中で知り、この構成で動かしてみたい!と思い実際に検証してみました。

結果、MCP Serverを呼び出すAIエージェントの場合だと、「Lambda固有の/tmpディレクトリのみしか書き込み権限がない仕様」が相性が悪いかもしれない...と判断し、今回は断念しています。

どういう事かというと、今回Lambda上で書き込み権限を何も気にせずPlaywright MCP Serverを呼び出そうとすると、以下のようなnpmキャッシュの書き込みエラーや、他にもPlaywrightのブラウザインストールエラー等が発生し、MCP接続が異常終了してしまいます。

npm error enoent ENOENT: no such file or directory, mkdir '/home/sbx_userXXXX'
npm error Log files were not written due to an error writing to the directory: /home/sbx_userXXXX/.npm/_logs
npm error You can rerun the command with `--loglevel=verbose` to see the logs in your terminal
[31mERROR[39m [2025-04-29 06:06:36.617 +0000] (LLM - MastraMCPClient): [36mFailed connecting to MCP server[39m
error: "McpError: MCP error -32000: Connection closed\n at Client._onclose (file:///app/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:97:23)\n at _transport.onclose (file:///app/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js:69:18)\n at ChildProcess.<anonymous> (file:///app/node_modules/@modelcontextprotocol/sdk/dist/esm/client/stdio.js:85:77)\n at ChildProcess.emit (node:events:524:28)\n at maybeClose (node:internal/child_process:1104:16)\n at ChildProcess._handle.onexit (node:internal/child_process:304:5)"
[31mERROR[39m [2025-04-29 06:06:36.621 +0000] (LLM - MCPConfiguration): [36mMCPConfiguration errored connecting to MCP server playwright[39m
error: "MCP error -32000: Connection closed"

これはLambda上だと/tmpディレクトリ以外で書き込み権限がないため、npmキャッシュやPlaywrightのブラウザインストール先が/tmpディレクトリ以外に書き込まれようとする度に発生するエラーになります。

上記を解消するためには、MCP Server毎に実行するタスクを分解して各々/tmpディレクトリへ事前にシンボリックリンクを貼る事が必要になります。

実際にLambda Web AdapterにおけるNext.jsのサンプルを見ると、デフォルトでは/.next/cacheにキャッシュが保存されてしまうため、事前に/tmp/cacheにシンボリックリンクを貼る事で回避している事が分かります。

https://github.com/awslabs/aws-lambda-web-adapter/blob/main/examples/nextjs/app/Dockerfile

しかしこのシンボリックシンクを貼る作業を、MCPサーバ接続エラーが発生する度に独自で修正してまた検証を繰り返すのは手間がかかりすぎるので、今回はそもそも書き込み権限の仕様が存在しないALB + ECS構成を最初から使った方が良いのでは?という結論に落ち着きました。

ただ一方でLambdalish構成による自動zero-scaleは魅力的なので、上記についてあまり手間をかけず上手く回避できる方法があればぜひ教えて欲しいです...!

まとめ

今回は、Mastra、Next.js、AWS CDKを用いてMCP Serverを呼び出すAIエージェントのテンプレートリポジトリを作成してみました。

このリポジトリを用いる事で、MCP Serverを呼び出すAIエージェントをMastraで簡単に作成する事ができ、さらにNext.jsの機能を用いてUI画面をカスタマイズしたり、CDKのパラメータを変更する事で柔軟にシステムをデプロイする事ができます。

ぜひこのリポジトリを参考にして、MCP Serverを呼び出すAIエージェントを作成してみてください!

以上、つくぼし(tsukuboshi0755)でした!

Discussion

がれっとがれっと

これはLambda上だと/tmpディレクトリ以外で書き込み権限がないため、npmキャッシュやPlaywrightのブラウザインストール先が/tmpディレクトリ以外に書き込まれようとする度に発生するエラーになります。

こちらはLambdaの環境変数としてHOME="/tmp"を設定することで回避可能です。

つくぼしつくぼし

こちらはLambdaの環境変数としてHOME="/tmp"を設定することで回避可能です。

おおおご教示頂きありがとうございます、本記事にもコメントのリンク追記しました!
ぜひ時間ある時に再度トライしてみます!