- 示例工程
【免费下载链接】examples
Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework on AWS Lambda, Microsoft Azure, Google Cloud Functions, and more.
导读
本文基于 Serverless Examples 仓库中的 mcp/custom/function-url 示例,讲解如何用官方 MCP TypeScript SDK 构建的 MCP Server 直接跑在 AWS Lambda Function URL 上,并借助 Lambda 响应流(RESPONSE_STREAM)在同一端点同时承载普通 JSON 与流式 SSE 响应。读完本文,你将掌握 Function URL 事件与 Node 风格请求/响应的适配写法、serverless.yml的关键配置、12 项协议级自动化测试的用法,以及 IAM(SigV4)与进程内 OAuth 两种认证方案的取舍——这是目前 AWS 上最精简、最省钱的 MCP 托管形态:无需 API Gateway、无按请求计费,流式响应只受函数超时限制。
为什么选择 Function URL:最精简的 MCP 托管
在 mcp/custom 系列示例中,官方 SDK 构建的同一个"标准 MCP Server"被以多种宿主方式部署到 AWS,每个示例只有托管胶水代码不同,服务端业务逻辑完全相同。其中function-url示例代表的是"最精简"的一类:
- 无 API Gateway:Function URL 由 Lambda 服务直接暴露 HTTPS 端点,调用直达函数,省去中间层;
- 无按请求费用:不像 API Gateway 有每请求计费,Lambda 的流式模式仅对响应超过前 6 MB 的出站流量计量(参见 mcp/custom/README.md 中的对比表);
- 流式只受函数超时限制:SSE 流可以一直写到函数
timeout上限(Lambda 函数最长可配 15 分钟),无需像 API Gateway 那样额外调timeoutInMillis,也不存在边缘端点 30 秒空闲截断的问题。
示例 README 明确给出选择建议:需要自定义域名、WAF、限流或"在调用函数之前就拒绝请求"的鉴权器时,选择 rest-api(API Gateway REST 流式模式);追求最简单、最便宜的部署时,选本示例(参见 function-url/README.md)。
三个组成部分:服务端、适配层与配置
整个示例只有三个关键部分,职责划分非常干净:
| 文件 | 职责 |
|---|---|
| src/server.mjs | 标准 MCP Server:工具、资源、提示词、server/discover与缓存提示,不含任何 Lambda 相关代码 |
| src/lambda.mjs | 约 80 行适配胶水:把 Function URL 事件与 Lambda 响应流映射成@modelcontextprotocol/node认识的 Node 风格请求/响应,不含任何 MCP 逻辑 |
| serverless.yml | Serverless Framework 配置:Function URL 开启invokeMode: RESPONSE_STREAM |
得益于这种"服务端与宿主解耦"的设计,同一个 server.mjs 在仓库里被多份复制,validate.js 会在仓库根目录执行校验,任何副本漂移都会导致校验失败。
1. 标准 MCP Server:完整覆盖协议面
src/server.mjs 用官方 SDK v2(@modelcontextprotocol/server)构建服务端,通过createMcpHandler导出一个 web 标准的 fetch handler,可以跑在任何 web 标准宿主上。它覆盖了 MCP 协议的主要能力面:
add工具:普通工具,用 zod 定义输入输出 schema,返回structuredContent(结构化内容)供客户端以类型化方式消费,对应测试断言sum=42;slow_report工具:长耗时工具,通过ctx.mcpReq._meta.progressToken读取客户端请求的进度令牌,循环中调用ctx.mcpReq.notify({ method: 'notifications/progress', ... })逐条发送进度通知,最终返回普通文本结果;approve_refund工具:展示"elicitation(征询)"往返——工具调用中途暂停向用户提问,客户端回答后带inputResponses重试,服务端用acceptedContent读取回答。源码中对"拒绝/取消"做了专门处理:inputResponse检查action !== 'accept'时直接终止返回refund cancelled,避免客户端无限循环追问;- 资源:静态资源
guide://usage(README 文档)与模板资源orders://{orderId}(ResourceTemplate,按请求参数解析,返回订单 JSON); - 提示词:
summarize_order模板,客户端可prompts/list与prompts/get填充参数; server/discover:通过instructions向客户端暴露使用说明;- 缓存提示:在
cacheHints中为tools/list配置ttlMs: 300000, cacheScope: 'public',允许客户端与共享缓存复用工具列表五分钟;若不加,SDK 会为每个可缓存结果输出保守默认值(ttlMs: 0, cacheScope: private)。
注意package.json(见 package.json)中的依赖只有三个:@modelcontextprotocol/server、@modelcontextprotocol/node与zod,运行环境要求 Node.js 20+(示例配置为nodejs22.x)。
2. Lambda 适配层:把 Function URL 事件映射成 Node 风格请求
src/lambda.mjs 是这个示例的核心创意所在:SDK 的@modelcontextprotocol/node包的toNodeHandler接受 Node 风格的请求/响应对象(结构化鸭子类型),而 Function URL 的 HTTP API v2 事件 + Lambda 响应流恰好能提供这两类形状,于是只需要写胶水:
toNodeRequest(event):把 Function URL 事件转成 Node 风格请求——头部全部小写化、cookies合并进cookie头、isBase64Encoded决定 body 解码方式、用rawPath + rawQueryString拼接 URL,并提供一个异步迭代器yieldbody;toNodeResponse(rawStream):包装awslambda.HttpResponseStream——writeHead时创建带状态码与响应头的流、write/end转发真实布尔值以保持背压、在目标流创建前暂存监听器(pendingListeners),确保on事件在writeHead之后也能挂上;- 导出:
awslambda.streamifyResponse(async (event, responseStream) => ...)包装整个适配流程,等待close/finish/error事件保证流完整结束。
整层没有任何 MCP 协议逻辑,纯粹是事件形状的转换。如果不想手写适配,hono 与 express-web-adapter 示例提供了基于现成维护包的零胶水替代方案。
3. serverless.yml:一行开启响应流
serverless.yml 的关键配置如下:
service: aws-mcp-function-url provider: name: aws runtime: nodejs22.x # MCP SDK v2 要求 Node.js 20+ functions: mcp: handler: src/lambda.handler timeout: 120 # 流式响应可一直跑到函数超时上限 url: invokeMode: RESPONSE_STREAM要点:
invokeMode: RESPONSE_STREAM是核心开关,让 Lambda 把响应以流式方式回传,SSE 才能在同一个端点上工作;timeout: 120是流的"唯一上限"——流可以持续到函数超时,无需其他配置;示例 README 说明流式响应最长可配 15 分钟(function-url/README.md 的 Going further 一节);- Function URL 默认无鉴权(公共端点),适合试用;真实部署需要打开注释中的
authorizer: aws_iam或进程内 OAuth(见下文"认证"一节)。
部署与测试
部署
npm install serverless deploy部署输出的 URL 就是 MCP 端点(serverless.yml中函数mcp对应的 Function URL)。
测试:共享客户端验证 12 项协议检查
该系列每个示例都附带同一个测试客户端 client.mjs,用一条命令运行:
ENDPOINT=<function URL from deploy output> node client.mjs脚本会按顺序执行 12 项协议检查,覆盖整个 MCP 能力面(详见 client.mjs):
PASS 1. tools/list returns the canonical tools with cache hints — ttlMs=300000 cacheScope=public PASS 2. add returns plain JSON with structured content — sum=42 PASS 3. slow_report streams incremental progress over SSE — 3 progress events, first at +810ms PASS 4. approve_refund elicitation round-trip (accept and cancel) — input_required -> refunded o-1 / refund cancelled PASS 5. resources list + read (Mcp-Name carries the uri) — guide://usage readable PASS 6. resource template read with per-resource cache hint — orders://o-42 -> shipped, ttlMs=60000 PASS 7. prompts list + get — summarize_order fills its argument PASS 8. server/discover surfaces the instructions — instructions present PASS 9. legacy initialize is answered on the same endpoint — served as 2025-06-18 PASS 10. GET is answered with the spec-mandated 405 — HTTP 405 PASS 11. Mcp-Method header mismatching the body is rejected (-32020) — -32020 PASS 12. elicitation without the client capability is rejected (-32021) — -32021 12/12 passed值得关注的是两个负向用例(第 11、12 项),它们证明了2026-07-28协议校验在端到端链路中是生效的:
Mcp-Method请求头与 body 中方法不一致时返回-32020;- 客户端未声明
elicitation能力却调用需要征询的工具时返回-32021。
客户端实现上值得注意的细节:请求同时带accept: application/json, text/event-stream,响应若为 SSE(content-type: text/event-stream)则逐帧解析data:前缀的 JSON,按时间戳记录事件到达顺序;普通的 JSON 响应则直接解析。resources/read时 URI 通过Mcp-Name请求头传递(对应服务端示例 README 所述"Mcp-Name header carries the uri")。
LONG=1:验证 36 秒长流
LONG=1 ENDPOINT=<function URL from deploy output> node client.mjs加LONG=1后会追加两个约 36 秒的长调用用例(对应客户端脚本第 13、14 项检查):
- 流式场景:
slow_report以 45 步、每步约 800ms 的节奏推送 45 条 progress 事件,断言跨过 29 秒后流仍然存活、最终结果正常收到; - 静默场景:不带
progressToken的 45 步调用在约 36 秒内不写任何字节然后一次性返回普通 JSON——这正是边缘优化的 API Gateway 会截断的场景(其空闲超时从调用开始计时而非首字节,调大timeoutInMillis也无济于事),因此 REST 前门的示例必须部署在区域端点;而 Function URL 上无需任何额外超时配置,函数timeout就是唯一边界(function-url/README.md)。
认证:公共端点之外的两个选项
示例默认端点公开,便于体验。真实部署有两种方案(源码中均已包含注释):
- IAM(SigV4):在 serverless.yml 中取消
authorizer: aws_iam注释,仅允许持有 AWS 凭证的调用方访问;共享客户端用AUTH=sigv4 SERVICE=lambda ENDPOINT=... node client.mjs测试该模式。客户端脚本内置了@smithy/signature-v4+@aws-sdk/credential-provider-node的 SigV4 签名器(见 client.mjs 中AUTH === 'sigv4'分支)。 - 进程内校验:利用 SDK 的
requireBearerAuth配合jose的 JWKS 验证器(src/server.mjs 底部注释块),校验来自任意 OpenID Connect 提供方的 OAuth Bearer 令牌。注释中给出了完整的createRemoteJWKSet+jwtVerify验证器实现,可校验issuer、audience,并从payload中提取clientId、scopes、expiresAt。
进阶:签名往返状态与长时工具
- 签名往返状态:当 elicitation 往返必须携带不可被篡改的服务端状态(如计算出的价格、幂等键)时,可用
createRequestStateCodec以 HMAC 密封状态——示例注释给出了用法:createRequestStateCodec({ key: process.env.MCP_STATE_KEY, ttlSeconds: 600 }),密钥(至少 32 字节)应存放在 Secrets Manager 或 SSM 中; - 长时工具:直接把函数
timeout调大即可——流式响应最长可运行 15 分钟,不需要其他配置。
清理
serverless remove与其他宿主方式的对比
function-url只是 mcp/custom 系列的一个成员。从系列总览(mcp/custom/README.md)可以看出各例子的定位差异:
| 示例 | 宿主 | 特点 |
|---|---|---|
| rest-api | API Gateway REST 流式 | 功能最全的前门:自定义域名、WAF、限流、网关鉴权器 |
| function-url(本文) | Lambda Function URL | 最精简:无 API Gateway、无按请求费用、流只受函数超时限制 |
| hono | Function URL | 零自定义胶水(官方@modelcontextprotocol/hono) |
| express-web-adapter | API Gateway REST 流式 + Web Adapter | 纯 Express 应用,代码可在任何地方原样运行 |
| fastify-container | Function URL + 容器镜像 | 可移植镜像,也能跑在 Fargate/App Runner |
三个维度(Web 框架/桥接、打包方式、前门)彼此完全独立,各示例只是各固定一种组合以保持最小化。在"选前门"这件事上,规则很清晰:要自定义域名、WAF、限流或请求级鉴权选 API Gateway;要最简单最省钱、且流只受函数超时约束,选 Function URL——即本示例的做法。
- 示例工程
【免费下载链接】examples
Serverless Examples – A collection of boilerplates and examples of serverless architectures built with the Serverless Framework on AWS Lambda, Microsoft Azure, Google Cloud Functions, and more.
相关推荐
tRPC × AWS Lambda Adapter 实战指南:接入 API Gateway v1/v2、Lambda Function URL 与响应流式返回
tRPC × AWS Lambda Adapter 实战指南:接入 API Gateway v1/v2、Lambda Function URL 与响应流式返回
后端RPC框架前端Apollo Lambda GraphQL API 实战:在 AWS Lambda 上用 TypeScript 搭建 Apollo Server(Serverless Framework v4)
Apollo Lambda GraphQL API 实战:在 AWS Lambda 上用 TypeScript 搭建 Apollo Server(Serverl
示例工程在 AWS Lambda 上托管 MCP Server:Serverless Framework `mcp` 配置块完整实战指南
在 AWS Lambda 上托管 MCP Server:Serverless Framework mcp 配置块完整实战指南 导读 本文将围绕 Serverle
开发工具CLI云原生后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考