☁️

AWS LambdaでRemote MCP Serverをほぼ無料でホスティングする

に公開

背景

以前はClaudeに記憶を与えるLocal Memory MCPを作った記事を作成した。
その後すぐ、Claude MobileがRemote MCPを対応したため、どうしてもスマホで使うClaudeも記憶を持たせてほしいのでLocal MCP Serverをリモート化したくてAWS Lambdaで実装した。

なぜLambdaなのか?

サーバーレスでイベント駆動

私のユースケースとしてはClaudeと会話するときに、必要な場合はMemory MCPに私に関する記憶を取り出したり、記憶を作成したりするものだ。
そのため、1日多くても数十回使うようなものであり、サーバーを常時起動する必要がなく、イベント駆動のLambdaに非常に合うものであった。

個人利用はほぼ無料枠で完結

AWS Lambdaの無料枠は以下の通りだ。

  • 毎月100万リクエスト無料
  • 毎月40万GB秒の実行時間無料

私の場合DynamoDBもメモリの保存先として利用していますが、DynamoDBも無料枠として

  • 毎月25GB ストレージ無料
  • 25RCU/25WCU無料

理論上毎月読み書き合計約200Mリクエストを処理できる。

しかもこれはアカウントを作って12ヶ月間無料ではなくずっと無料なのだ。
MCPのような軽量な処理であれば、個人利用では無料枠内で十分運用できるだろう。

使うライブラリ

Claudeに良さそうなLibraryを探してもらったところ、AWS公式のPythonのmcp_lambda_handlerというライブラリを見つけてそれを使った。

https://github.com/awslabs/mcp/tree/8d90be5c403af4829a45d8f03093f830ffed6285/src/mcp-lambda-handler

ローカル vs リモートのアーキテクチャ

私も記事を書くために理解したので自分のメモのためにも詳細載せておく。

通信手段が異なる

MCPはローカルとリモートどちらもアプリケーション層ではJSON-RPC 2.0のプロトコルでやりとりしているが、通信手段が異なる。

  • ローカル:JSON-RPC over stdio
  • リモート:JSON-RPC over Streamable HTTP(Lambda Function URL経由)

なぜmcp_lambda_handlerを使うか

FastMCPとの役割の違い

ローカルMCPで使ったFastMCPは、Python関数をMCPツールとして登録し、stdioで受け取ったJSON-RPCの内容をPython関数呼び出しに変換する役割を果たした。

Lambdaでは常駐HTTPサーバーを立てることができず、Lambda特有のイベント形式で発火するように設計されている。今回使うmcp_lambda_handlerは、Lambda Event形式の中のJSON-RPC部分を取り出し、それをPython関数呼び出しに変換して、実行結果をMCP Clientが受け付けるJSON-RPCに変換して返す役割を果たしている。HTTPをLambdaがサポートするイベント形式に変換するために、Lambda Function URLを利用している。

データフローの比較

Local MCPの場合

Remote MCPの場合

どちらも最終的にPython関数呼び出しに帰着するのは同じだが、中間の変換レイヤーが異なる。mcp_lambda_handler はいい感じにLambdaの癖のあるイベント形式を吸収してくれるものだ。

FastMCPもリモートMCPをサポートしてるからそのままではダメなの?

FastMCPでもいけるが、FastAPIと同じく基本的に常駐サーバー前提で設計されている。私はLambdaの無料枠を活用したくイベント駆動でやりたいため、常駐サーバーを立てたくない。既存のHTTPサーバーをLambdaに移植するなら、Lambda Web Adapterでラッピングする必要があるが、必要以上にレイヤーが増える。特に今回は4つくらい簡単なCRUD関数を実行するだけなので、一番軽量なmcp_lambda_handlerが最適かなと。
(作るときにはただサンプルコードを探してClaudeに作ってもらっただけだが、記事を書くために色々考えたらこれは確かに良い選択肢だと思えた)

実装例

以下は私のDynamoDBにメモリの追加や、既存メモリをリストできるリモートMCPをLambdaでホストする際の例。内容を最小限に削っている。

# app.py
import boto3
from datetime import datetime
from awslabs.mcp_lambda_handler import MCPLambdaHandler

# Initialize MCP server
mcp_server = MCPLambdaHandler(name="memory-mcp", version="1.0.0")

# メモリをDynamoDBに保存している
dynamodb = boto3.resource('dynamodb')
table = dynamodb.Table('mcp-memories')

@mcp_server.tool()
def create_memory(content: str) -> str:
    """Create new user memory""" #ここの内容はLLMにTool Descriptionとして渡されるので重要
    memory_id = f"memory_{datetime.now().strftime('%Y%m%d%H%M%S')}"
    table.put_item(Item={
        'memory_key': memory_id, 
        'content': content
    })
    return f"saved: {memory_id}"

@mcp_server.tool()
def list_memories() -> str:
    """List all saved memories"""
    response = table.scan()
    items = response.get('Items', [])
    if not items:
        return "no memory found"
    return '\n'.join([f"{item['memory_key']}: {item['content']}" for item in items])

def lambda_handler(event, context):
    return mcp_server.handle_request(event, context)

注意事項

mcp_lambda_handlerはAWS Labsのライブラリなので長期的なメンテナンスは保証されていない。ただ実装はシンプルなので必要に応じて自前で同等の機能の実装も可能かと思います。
商用で長期運用する場合はAgentCore Runtimeも視野に入れると良いかも。

デプロイと運用

あまり本筋ではないので割と略す。

セットアップでやること

  1. DynamoDB テーブル作成:テーブル名 mcp-memories、パーティションキー memory_key
  2. Lambda 関数作成:上記コードをデプロイ、DynamoDB権限を付与
  3. Function URL 有効化:認証なし、またはAPIキー認証*

*Claude Remote MCPの設定はMCP名とエンドポイントのみなので、細かく認証の設定はできない(OAuthはできるがそれをやりたいわけではない)、かつ個人利用でそこまで細かくしたいわけではないため、Function URL + 簡易的なAPIキー認証で私は実装した。

デプロイ方法はみんなそれぞれだが、私はClaude Codeに任せたらSAM + bash scriptになった。

終わりに

ローカルMCPからリモートMCPへの移行により、以下のメリットを実現できた。

  • デバイス間での一貫した体験:Desktop、Web、Mobileすべてで同じメモリにアクセス可能
  • コスト効率:ほぼ無料枠内で運用可能
  • 運用負荷の軽減:サーバーレスでイベント駆動

実際に使ってみると、ローカルからリモートに変わったものの、気になるほどの遅延はなかった。すべてのデバイスのClaudeがちゃんとメモリの読み書きができるようになり、特にモバイルでの利用体験が大幅に向上した。mcp_lambda_handlerを使った実装例が少なかったため、この記事が参考になると幸いです。

過去の関連記事

https://zenn.dev/zhizhiarv/articles/local-memory-mcp-for-claude-desktop
https://zenn.dev/zhizhiarv/articles/use-remote-mcp-on-claude-mobile

GitHubで編集を提案

Discussion