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

## 前書き

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

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

https://speakerdeck.com/minorun365/yasasiimcpru-men

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

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

[公式ドキュメント](https://vercel.com/docs/mcp)

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

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

```
npm install @vercel/mcp-adapter zod
```

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

```typescript: 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/79348f64-32c8-4c51-8cdc-45205e913d66.jpeg)

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

```json
"local-server": {
  "url": "http://localhost:3000/api/mcp"
}
```
次はCopilotを開き、**エージェントモード** でツールを利用するようにプロンプトを投げてください。
特に問題がなければ、ダイスの結果が返ってきます。

![8A8AA36B-1A21-4D71-8A9E-0C8BDED3C369.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/f3d51f38-c9de-4462-9ae1-65e23a0f3eb1.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」を選択する
- `URL`に http://localhost:3000/api/mcp を入力する
- `Proxy Session Token`にtoken_valueを入力する

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

![4F73560F-5A48-4AB7-8733-BF3A865EF0D1_4_5005_c.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/c71aeb3e-8f92-41b8-bc34-e7237bbd384c.jpeg)

## mcp-adapterの認証

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

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

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

```diff_typescript: 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を渡せるようにします。

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

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

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

## Vercelにデプロイ

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

![507307C5-8A97-4C3A-8C4B-70EC514D9AA8.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/9f7f2d4d-bf34-4d48-8033-815b6f1af4f6.jpeg)

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

![F1E7F944-62BE-48A1-BFBB-4B8C6666FD43.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/ac17f678-31ad-4868-a75d-754dbc6fb076.jpeg)

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

```diff_json: 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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/1af6f158-6d4a-4d83-8982-62174dc74d29.jpeg)

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

![71210CF8-0D6B-4112-B0E0-A3A0A73A2E76.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/05ba59d2-f610-40ff-81e7-8ba2e6a8a6c2.jpeg)

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

## AWS Amplifyでもいけるか

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

![29f8d74d-7229-2a63-9291-64dfcf705e2e.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/068a94a3-d86a-4707-a2e8-7745f962cac3.png)


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

![C4BFB0BD-6105-457E-8E85-440BAEAA8545.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/25a4692c-86ec-46fc-91bb-3e93fd581e3c.jpeg)

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

![76D079A4-E93F-4AAC-B436-73015AF9FD50.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/509dca06-ef37-4a64-9f3a-1d71a1fd7912.jpeg)

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

![171D6A1F-1396-4E87-B818-0B8418320B07.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/af8abbe8-cf79-4757-9ca4-f7fa0126dfe2.jpeg)

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

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

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

```diff_json:settings.json
+    "amplify-server": {
+      "url": "https://main.xxxxx.amplifyapp.com/api/mcp",
      "headers": {
        "Authorization": "Bearer my-api-key",
      },
     }
```
問題なくツールを呼び出せます。

![E5E496A0-5D93-4048-98C9-154414FD7171.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/ed0ec1d0-94b2-4b11-9b07-8f137b9ab87c.jpeg)

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

Amplifyのモニタリングから`ホスティングしているコンピューティングログ`を開き、
CloudWatch ログストリームを確認します
![C07103DA-F8CD-4A61-A9A5-0B4CB8437D05.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/e55132f1-712c-4455-b979-a5fe202ea26a.jpeg)

![FB6F156D-6E20-4ADF-8ED0-F247877D80F5.jpeg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/320164/00c11fec-6b28-4dd6-bf6e-91271b58a52f.jpeg)

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

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

https://github.com/huanshenyi/vercel-mcp

## 参考資料

https://vercel.com/docs/mcp#deploy-mcp-servers-efficiently-on-vercel

https://vercel.com/changelog/mcp-server-support-on-vercel

https://github.com/vercel-labs/mcp-on-vercel

https://azukiazusa.dev/blog/vercel-mcp-server/

https://note.com/hip_iris4064/n/nd818e231902b