
AWS Cost Analysis MCP ServerのツールをLambda関数に移植してAWS Lambda MCP Server経由で実行する
お疲れさまです。とーちです。
AWS Lambda MCP Serverをご存知でしょうか?私は以下の記事で紹介されていて初めて知りました。
このAWS Lambda MCP Serverですが、なかなか可能性を秘めているものだということが以下の記事を読んでわかりました。
この記事では上記の記事を元にAWS Lambda MCP Serverの利点を私なりに整理し、実際にAWS Cost Analysis MCP ServerのツールをLambda関数に移植して実行するところまでやってみようと思います。
MCPサーバの課題
現状のMCPサーバには以下のような課題があります。
セキュリティ面の課題
例えば社内で使用しているデータベースにアクセスするためのMCPサーバを作成したとします。MCPサーバは現時点では開発端末等のローカルで実行するケースが多いと思いますが、ローカルで実行する場合、MCPクライアント(Claude Desktop等)にMCPサーバの設定さえ入れてしまえば誰でも使用できます。MCPサーバの中には追加でAPIキー等を設定するケースもありますが、その場合においてもAPIキーさえ知っていれば誰でも使用可能となるため、セキュリティ面では万全とは言い難いです。
運用面の課題
組織内の各チームがそれぞれで独自MCPサーバ(及びツール)を作ってしまうと、MCPサーバの一元的な管理が難しいです。他のチームで既に作成されている同じようなMCPサーバを作ってしまうということもあるかもしれません。他のチームで有用なMCPサーバを作っていたとしてもそれを知るのもなかなか難しいと思います。
ツールとMCPサーバを分離するアプローチ
こういった課題を解決する一つのアイディアとしてツールとMCPサーバを分離するというアプローチを上記の記事では取っています。図にするとこんな形です。
AWS | Community | Scaling MCP Across Your Organization Through AWS Lambdaより画像引用
上記図の中の MCP Server A
にあたる部分をAWS Lambda MCP Serverが担う形になります。また通常であればMCPサーバと一緒にパッケージングされるツールはAWS上のLambda関数(上記図ではAWS Lambda Function A~C
)として実装します。AWS Lambda MCP ServerがAWS上のLambda関数をツールとして認識し、MCPクライアントはAWS Lambda MCP Serverを通して各ツールからの情報を取得するという形です。
これによって上記の課題がどのように解決されるか考えてみます。
セキュリティ面の改善
MCPクライアントに登録するMCPサーバは AWS Lambda MCP Server
となります。MCPクライアントは各ツールが実装されたLambda関数を実行するIAM権限を持っていないとツールを使用することはできません。つまりIAMでの認証・認可をMCPの世界に持ち込めるということになります。
またMCPサーバとツールが分離されたことによりツールが使用するための権限をMCPサーバ(を実行するクライアント側)に持たせる必要もなくなりました。例えばツールがDBにアクセスするという場合、従来であればDBへの認証情報をクライアント側に設定する必要がありましたが、これをLambda側に持たせることができるようになります。
運用面の改善
MCPサーバとツールを分離することで、ツールをAWS上で集中管理することが可能になります。クライアント側は AWS Lambda MCP Server
を設定するだけで必要なツールが自動でリストアップされるので、セットアップがとても簡単になります。
新しくチームに加わったメンバーも、「どのMCPサーバを使えばいいの?」と悩む必要がなくなります。また、ツールをAWS上で一元管理することで、「このツールを全社で使えるようにしよう」とか「新しいバージョンのツールをリリースしよう」といった作業も、クライアント側を変更することなくサーバー側だけで完結できます。
組織全体でのツール提供と管理を効率化するプラットフォームエンジニアリング的な運用も可能になるということですね。
Cost Analysis MCP ServerのツールをLambda関数に移植してみた
上記を実践するうえで気になるのは、「ツールをLambda関数にどう実装するか」という点ではないでしょうか。私もその部分が気になったため、実際にAWS MCP ServersのCost Analysis MCP Server
をLambda関数として実装してみました。
結論から言うと、ツールをLambda関数に実装する際は、もともとMCPサーバのツールとして実装していた関数をそのままLambda関数に移植するようなイメージになります。
Lambda関数として実装する際に特に重要なポイントは以下の通りです(これはAWS Lambda MCP Server
の仕様に基づいています)
- 関数名: MCPツール名として使用されます
- AWS Lambdaの
Description
プロパティ: MCPツールの説明として使用されます- 説明文では、この関数が「いつ」(何を提供するのか)、「どのように」(どのパラメータを使用するのか)を明確に記述する必要があります
- AIモデルが関数をいつ、どのように、どのパラメータとともに使用すべきかを理解できるよう、非常に詳細に記述することが重要です
具体的には以下のAnthropic公式ドキュメントのツールを定義する際のベストプラクティスに従うと良いと思います。
コードは以下に格納しています。ちなみにコードはClineで作成しました。
具体的にはCost Analysis MCP Serverの中の以下のツールに絞ってそれぞれLambda関数として実装しています
- get_pricing_from_web:Webスクレイピングを行い指定したサービスのWebページを取得するツール
- get_pricing_from_api:Price List APIを使って指定したサービス・リージョンの料金情報を取得します
- generate_cost_report:使用しているリソース情報を入力することで、コスト分析レポートを出力する
差分を見るとどのようにLambda関数で動作するようにしたかが、分かりやすいかと思います。Get_pricing_from_webの元コードとLambda関数との差分は以下の通りです。
-async def get_pricing_from_web(service_code: str, ctx: Context) -> Optional[Dict]:
- """Get pricing information from AWS pricing webpage.
+# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+# SPDX-License-Identifier: Apache-2.0
+# This file is based on code originally provided by Amazon Web Services and has been modified.
+import json
+import httpx
+from bs4 import BeautifulSoup
+
+def lambda_handler(event, context):
+ """AWS Lambda function to retrieve pricing information from AWS pricing webpage.
+
Args:
- service_code: The service code (e.g., 'opensearch-service' for both OpenSearch and OpenSearch Serverless)
- ctx: MCP context for logging and state management
+ event (dict): The Lambda event object containing the service code
+ Expected format: {"service_code": "lambda"}
+ context (dict): AWS Lambda context object
Returns:
- Dict: Dictionary containing the pricing information retrieved from the AWS pricing webpage
+ dict: Pricing information if found, otherwise an error message
"""
try:
+ # Extract service code from the event
+ service_code = event.get('service_code')
+
+ if not service_code:
+ return {
+ 'status': 'error',
+ 'message': 'Missing service_code parameter'
+ }
+
+ # サービスコードの前処理
for prefix in ['Amazon', 'AWS']:
if service_code.startswith(prefix):
service_code = service_code[len(prefix) :].lower()
service_code = service_code.lower().strip()
+
+ # AWSの価格ページにアクセス
url = f'https://aws.amazon.com/{service_code}/pricing'
- async with AsyncClient() as client:
- response = await client.get(url, follow_redirects=True, timeout=10.0)
+ response = httpx.get(url, follow_redirects=True, timeout=10.0)
response.raise_for_status()
+ # HTMLをパース
soup = BeautifulSoup(response.text, 'html.parser')
- # Remove script and style elements
+ # スクリプトとスタイル要素を削除
for script in soup(['script', 'style']):
script.decompose()
- # Extract text content
+ # テキストコンテンツを抽出
text = soup.get_text()
- # Break into lines and remove leading and trailing space on each
+ # 行に分割して各行の先頭と末尾の空白を削除
lines = (line.strip() for line in text.splitlines())
- # Break multi-headlines into a line each
+ # 複数行の見出しを1行ずつに分割
chunks = (phrase.strip() for line in lines for phrase in line.split(' '))
- # Drop blank lines
+ # 空行を削除
text = '\n'.join(chunk for chunk in chunks if chunk)
- result = {
+ # 結果を返す
+ return {
'status': 'success',
'service_name': service_code,
'data': text,
'message': f'Retrieved pricing for {service_code} from AWS Pricing url',
}
-
- # No need to store in context, just return the result
-
- return result
-
except Exception as e:
- await ctx.error(f'Failed to get pricing from web: {e}')
- return None
\ No newline at end of file
+ return {
+ 'status': 'error',
+ 'message': str(e),
+ }
上記の通り主要な部分は変わっていません。主な差分としてはdef lambda_handler
などのLambda関数としてのお作法的な部分です。
今回はLambdaのデプロイにCDKを使いました。CDKのコードは以下です。Lambdaを定義しているだけですが、上記のようにfunctionNameとdescriptionには注意しましょう。
実際にコスト分析してみる
実際に作成したLambdaをLambda MCP Serverから実行してコスト分析をしてみます。今回はMCPクライアントとしてClineを使用します。
ClineへのMCP設定は以下の通りです。
{
"mcpServers": {
"awslabs.lambda-mcp-server": {
"command": "uvx",
"args": ["awslabs.lambda-mcp-server@latest"],
"env": {
"AWS_PROFILE": "lambda-mcp-server",
"AWS_REGION": "ap-northeast-1",
"FUNCTION_LIST": "get_pricing_from_web, get_pricing_from_api, generate_cost_report"
}
}
}
}
envへの設定項目については以下の公式ドキュメントを参照いただければと思います。
注目していただきたいのがAWSプロファイルの部分で、今回このプロファイルには以下の権限しか付与していません。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"lambda:ListFunctions",
"lambda:ListTags"
],
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "lambda:InvokeFunction",
"Resource": [
"arn:aws:lambda:ap-northeast-1:<accountid>:function:get_pricing_from_api",
"arn:aws:lambda:ap-northeast-1:<accountid>:function:get_pricing_from_web",
"arn:aws:lambda:ap-northeast-1:<accountid>:function:generate_cost_report"
]
}
]
}
本来のCost Analysis MCP Serverでは上記の通り、Price List APIを使うツールがあるので、この権限がMCPクライアント側にも必要なのですが、ツールをLambda側に実装したおかげで、この権限がMCPクライアント側には不要となっています。
ちなみにget_pricing_from_api
Lambdaにはログ出力のための権限に加えて以下の権限がついています。
{
"Version": "2012-10-17",
"Statement": [
{
"Action": "pricing:GetProducts",
"Resource": "*",
"Effect": "Allow"
}
]
}
それではこの状態で、Cline(モデルはVS Code LM APIのcopilot - claude-3.5-sonnetを使用)から今回作成したLambdaのコスト見積もりをしてみましょう。
ちゃんとget_pricing_from_apiを呼び出していますね。
取得したデータを使ってgenerate_cost_reportツールを起動しています。get_pricing_from_apiで料金情報を取得したので正確な料金を取得できていますね。Lambdaの料金ページをみるとリクエスト料金がリクエスト 100 万件あたり USD 0.20(1リクエストあたりUSD 0.0000002)
、実行時間料金がGB-秒あたり USD 0.0000166667
なので、完全に一致しています。
generate_cost_reportツールによりレポートが出力されていますが、内容が以下の通りいまいちな感じになっています。generate_cost_reportツールは元々複雑な実装となっていたので、元のツールの機能を完全に再現できていなさそうです。
※単価の詳細 (Unit Pricing Details)のレポートのみ抜粋
サービス | リソースタイプ | 単位 | 価格 | 無料利用枠 |
---|---|---|---|---|
AWS Lambda | N/A | N/A | N/A |
最終的には以下のような出力結果となりました。実行時間料金が間違っていますね。
0.0000166667USD * (256/1024)GB * 5秒 * 1000
は自分が手元で計算した限りでは 0.02083338
ですので、桁を一つ間違えています。これはgenerate_cost_reportツールが正常に動作していないので、正確に計算ができていないのが原因でしょう。
まとめ
generate_cost_report
ツールは不完全なものの、get_pricing_from_api
についてはLambda上に移植し動作させることができました。
AWS Lambda MCP Server、いいですね。社内向けのデータにアクセスするMCPサーバであれば選択肢に入れてもいいと思います。
一方で実際にLambdaに移植する中で気になった点として、descriptionの文字制限というものがあります。
Lambdaをツールとして使う場合は上記の通り、Lambda関数のdescriptionがMCPツールのdescriptionになるわけですが、Lambdaのdescriptionは256文字の制限があります。
こちらのAnthropicのドキュメントにある通り、descriptionはなるべく詳しく書いたほうがLLMが適切にツールを使うことができます。ここの書き方が悪いとツールに間違った入力を与えてエラーレスポンスが返ってくるということが多発すると思うので、できるだけ詳細に書きたいわけですが、ここで256文字の制限が壁となるわけです。
英文で書いても256文字の制限は厳しいのでアップデートで記入できる文字数が増えると嬉しいなと思ったりしました。
以上、とーちでした。