Skip to content

Commit 34fcfaa

Browse files
authored
Merge pull request #1791 from future-architect/feature
API GatewayとLambdaで実装するプライベートなMCPサーバー
2 parents c4463fd + d02c766 commit 34fcfaa

7 files changed

Lines changed: 314 additions & 0 deletions

File tree

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
---
2+
title: "API GatewayとLambdaで実装するプライベートなMCPサーバー"
3+
date: 2026/03/24 00:00:00
4+
postid: a
5+
tag:
6+
- MCP
7+
- Lambda
8+
- Dify
9+
- APIGateway
10+
category:
11+
- Infrastructure
12+
thumbnail: /images/2026/20260324a/thumbnail.png
13+
author: 木村太陽
14+
lede: "Strategic AI Group/MLOpsチームでアルバイトをしている木村です。アルバイトでは最新技術の調査を担当し、社内や案件にて活用することを想定したシステム導入の検証を実施しています。AWS上のLambdaで自作したプライベートなMCPサーバーをDify上で使用する手順について記事にします。"
15+
---
16+
Strategic AI Group/MLOpsチームでアルバイトをしている木村です。アルバイトでは最新技術の調査を担当し、社内や案件にて活用することを想定したシステム導入の検証を実施しています。プライベートではエンジニアにありがちな(?)運動不足解消として、マラソンをしていて、47都道府県制覇を目指しています。
17+
18+
今回はAWS上のLambdaで自作したプライベートなMCPサーバーをDify上で使用する手順について記事にします。これから社内でもどんどん広がっていくであろうMCPを使っていてとても面白かったので、ぜひ皆さんも本記事を参考に試してみてください!
19+
20+
# 概要
21+
22+
「社内でLLMに様々なスキルを持たせたい」というのは、多くの企業が抱える要望です。本記事では、AWS Lambda上に自作のプライベートなMCP(Model Context Protocol)サーバーを構築し、それをDifyから呼び出す手順を解説します。
23+
24+
セキュリティを担保しつつ、誰もが簡単にAIアプリを構築できる環境の「第一歩」を目指します。
25+
26+
最終的には以下のようにDifyからMCPサーバーにアクセスできます。
27+
28+
以下のツールではこんにちはに対してHELLOを返すcalculate_helloというmcpツール、計算について、足し算、掛け算、引き算を行うcalculate_add,calculate_product,calculate_subというmcpツールが使われています。
29+
30+
<img src="/images/2026/20260324a/image.png" alt="image.png" width="844" height="724" loading="lazy">
31+
32+
これを応用してWeb検索を行ったり、Googleカレンダーに予定を自動で入れることができます。
33+
34+
# 本記事のキーテクノロジー
35+
36+
## Dify
37+
38+
- [Dify公式サイト](https://dify.ai/jp)
39+
40+
Difyは、LLMアプリを直感的に開発できるオープンソースのLLMアプリ開発プラットフォームです。RAG(検索拡張生成)やエージェント機能を手軽に実装できるのが特徴です。
41+
42+
本記事では社内ネットワークのEC2でDifyを動かすことでプライベートな構築を実現しています。
43+
44+
## MCP
45+
46+
Anthropicが公開したMCPは(参考:[Model Context Protocol(MCP)とは](https://zenn.dev/cloud_ace/articles/model-context-protocol))、LLMと外部データ(DB、API、ローカルファイル等)を接続するためのオープンプロトコルです。これまではツールごとに専用の繋ぎ込みが必要でしたが、MCPという「共通規格」を通すことで、一つのMCPサーバーを作るだけで様々なAIクライアントからデータを利用可能になります。似ているものでRAGがありますがRAGは「知識の検索・参照」に特化しているのに対し、MCPは「機能(ツール)の呼び出し・実行」に特化しています。
47+
48+
<img src="/images/2026/20260324a/image_2.png" alt="image.png" width="709" height="330" loading="lazy">
49+
50+
## API Gateway+Lambdaによるプライベートな環境
51+
52+
社内DBや秘匿性の高い情報が流出するリスクを排除するため、社内ネットワーク内での運用が求められるケースがあります。
53+
54+
今回は上記を実現するべく、API Gatewayのアクセスを社内ネットワークからに制限し、すべてのリソースを社内に置いておくことでプライベートな環境を実現することを目指しました。
55+
※前提として、社内ネットワークとAWS環境がVPN接続されていることとします。
56+
57+
## 通信方式
58+
59+
MCPには、ローカルMCP(stdio)とリモートMCP(Streamable HTTP)の2種類があります。ローカルMCPの場合、同じ環境を相手のPCにも構築する必要があり、共有に手間がかかってしまうので、リモートMCPを採用しました。
60+
61+
また、リモートMCPをサーバレスで実行するために通信方式は以下の設定にしています。(参考:[MCPの通信方式](https://qiita.com/nogataka/items/7fbeb58339703ec98a86))
62+
63+
- stateless_http: stateless_http=True に設定し、本来Statefulな通信を前提とするMCPプロトコルをステートレスなHTTP通信に変換することでLambdaのような1回切りのリクエストに対応させます
64+
- Streamable_http:Lambdaでの1回限りの重い処理を、タイムアウトで切断される前に小出しで届けて完結させる
65+
66+
# 構成図
67+
68+
本構成では、セキュリティを最優先し、API Gatewayを「プライベート」モードでデプロイします。これにより、社内ネットワークからのみAIツールを呼び出すことが可能になります。
69+
70+
<img src="/images/2026/20260324a/image_3.png" alt="image.png" width="696" height="364" loading="lazy">
71+
72+
## Lambda採用理由
73+
74+
EC2でMCPサーバをホストした場合、常時EC2を起動しておく必要があり、利用していない期間も不要な料金が発生します。そこでサーバレスなLambdaでホストすることで、MCPサーバーが呼ばれた時だけ料金が発生するので、コストを抑えられます。
75+
76+
## 本構成のデメリット
77+
78+
大量のログ解析や複雑なデータ集計など、完了までに時間がかかるタスクを依頼すると、AIに結果が返る前にAPI Gatewayのタイムアウト制限より、接続が切れてエラーになる可能性があります。
79+
80+
また、VPCエンドポイントは月10ドルほどの固定費がかかるというデメリットも挙げられます。
81+
82+
# 構築手順
83+
84+
1. まずは開発環境(EC2)にAWS SAM CLIやPython 3.11をインストールし、必要なIAMロールを付与します
85+
2. コードを作成してAWSにデプロイ
86+
87+
ファイル構成は以下の通りです。
88+
89+
```sh
90+
.
91+
├── mcp_server
92+
│ ├── __init__.py
93+
│ └── __main__.py
94+
├── requirements.txt
95+
├── run.sh
96+
└── template.yaml
97+
```
98+
99+
mcp_server/`__init__.py` は空ファイルで大丈夫です。
100+
101+
```bash
102+
touch mcp_server/'__init__.py'
103+
```
104+
105+
```py mcp_server/__main__.py
106+
import uvicorn
107+
from fastmcp import FastMCP
108+
109+
# FastMCPの初期化
110+
# stateless_http=True にすることで、Lambdaのような1回切りのリクエストに対応させます
111+
mcp = FastMCP("MyRemoteMCP", stateless_http=True)
112+
113+
# ツール定義:型ヒントとドキュメント文字列がそのままMCPの定義になります
114+
@mcp.tool()
115+
def calculate_add(a: float, b: float) -> str:
116+
"""2つの数値を足し合わせます"""
117+
result = a + b
118+
return f"計算結果: {a} + {b} = {result}"
119+
120+
# @mcp.tool()でいくつでもツールを追加可能
121+
122+
# FastMCPが内部で生成したFastAPIアプリを抽出
123+
app = mcp.http_app()
124+
125+
if __name__ == "__main__":
126+
# Lambda Web Adapterが待機する8080ポートで起動
127+
uvicorn.run(app, host="0.0.0.0", port=8080)
128+
```
129+
130+
```sh run.sh
131+
#!/bin/bash
132+
exec python -m mcp_server
133+
```
134+
135+
run.shに実行権限を付与。
136+
137+
```bash
138+
chmod +x run.sh
139+
```
140+
141+
```text requirements.txt
142+
fastmcp==2.14.5
143+
fastapi==0.128.1
144+
uvicorn==0.40.0
145+
```
146+
147+
3. AWSリソースの構築
148+
149+
以下のSam templateを実行すれば本記事のAWSリソースが構築できます。
150+
[]内は各々の環境に合わせて変更してください。
151+
152+
```yaml
153+
Transform:
154+
- AWS::Serverless-2016-10-31
155+
- AWS::LanguageExtensions
156+
157+
Parameters:
158+
VpcId: { Type: String, Default: vpc-[VPCID] }
159+
SubnetId1: { Type: String, Default: subnet-[サブネットID] }
160+
SubnetId2: { Type: String, Default: subnet-[サブネットID] }
161+
162+
Resources:
163+
# --- セキュリティグループ ---
164+
InternalServiceSG:
165+
Type: AWS::EC2::SecurityGroup
166+
Properties:
167+
GroupDescription: Allow internal traffic for MCP Server
168+
VpcId: !Ref VpcId
169+
SecurityGroupIngress:
170+
- IpProtocol: tcp
171+
FromPort: 443
172+
ToPort: 443
173+
CidrIp: [CiderIp]
174+
175+
# --- VPCエンドポイント (API Gateway用) ---
176+
MyVpce:
177+
Type: AWS::EC2::VPCEndpoint
178+
Properties:
179+
ServiceName: !Sub "com.amazonaws.${AWS::Region}.execute-api"
180+
VpcEndpointType: Interface
181+
VpcId: !Ref VpcId
182+
SubnetIds: [ !Ref SubnetId1, !Ref SubnetId2 ]
183+
SecurityGroupIds: [ !Ref InternalServiceSG ]
184+
PrivateDnsEnabled: true
185+
186+
# --- API Gateway (Private) ---
187+
MyApi:
188+
Type: AWS::Serverless::Api
189+
DependsOn: MyVpce
190+
Properties:
191+
Name: mcp-api
192+
StageName: prod
193+
EndpointConfiguration:
194+
Type: PRIVATE
195+
VPCEndpointIds:
196+
- !Ref MyVpce
197+
Auth:
198+
ResourcePolicy:
199+
CustomStatements:
200+
- Effect: Allow
201+
Principal: "*"
202+
Action: "execute-api:Invoke"
203+
Resource: "execute-api:/*/*/*"
204+
- Effect: Deny
205+
Principal: "*"
206+
Action: "execute-api:Invoke"
207+
Resource: "execute-api:/*/*/*"
208+
Condition:
209+
StringNotEquals:
210+
"aws:SourceVpce": !Ref MyVpce
211+
DefinitionBody:
212+
openapi: "3.0.1"
213+
paths:
214+
/{proxy+}:
215+
x-amazon-apigateway-any-method:
216+
x-amazon-apigateway-integration:
217+
httpMethod: POST
218+
type: aws_proxy
219+
uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${Function.Arn}/invocations"
220+
responses: {}
221+
222+
# --- Lambda関数(VPC内) ---
223+
Function:
224+
Type: AWS::Serverless::Function
225+
Properties:
226+
Architectures: [arm64]
227+
Runtime: python3.11
228+
Timeout: 30
229+
CodeUri: .
230+
Handler: run.sh
231+
Layers:
232+
- !Sub arn:aws:lambda:${AWS::Region}:753240598075:layer:LambdaAdapterLayerArm64:18
233+
VpcConfig:
234+
SecurityGroupIds: [ !Ref InternalServiceSG ]
235+
SubnetIds: [ !Ref SubnetId1, !Ref SubnetId2 ]
236+
Environment:
237+
Variables:
238+
AWS_LAMBDA_EXEC_WRAPPER: /opt/bootstrap
239+
PORT: 8080
240+
Events:
241+
ProxyApiRoot:
242+
Type: Api
243+
Properties:
244+
RestApiId: !Ref MyApi
245+
Path: /{proxy+}
246+
Method: ANY
247+
248+
# --- API GatewayからLambdaを呼ぶ許可 ---
249+
LambdaInvokePermission:
250+
Type: AWS::Lambda::Permission
251+
Properties:
252+
Action: lambda:InvokeFunction
253+
FunctionName: !Ref Function
254+
Principal: apigateway.amazonaws.com
255+
SourceArn: !Sub "arn:aws:execute-api:${AWS::Region}:${AWS::AccountId}:${MyApi}/*"
256+
257+
Outputs:
258+
FunctionArn:
259+
Description: "Lambda Function ARN"
260+
Value: !GetAtt Function.Arn
261+
VpceId:
262+
Description: "VPC Endpoint ID"
263+
Value: !Ref MyVpce
264+
ApiUrl:
265+
Description: "API Gateway URL"
266+
Value: !Sub "https://${MyApi}.execute-api.${AWS::Region}.amazonaws.com/prod/"
267+
268+
```
269+
270+
本構成で重要な部分をピックアップして解説します。
271+
272+
API Gateway:
273+
274+
- エンドポイントタイプ:プライベート
275+
- VPCエンドポイントID:[エンドポイントID(vpce-xxxx)]
276+
- リソースポリシー:特定のVPCエンドポイント経由のアクセスのみ許可
277+
- 統合タイプ:Lambda関数
278+
- Lambdaプロキシ統合:ON
279+
- Lambda関数:SAMで作った関数の名前(mcp-server-stack-Function-xxx)
280+
- デプロイの際のステージ名:prod
281+
282+
Lambda:
283+
284+
- LambdaをVPCのプライベートサブネットを指定することで、インターネットからは直接見えない状態に設定
285+
286+
VPCエンドポイント:
287+
288+
- VPCEndpoint(Interface型)の採用によりAPI Gatewayをインターネットに公開せず、VPC内部のプライベートIPだけで叩けるようにします。
289+
290+
## Difyからの接続
291+
292+
Difyのツール>MCPからツールを追加します。
293+
294+
- サーバー名:https://[自分のVPCEのDNS名(上から2つ目)]/prod/mcp
295+
- ヘッダー名:HOST
296+
- ヘッダーの値:[自分のAPIのID].execute-api.ap-northeast-1.amazonaws.com
297+
298+
<img src="/images/2026/20260324a/image_5.png" alt="image.png" width="546" height="876" loading="lazy">
299+
300+
# 利用結果
301+
302+
Difyのツール設定から外部MCPツールを登録し、実際に計算を依頼した結果です。
303+
304+
<img src="/images/2026/20260324a/image_6.png" alt="image.png" width="844" height="724" loading="lazy">
305+
306+
VPCエンドポイント経由の閉域網通信でありながら、Difyのエージェント機能によってmcpツールが呼び出されていることが確認できました。
307+
308+
# まとめ
309+
310+
- したことまとめ
311+
- AWS Lambda + API Gateway (Private) によるセキュアでサーバレスなMCPサーバーの構築
312+
- Dify との連携による実用的なAIエージェント環境の構築
313+
- 次やりたいこと
314+
- 今回は数値計算のシンプルなツールでしたが、次はGoogleカレンダーAPIとの連携によるスケジュール調整の自動化に挑戦し、最終的には秘書のようなAIを作りたいと考えています
96.1 KB
Loading
43.3 KB
Loading
42.4 KB
Loading
79.1 KB
Loading
96.1 KB
Loading
17.5 KB
Loading

0 commit comments

Comments
 (0)