この記事では、Amazon API Gateway と AWS Lambda の構成で、Honoと
@hono/mcpを使ったリモートMCPサーバーを構築した際に、Amazon API Gatewayの制約で躓いた点と、解決方法について解説したいと思います。
※ 完成版のソースコードをこちらに格納しています。
リモートMCPサーバーの構成
今回作成したリモートMCPサーバーは下記の構成となっています。MCPサーバーはAWS Lambda上でHono + @hono/mcp で構成されており、Amazon API Gateway 経由でMCPホスト(Claude Codeなど)とMCPプロトコルでデータをやり取りします。
Amazon API Gatewayを採用した事により、Lambda Authorizer や AWS WAFとの関連付けを含む、Amazon API Gatewayの機能を利用してセキュリティを担保しようというのが本構成の目的となります。
初期検討時のコード(失敗)
最初に、公式ドキュメントを参考に Hono on Lambdaの設定と、足し算をするツール(add関数)のみを実装したMCPサーバーを作成してみたところ、上手く動作しませんでした。※下記に、実際に作成したコードのうちhandler部分のコードのみ掲載します。
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPTransport } from '@hono/mcp';
import { Hono } from 'hono';
import { handle } from 'hono/aws-lambda';
import { z } from 'zod';
const app = new Hono();
// Create an MCP server
const server = new McpServer({
name: 'demo-server',
version: '1.0.0',
});
// Add an addition tool
server.registerTool(
'add',
{
title: 'Addition Tool',
description: 'Add two numbers',
inputSchema: { a: z.number(), b: z.number() },
outputSchema: { result: z.number() },
},
async ({ a, b }: { a: number; b: number }) => {
const output = { result: a + b };
console.log('output', output);
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output,
};
},
);
app.all('/mcp', async (c) => {
const transport = new StreamableHTTPTransport();
await server.connect(transport);
return transport.handleRequest(c);
});
export const lambdaHandler = handle(app);
修正版のコード
こちらの記事の設定内容を参考に修正したバージョンが下記となります。修正箇所としては、StreamableHTTPTransport生成時のオプションを追加している部分になります。
公式SDKのコメントを確認すると、それぞれのオプションについて下記の記載があります。
-
sessionIdGenerator: undefined→ セッション管理を無効化 -
enableJsonResponse: true→ SSEストリームではなく、通常のJSONレスポンスを返却
つまり、ステートレスで通常のJSONを返却するように設定する事で、この構成でもMCPプロトコルによる通信が出来るようになります。
※ 動作したケースと動作しなかったケースを比較すると、公式ドキュメントから明確に記述している箇所は見つけられなかったのですが、おそらくAmazon API GatewayがSSEに対応していない事が原因だと考えられます。
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StreamableHTTPTransport } from '@hono/mcp';
import { Hono } from 'hono';
import { handle } from 'hono/aws-lambda';
import { z } from 'zod';
const app = new Hono();
// Create an MCP server
const server = new McpServer({
name: 'demo-server',
version: '1.0.0',
});
// Add an addition tool
server.registerTool(
'add',
{
title: 'Addition Tool',
description: 'Add two numbers',
inputSchema: { a: z.number(), b: z.number() },
outputSchema: { result: z.number() },
},
async ({ a, b }: { a: number; b: number }) => {
const output = { result: a + b };
console.log('output', output);
return {
content: [{ type: 'text', text: JSON.stringify(output) }],
structuredContent: output,
};
},
);
app.all('/mcp', async (c) => {
const transport = new StreamableHTTPTransport({
+ sessionIdGenerator: undefined,
+ enableJsonResponse: true,
});
await server.connect(transport);
return transport.handleRequest(c);
});
export const lambdaHandler = handle(app);
Function URLs との使い分け
@hono/mcpを利用してLambda上でMCPサーバーを実行するアプローチの別解として、Function URLsを利用する方法があります。この場合、こちらの記事で紹介されているようにStreamableHTTPTransport生成時にオプションを指定する必要がなく、SSEによる通信が実現出来る事が分かります。
また、Function URLsを利用した場合、Lambdaのタイムアウト上限である15分まで処理を実行する事が出来る為、重い処理を実行する場合はFunction URLsの方が優れていますが、一方でAmazon API Gatewayの機能が利用出来なくなる為、セキュリティを担保する為にAmazon CloudFront経由でAWS WAFと関連付けたり、Honoが提供するMiddlewareを利用する必要が出てきます。
| Amazon API Gateway | Function URLs | |
|---|---|---|
| タイムアウト | 29(秒) | 900(秒) |
| Amazon API Gatewayの機能 | 利用可 | 利用不可 |
| AWS WAFとの関連付け | 直接可能 | Amazon CloudFront経由 |
以上を踏まえると、S3からファイルを取得してAIエージェントに渡す、MCPサーバー内で外部のAPIを実行するといったような軽量な処理をするだけであれば、Amazon API Gatewayと連携させる方が良いでしょう。
最後に
この記事を最後まで読んで頂き、ありがとうございました。
同様の構成を検討されている方の参考になれば幸いです。
