11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vercelのmcp-adapterはいいぞ!AWSでも使える!

Posted at

前書き

Streamable HTTP対応のMCPサーバーを気軽にデプロイしたい!
その思いから@vercel/mcp-adapterと出会いました。使用感も良好で、皆さんに紹介したいと思います。

MCPについてまだご存知でない方は、こちらのスライドをご覧ください。

プロジェクト初期化

まずはNext.jsのプロジェクトを初期化します。

Next.jsを使ってMCPサーバーを作るのは少し不思議な感覚ですが、Next.jsは普通にAPIサーバーとしても使えるので、フルスタック開発が可能です。

公式ドキュメント

npx create-next-app@latest my-mcp-server

初期化完了したら、@vercel/mcp-adapterzodをインストールします。

npm install @vercel/mcp-adapter zod

下記のファイルを追加します。
シンプルなツールで、使用するとランダムに点数が返ってきます。

src/app/api/mcp/route.ts

import { z } from 'zod';
import { createMcpHandler } from '@vercel/mcp-adapter';
 
const handler = createMcpHandler(
  (server) => {
    server.tool(
      'roll_dice',
      'Rolls an N-sided die',
      { sides: z.number().int().min(2) },
      async ({ sides }) => {
        const value = 1 + Math.floor(Math.random() * sides);
        return {
          content: [{ type: 'text', text: `🎲 You rolled a ${value}!` }],
        };
      },
    );
  },
  {},
  { basePath: '/api' },
);
 
export { handler as GET, handler as POST, handler as DELETE };

実装は以上です!

VScodeからテストします

まずはNext.jsのローカルサーバーを立ち上げます。

npm run dev

VScodeの設定を開き、settings.jsonを編集します。

698281ED-7DEF-4F09-9C05-F24A90CAF3EB.jpeg

下記の内容を追加してください。
portは実際に立ち上げたサーバーの利用ポートに変更してください。

"local-server": {
  "url": "http://localhost:3000/api/mcp"
}

次はCopilotを開き、エージェントモード でツールを利用するようにプロンプトを投げてください。
特に問題がなければ、ダイスの結果が返ってきます。

8A8AA36B-1A21-4D71-8A9E-0C8BDED3C369.jpeg

MCP Inspectorからテストします

VScodeがインストールされてない場合、MCP Inspectorからでも接続テストを行えます。

npx @modelcontextprotocol/inspector

実行後、下記の内容が出力されると思います。

...
Session Tokenに ⚙️ Proxy server listening on 127.0.0.1:6277
🔑 Session token: token_value
...

http://127.0.0.1:6274 にアクセスして MCP Inspector を開きます。

  • Transport Type で「Streamable HTTP」を選択する
  • URLhttp://localhost:3000/api/mcp を入力する
  • Proxy Session Tokenにtoken_valueを入力する

Connect ボタンをクリックすれば、MCPサーバーに接続できます。

4F73560F-5A48-4AB7-8733-BF3A865EF0D1_4_5005_c.jpeg

mcp-adapterの認証

公式のドキュメントには記載されていませんが、mcp-adapterには認証用の関数が用意されています。

まだ検証段階のようですが、experimental_withMcpAuthという関数です。

src/app/api/mcp/route.tsのcodeを下記のように修正してください。

src/app/api/mcp/route.ts
import { z } from 'zod';
import { createMcpHandler,experimental_withMcpAuth } from '@vercel/mcp-adapter';
 
const handler = createMcpHandler(
  (server) => {
    server.tool(
      'roll_dice',
      'Rolls an N-sided die',
      { sides: z.number().int().min(2) },
      async ({ sides }, {authInfo}) => {
       console.log('authInfo', authInfo);
      if (!authInfo?.token) {
        return { content: [{ type: "text", text: "Unauthorized" }] };
      }
        const value = 1 + Math.floor(Math.random() * sides);
        return {
          content: [{ type: 'text', text: `🎲 You rolled a ${value}!` }],
        };
      },
    );
    server.tool(
      'story_list',
      'Display a list of stories owned by this user',
      {},
      async ({}, {authInfo}) => {
        console.log('authInfo', authInfo);
        if (!authInfo?.token) {
          return { content: [{ type: "text", text: "Unauthorized" }] };
        }
        return {
          content: [{ type: 'text', text: `黒い馬,天から降りる災難` }],
        };
      },
    )
  },
  {},
  { basePath: '/api' },
);

const wrappedHandler = async (req: Request) => {
  const authHandler = experimental_withMcpAuth(handler, (req) => {
    const header = req.headers.get("Authorization");

    if (header?.startsWith("Bearer ")) {
      const token = header.slice(7).trim();
      return Promise.resolve({
        token,
        clientId: "agent-mcp",
        scopes: ["runAgent"],
      });
    }

    return undefined;
  });

  return authHandler(req);
};
 
export { wrappedHandler as GET, wrappedHandler as POST, wrappedHandler as DELETE };

VScodeのsettings.jsonに、headersを追加し、Bearer tokenを渡せるようにします。

settings.json
    "local-server": {
      "url": "http://localhost:3000/api/mcp",
      "headers": {
        "Authorization": "Bearer my-api-key",
      },
     }

修正後、再度VScodeからMCPサーバーのツールを呼び出してみると、
追加したlogの内容が出力されたことが確認できます。

authInfo { token: 'my-api-key', clientId: 'agent-mcp', scopes: [ 'runAgent' ] }

Vercelにデプロイ

作業中にプロジェクトをGitHubにプッシュしてから、Vercelで連携するのが最も簡単な方法ですね。

507307C5-8A97-4C3A-8C4B-70EC514D9AA8.jpeg

オプションはデフォルトのままで大丈夫です。

F1E7F944-62BE-48A1-BFBB-4B8C6666FD43.jpeg

デプロイが完了すると、Vercelから提供されるドメインが付与されます。
それを使って、デプロイされたMCPサーバーに接続してみます。

setting.json
    "vercel-server": {
+      "url": "https://xxxx-xxx.vercel.app/api/mcp",
      "headers": {
        "Authorization": "Bearer my-api-key",
      },
     }

VercelにデプロイされたMCP Serverのツールも呼び出しに成功することがわかります。

24531D74-BB71-42C8-824A-686A379C9D8A.jpeg

Vercelのダッシュボードからログの内容を確認することができます。

71210CF8-0D6B-4112-B0E0-A3A0A73A2E76.jpeg

Next.js製のMCPサーバーを簡単にデプロイできて良いですね。
しかもクライアントとしての機能も残しているため、管理が楽で便利です。

AWS Amplifyでもいけるか

AWS Amplifyは、Webアプリケーションやモバイルアプリケーションを構築、デプロイ、管理するためのフルスタック開発プラットフォームです。

29f8d74d-7229-2a63-9291-64dfcf705e2e.png

Next.jsのプロジェクトも簡単にデプロイできます。

C4BFB0BD-6105-457E-8E85-440BAEAA8545.jpeg

GitHubAmplifyの連携が完了したら、リポジトリとブランチを選択して、「次へ」

76D079A4-E93F-4AAC-B436-73015AF9FD50.jpeg

アプリケーションの設定はそのままで大丈夫です。

171D6A1F-1396-4E87-B818-0B8418320B07.jpeg

最後に特に問題がなければ、「保存してデプロイ」。

初回デプロイは多少時間がかかりますが、それでも5分以内にロケットの打ち上げが確認できるでしょう。

URLをAmplifyから付与されたURLに変更します。

settings.json
+    "amplify-server": {
+      "url": "https://main.xxxxx.amplifyapp.com/api/mcp",
      "headers": {
        "Authorization": "Bearer my-api-key",
      },
     }

問題なくツールを呼び出せます。

E5E496A0-5D93-4048-98C9-154414FD7171.jpeg

ログも確認してみましょう!

Amplifyのモニタリングからホスティングしているコンピューティングログを開き、
CloudWatch ログストリームを確認します
C07103DA-F8CD-4A61-A9A5-0B4CB8437D05.jpeg

FB6F156D-6E20-4ADF-8ED0-F247877D80F5.jpeg

何が嬉しいかというと、AmplifyでMCPサーバーをデプロイすれば、既存のAWSリソースを利用できることです。
簡単にファイアウォールやカスタムドメインを追加することができます。

検証に使用したリポジトリ

参考資料

11
2
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
11
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?