
Deno + Lambda Web Adapterを使ってリモートMCPサーバーをLambdaで動かす
はじめに
Model Context Protocol(MCP)は、Claude Code や Claude Desktop などの MCP クライアントが外部システムと連携するためのプロトコルです。最近では、Claude Code がリモート MCP サーバーに対応したため、Deno + Lambda Web Adapter を利用した Lambda 上で動くリモート MCP サーバーを実装してみました。
Claude CodeのリモートMCPサーバーの対応については、以下のブログがわかりやすいです。
技術スタック
今回の実装で使用した技術は以下の通りです:
- Deno v2.x
- Express.js
- MCP TypeScript SDK
- AWS Lambda (Function URLs)
- Lambda Web Adapter
- AWS CDK
- TypeScript
参考実装
リモートMCPサーバーをLambdaで動かす
MCP サーバーの実装方法は主に以下の 3 つがあります。
- stdio (標準入出力による通信)
- SSE (Server-Sent Events: Streamable HTTP により現在は非推奨な扱い)
- Streamable HTTP (2025-03-26 のプロトコルバージョンから追加)
stdio についてはローカル MCP サーバー(ローカルの PC で起動しておく方式)用なので、リモート MCP サーバーについては SSE (Server-Sent Events) または Streamable HTTP を利用することになります。最新の MCP の仕様では SSE は非推奨な扱いになっているので、ここでは Streamable HTTP を利用する方法で実装します。実装自体は Express.js による Web API のアプリケーションを実装する方法になります。
今回は簡易的な Basic 認証を利用した方式で実装しましたが、2025-03-26 バージョンの仕様から OAuth 2.1 に基づく認証仕様の追加がありました。自分でも試してみましたがうまく動かなかったため、こちらについては引き続き調査をしたいと思っています。
Streamable HTTP を利用する方式では、以下の 3 つの /mcp
エンドポイントのうち必要なものを実装する必要があります。
POST /mcp
: 必須GET /mcp
: SSE エンドポイントとの互換性のために実装(SSE がない場合は不要)DELETE /mcp
: ステートフル MCP サーバーを実装する場合は必須
今回実装するのはステートレスな MCP サーバですので、POST /mcp
のみ実装することになります。
MCPサーバーの実装
コイントスをする簡易的な MCP サーバーを実装します。
server.ts
import { McpServer } from "npm:@modelcontextprotocol/sdk/server/mcp.js";
export function createMCPServer() {
const server = new McpServer({
name: "sample-mcp-server",
version: "1.0.0",
});
// コイントスツール
server.registerTool(
"coin_flip",
{
title: "コイントス",
description: "コインを投げて表(heads)か裏(tails)を返します",
inputSchema: {},
},
() => {
const result = Math.random() < 0.5 ? "表(heads)" : "裏(tails)";
return {
content: [{
type: "text" as const,
text: `コインの結果: ${result}`,
}],
};
},
);
return server;
}
Expressのアプリケーションの実装
MCP TypeScript SDK の StreamableHTTPServerTransport
を利用します。この辺の実装は以下の MCP TypeScript SDK の Streamable HTTPのWith Session Management (stateless) を参考にしています。ステートレス MCP サーバーの実装なのでセッション管理は行わないシンプルな構成です。
index.ts
import express, { type Request, type Response } from "npm:express";
import cors from "npm:cors";
import basicAuth from "npm:basic-auth";
import { StreamableHTTPServerTransport } from "npm:@modelcontextprotocol/sdk/server/streamableHttp.js";
import { createMCPServer } from "./server.ts";
const PORT = parseInt(Deno.env.get("PORT") || "8080");
const USERNAME = Deno.env.get("USERNAME");
const PASSWORD = Deno.env.get("PASSWORD");
if (!USERNAME || !PASSWORD) {
Deno.exit(1);
}
const app = express();
app.use(cors({
origin: true,
credentials: true,
}));
app.use(express.json());
// Basic認証
const authenticate = (req: Request, res: Response, next: () => void) => {
const credentials = basicAuth(req);
if (!credentials || credentials.name !== USERNAME || credentials.pass !== PASSWORD) {
res.status(401).set("WWW-Authenticate", 'Basic realm="Sample MCP Server"').json({
error: "Authentication required",
});
return;
}
next();
};
// Streamable HTTPのエンドポイント
app.post("/mcp", authenticate, async (req: Request, res: Response) => {
try {
const transport: StreamableHTTPServerTransport = new StreamableHTTPServerTransport({
sessionIdGenerator: undefined,
});
const server = createMCPServer();
res.on("close", () => {
console.log("Request closed");
transport.close();
server.close();
});
await server.connect(transport);
await transport.handleRequest(req, res, req.body);
} catch (error) {
console.error("Error handling MCP request:", error);
if (!res.headersSent) {
res.status(500).json({
jsonrpc: "2.0",
error: {
code: -32603,
message: "Internal server error",
},
id: null,
});
}
}
});
app.listen(PORT, () => {
console.log(`MCP Server running on port ${PORT}`);
console.log(`MCP endpoint: http://localhost:${PORT}/mcp`);
});
// Lambda関数URL用のエクスポート
export default app;
今回はステートレスな MCP サーバーですが、ステートフルな MCP サーバーを実装する場合は以下の仕様に沿った MCP サーバーを実装する必要があります。
MCP TypeScript SDK にはセッション管理を含めたステートフル MCP サーバーの実装方法も記載されているので参考にしてみてください。
Lambda Web Adapter用のDockerfile
Lambda Web Adapter を使うことで、Express.js のようなWebフレームワークをそのまま Lambda の上で動かすことができます。Lambda コンテナを使ってデプロイするため、Lambda Web Adapter を利用する Dockerfile を作成します。
FROM public.ecr.aws/awsguru/aws-lambda-adapter:0.9.1 AS aws-lambda-adapter
FROM denoland/deno:bin-2.3.6 AS deno_bin
FROM debian:bookworm-20230703-slim AS deno_runtime
COPY /lambda-adapter /opt/extensions/lambda-adapter
COPY /deno /usr/local/bin/deno
EXPOSE 8080
RUN mkdir /var/deno_dir
ENV DENO_DIR=/var/deno_dir
WORKDIR "/var/task"
COPY . /var/task
# Warmup caches
RUN timeout 20s deno task start || [ $? -eq 124 ] || exit 1
CMD ["deno", "task", "start"]
ECRにDockerイメージをデプロイしておく
ECR レジストリを別で作っておきます。以下のシェルスクリプトを使って Dockerfile のビルドとイメージの Push を行います。
#!/bin/bash
set -e
REGION="ap-northeast-1"
ECR_REGISTRY="123456789012.dkr.ecr.ap-northeast-1.amazonaws.com"
IMAGE_NAME="sample-mcp-server"
aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ECR_REGISTRY
docker build --platform=linux/amd64 -t $IMAGE_NAME .
docker tag $IMAGE_NAME:latest $ECR_REGISTRY/$IMAGE_NAME:latest
docker push $ECR_REGISTRY/$IMAGE_NAME:latest
echo "Deployed to $ECR_REGISTRY/$IMAGE_NAME:latest"
AWS CDKによるLambdaの構築
AWS CDK を使って、Lambda コンテナーを利用した Lambda 関数を作成します。今回は API Gateway は使わず AWS Lambda の関数 URL(Function URLs) の機能を利用し、Lambda 単体で HTTP エンドポイントを公開する構成にしました。Lambda 関数のみデプロイするだけで良くなり、MCP サーバーのようなシンプルな構成のアプリケーションにはオススメです。
app.ts:
#!/usr/bin/env -S deno run --allow-all
import { App } from "npm:aws-cdk-lib";
import { SampleMcpServerStack } from "./stack.ts";
const app = new App();
const env = {
account: Deno.env.get("CDK_DEFAULT_ACCOUNT"),
region: Deno.env.get("CDK_DEFAULT_REGION"),
};
const appContext: AppContext = app.node.tryGetContext("config");
if (!appContext) {
console.error("Error: 'env' context not found in cdk.json");
Deno.exit(1);
}
new SampleMcpServerStack(app, 'SampleMcpServerStack', {
appContext,
env,
});
export interface AppContext {
ecrRepoName: string;
}
stack.ts:
import { Duration, Stack, StackProps } from "npm:aws-cdk-lib";
import * as lambda from "npm:aws-cdk-lib/aws-lambda";
import * as ecr from "npm:aws-cdk-lib/aws-ecr";
import * as iam from "npm:aws-cdk-lib/aws-iam";
import { Construct } from "npm:constructs";
import { AppContext } from "./app.ts";
export interface SampleMcpServerStackProps extends StackProps {
appContext: AppContext;
}
export class SampleMcpServerStack extends Stack {
public readonly mcpServerFunction: lambda.Function;
constructor(scope: Construct, id: string, props: SampleMcpServerStackProps) {
super(scope, id, props);
const lambdaRole = new iam.Role(this, 'McpServerLambdaRole', {
assumedBy: new iam.ServicePrincipal("lambda.amazonaws.com"),
managedPolicies: [
iam.ManagedPolicy.fromAwsManagedPolicyName("service-role/AWSLambdaBasicExecutionRole"),
]
});
this.mcpServerFunction = new lambda.Function(this, 'McpServerFunction', {
functionName: 'remote-mcp-server-sample',
code: lambda.Code.fromEcrImage(
ecr.Repository.fromRepositoryName(
this,
`McpServerEcrRepo`,
props.appContext.ecrRepoName
)
),
environment: {
USERNAME: "hoge",
PASSWORD: "hoge",
},
handler: lambda.Handler.FROM_IMAGE,
runtime: lambda.Runtime.FROM_IMAGE,
role: lambdaRole,
timeout: Duration.seconds(30),
memorySize: 256,
});
this.mcpServerFunction.addFunctionUrl({
authType: lambda.FunctionUrlAuthType.NONE,
cors: {
allowCredentials: true,
allowedHeaders: ["*"],
allowedMethods: [lambda.HttpMethod.ALL],
allowedOrigins: ["*"],
maxAge: Duration.days(1),
},
});
}
Deno 経由で AWS CDK でデプロイします。
deno run --allow-all npm:aws-cdk --app 'deno run --allow-all cdk/app.ts' deploy
MCPクライアントから利用する
今回は Claude Code から利用してみたいと思います。AWS コンソールからデプロイした Lambda 関数のページに移動すると関数 URL が取得できると思いますので、こちらをコピーします。
以下のコマンドで先ほどコピーしたURLを設定し Claude Code に Streamable HTTP のリモート MCP サーバーを登録します。
claude mcp add mcp-server-sample --transport http --header "Authorization: Basic $(echo -n 'username:password' | base64)" -- https://xxx.lambda-url.ap-northeast-1.on.aws/mcp
「コイントスして」と Claude Code のプロンプトに入力すると、Lambda の MCP サーバーにアクセスされコイントスの結果が表示されました。
╭─────────────────────────────────────────────────────────────────────╮
│ ✻ Welcome to Claude Code! │
│ │
│ /help for help, /status for your current setup │
│ │
│ cwd: /Users/sato.naoya/WebstormProjects/remote-mcp-server-sample │
╰─────────────────────────────────────────────────────────────────────╯
※ Tip: Run /install-github-app to tag @claude right from your Github issues and PRs
╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ > コイントスして │
╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
> コイントスして
⎿ コインの結果: 裏(tails)
⏺ 裏(tails)が出ました。
まとめ
Deno と Lambda Web Adapter を使用して AWS Lambda 上で動作するリモート MCP サーバーを実装する方法を紹介しました。Lambda Web Adapter と Lambda Functions URLs を利用することで Lambda のみで完結するリモートMCPサーバーを作成することができます。