模型上下文协议服务

一个基于AWS Lambda的模型上下文协议(MCP),为在线商店提供AI代理层,支持自然语言对话处理与流式响应。

  • by asyrul21
  • TypeScript
  • 本地部署
  • 更新于
在 GitHub 查看 asyrul21/reso-online-store-mcp

使用说明

RESO在线商店MCP

基于AWS Lambda的 模型上下文协议(MCP) 它在 reso在线商店服务器MCP接收自然语言对话,运行OpenAI驱动的代理循环,并将响应流式传输回React UI。

建筑

React UI
  │
  │  POST /mcp/admin or /mcp/client
  ▼
CloudFront Distribution  (HTTPS, custom domain support, SigV4 signing)
  │
  │  AWS_IAM signed request
  ▼
Lambda Function URL  (RESPONSE_STREAM — no 29s timeout)
  │
  │  awslambda.streamifyResponse
  ▼
Lambda Handler (src/index.ts)
  │
  ├── GET /api/auth/me  ──► online-store-server  (verify JWT, get user)
  │
  ├── OpenAI agentic loop (run.ts)
  │     ├── max 20 iterations
  │     ├── max 200,000 cumulative tokens
  │     └── tool calls ──► online-store-server REST API
  │
  └── Streams response back to React UI

关键设计决策

  • 没有Prisma --通过HTTP调用访问所有数据 online-store-server
  • 令牌转发 --每个工具调用都会转发用户的JWT+ client-country-code 头球
  • 身份验证类型AWS_IAM --Lambda函数URL不可公开访问;CloudFront使用源访问控制(OAC)+SigV4对请求进行签名
  • 添加剂 — reso-online-store-server 无需此MCP即可独立工作

先决条件

工具 版本 安装
Node.js 20.x nodejs.org
npm 10.x+ 与Node捆绑在一起
TypeScript 5.x 包含在devDependencies中
AWS SAM命令行界面 1.x aws.amazon.com/serverless/sam
AWS命令行界面 2.x aws.amazon.com/cli
Docker 任何 需要 sam local invoke 只有

环境变量

复制 .env.example 到 .env 并填写以下值:

cp .env.example .env
变量 必填 描述
API_SERVER_URL 是 的基本URL reso-online-store-server
OPENAI_API_KEY 是 您的OpenAI API密钥
OPENAI_MODEL 否 要使用的模型(默认值: gpt-4o)
MAX_ITERATIONS 否 最大代理循环迭代次数(默认值: 20)
MAX_TOTAL_TOKENS 否 每个请求的最大累积令牌数(默认值: 200000)
PORT 否 本地Express服务器端口(默认值: 3001)

在AWS中: OPENAI_API_KEY 从以下内容读取 AWS SSM参数存储 路径处的(SecureString) /reso-mcp/openai-api-key。在部署之前设置它:

aws ssm put-parameter \
  --name /reso-mcp/openai-api-key \
  --value "sk-..." \
  --type SecureString

1.构建和验证

# Install dependencies
npm install

# Build TypeScript (outputs to dist/)
npm run build

# Check for TypeScript errors without emitting
npx tsc --noEmit

如果 npm run build 完成时没有错误,项目设置正确。


2.使用Express Server进行本地开发

SAM本地做 不 支持Lambda函数URL RESPONSE_STREAM。相反,使用内置的Express开发服务器,它反映了Lambda处理程序的行为。

步骤1:启动在线商店服务器

# In the reso-online-store-server directory
cd ../reso-online-store-server
npm run dev
# Server starts on http://localhost:8080

步骤2:启动MCP本地服务器

# In this directory
npm run dev
# MCP server starts on http://localhost:3001

或者使用辅助脚本:

./scripts/local.sh

第3步:使用curl进行测试

健康检查 (无需身份验证):

curl http://localhost:3001/mcp/health
# { "status": "ok", "timestamp": "..." }

管理代理 (需要来自在线商店服务器的有效JWT):

curl -X POST http://localhost:3001/mcp/admin \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "client-country-code: MY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": [
      { "role": "user", "content": "How many categories do we have?" }
    ]
  }'

客户端代理 (任何经过验证的用户):

curl -X POST http://localhost:3001/mcp/client \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "client-country-code: MY" \
  -H "Content-Type: application/json" \
  -d '{
    "conversation": [
      { "role": "user", "content": "Show me all available products" }
    ]
  }'

致电获取JWT POST http://localhost:8080/api/auth/signin 在在线商店服务器上。

请求头

标题 必填 描述
Authorization 是的 Bearer <jwt> 从在线商店服务器登录
client-country-code 是 ISO国家代码(例如。 MY, SG, US)
Content-Type 是的 application/json

可选查询参数

参数 说明
currency 货币代码(例如。 MYR, USD).如果省略,则来源于国家代码。

3.使用SAM本地调用(非流式)进行测试

sam local invoke 直接使用Docker运行Lambda处理程序。不支持流式传输,但它对于测试冷启动和事件解析很有用。

设置 .env.json 对于SAM

创建一个 .env.json 文件(gitignored):

{
  "McpFunction": {
    "API_SERVER_URL": "http://host.docker.internal:8080",
    "OPENAI_API_KEY": "sk-...",
    "OPENAI_MODEL": "gpt-4o",
    "MAX_ITERATIONS": "20",
    "MAX_TOTAL_TOKENS": "200000"
  }
}

host.docker.internal 从Docker容器(SAM运行的地方)解析到您的Mac主机。

使用真实的JWT更新事件文件

编辑 events/admin-event.json 并替换 YOUR_JWT_TOKEN_HERE 一个真正的令牌。

调用

# Build first
npm run build && sam build

# Invoke health check
sam local invoke McpFunction -e events/health-event.json

# Invoke admin agent
sam local invoke McpFunction -e events/admin-event.json --env-vars .env.json

# Invoke client agent
sam local invoke McpFunction -e events/client-event.json --env-vars .env.json

4.部署到AWS

代码所在的基础设施 infra/.CI/CD管道(reso-infra kit)通过CloudFormation部署——管道中不需要SAM CLI。

infra/
├── cert-stack.yml   — ACM certificate (deployed in us-east-1)
└── app-stack.yml    — Lambda + CloudFront stack

步骤1-在SSM中设置OpenAI API密钥

OpenAI密钥作为SecureString存储在SSM参数存储中,并在CloudFormation部署时解析。设置一次:

aws ssm put-parameter \
  --name /reso-mcp/openai-api-key \
  --value "sk-..." \
  --type SecureString \
  --region ap-southeast-1

第二步——添加GitHub机密

首选 设置→ 秘密与变量→ 行动 在这个repo中添加:

必需

机密 描述
AWS_ACCESS_KEY_ID github-deployer 访问密钥(来自 reso-infra 独自创立
AWS_SECRET_ACCESS_KEY github-deployer 密钥
APP_NAME reso-online-store-mcp
DOMAIN_NAME 自定义域(例如。 mcp.reso-store.com)
HOSTED_ZONE_ID 53号公路托管区ID
API_SERVER_URL 在线商店服务器的基本URL(例如。 https://api.reso-store.com)
CLIENT_DOMAIN_URLS 逗号分隔的允许客户端来源(例如。 https://reso-store.com)

可选--预先配置合理的默认值

机密 默认值 描述
AWS_REGION ap-southeast-1 目标AWS区域
OPENAI_MODEL gpt-4o OpenAI模型
MAX_ITERATIONS 20 最大代理循环迭代次数
MAX_TOTAL_TOKENS 200000 每个请求的最大累积令牌数
MEMORY_SIZE 512 Lambda内存(MB)
TIMEOUT 900 Lambda超时(秒)

OpenAI API密钥 不 需要一个GitHub Secret——它直接从SSM参数存储中读取(/reso-mcp/openai-api-key)在部署时由CloudFormation执行。

步骤3——部署

推至 main --管道运行三个作业: ci → infra → deploy.

  • ci --构建TypeScript,修剪dev-deps,压缩工件
  • infra --将证书堆栈部署到 us-east-1 (幂等),部署应用程序堆栈(幂等的)
  • deploy — aws lambda update-function-code,等待,使CloudFront无效

或手动触发: GitHub→ 行动→ CI/CD部署→ 运行工作流.


5.通过AWS Web控制台触发Lambda

  1. 首选 AWS控制台→ 拉姆达→ reso在线商店mcp prod→ Test
  2. 创建新的测试事件。使用下面的JSON作为模板。

测试事件:健康检查

{
  "rawPath": "/mcp/health",
  "requestContext": {
    "http": { "method": "GET" }
  },
  "headers": {},
  "queryStringParameters": {},
  "body": null
}

测试事件:管理代理

{
  "rawPath": "/mcp/admin",
  "requestContext": {
    "http": { "method": "POST" }
  },
  "headers": {
    "authorization": "Bearer YOUR_JWT_TOKEN",
    "client-country-code": "MY",
    "content-type": "application/json"
  },
  "queryStringParameters": {
    "currency": "MYR"
  },
  "body": "{\"conversation\":[{\"role\":\"user\",\"content\":\"How many products do we have?\"}]}"
}

测试事件:客户端代理

{
  "rawPath": "/mcp/client",
  "requestContext": {
    "http": { "method": "POST" }
  },
  "headers": {
    "authorization": "Bearer YOUR_JWT_TOKEN",
    "client-country-code": "MY",
    "content-type": "application/json"
  },
  "queryStringParameters": {},
  "body": "{\"conversation\":[{\"role\":\"user\",\"content\":\"Show me all available products\"}]}"
}

注意:Lambda控制台不支持流媒体;完整响应被缓冲。对于真正的流媒体,请通过CloudFront或本地Express服务器进行呼叫。


6.流响应格式

响应是一个分块的文本流。每个逻辑块都被包裹在 <output> 标签:

<output>I'm checking the categories now...</output>
<output>{"meta":{"type":"function_call","name":"getCategoriesAdmin","arguments":"{}","call_id":"call_abc","id":"fc_xyz","status":"completed"}}</output>
<output>{"meta":{"type":"function_call_output","call_id":"call_abc","output":"[{\"id\":\"cat1\",\"name\":\"T-Shirts\"}]"}}</output>
<output>I found 3 categories: T-Shirts, Trousers, and Hoodies.
{"meta":{"goalStatus":"SUCCESS","chatStatus":"COMPLETED"}}</output>
{"streamCompletedAt":"2026-03-11T10:00:00.000Z"}

块类型:

  • 内部文本增量 <output>...</output> --直接流式传输到UI
  • {"meta":{...}} 内部堵塞 <output> --工具调用和工具结果元数据
  • {"streamCompletedAt":"..."} --最后一块,流完成
  • {"streamError":{"status":500,"message":"..."}} --错误,流终止

7.启用自定义域

自定义域由CI/CD管道通过以下方式自动处理 DOMAIN_NAME, HOSTED_ZONE_ID,并将证书堆栈部署到 us-east-1.

  1. 添加 DOMAIN_NAME (例如。 mcp.reso-store.com)以及 HOSTED_ZONE_ID 作为GitHub机密(见上文第4节中的步骤2)。
  2. 推至 main --管道部署ACM证书(通过路由53验证的DNS),并自动创建指向CloudFront的路由53别名记录。
  3. 部署后,从堆栈输出中获取实时URL:
    aws cloudformation describe-stacks \
      --stack-name reso-online-store-mcp-stack \
      --query "Stacks[0].Outputs[?OutputKey=='WebsiteUrl'].OutputValue" \
      --output text
    

8.将此项目复制到新的后端

此MCP被设计为可重复使用的模板。要使其适应不同的后端:

第一步:分叉/克隆

git clone https://github.com/your-org/reso-online-store-mcp.git your-new-mcp
cd your-new-mcp

第二步:添加 GET /api/auth/me 到您的后端

后端需要一个端点来验证JWT并返回用户对象。它必须返回:

{
  "success": true,
  "data": {
    "id": "...",
    "email": "...",
    "name": "...",
    "isActive": true,
    "isVerified": true,
    "userType": { "id": "...", "name": "administrator" }
  }
}

MCP检查 userType.name === 'administrator' 以阻止管理员访问。

步骤3:更新 .env.example 和 .env

API_SERVER_URL=https://your-new-backend.com

第四步:更换工具

删除中的现有工具文件 src/lib/ai/tools/ 并创建与后端的REST API匹配的新API:

// src/lib/ai/tools/myResource.tool.ts
import { AiAgentTool, ToolContext } from '../types';

export const getMyResources: AiAgentTool = {
  meta: {
    type: 'function',
    name: 'getMyResources',
    description: 'Fetch all resources from the new backend.',
    parameters: null,
  },
  fn: async (_args, ctx: ToolContext) => {
    return ctx.serverClient.get('/api/my-resource');
  },
};

步骤5:更新工具注册表

编辑 src/lib/ai/tools/index.ts 导入并注册您的新工具。

步骤6:更新系统提示

编辑 src/lib/ai/prompts/agent.ts 描述您的新域名。

步骤7:更新 samconfig.toml

[dev.deploy.parameters]
stack_name = "your-new-mcp-dev"
parameter_overrides = [
  "Environment=dev",
  "ApiServerUrl=https://your-new-backend.com",
  ...
]

步骤8:为OpenAI密钥添加SSM参数

aws ssm put-parameter \
  --name /your-project/openai-api-key \
  --value "sk-..." \
  --type SecureString

步骤9:部署

通过GitHub操作进行部署。推到相关分支以触发工作流。

框架(Lambda处理程序、Express本地服务器、run.ts、HTTP客户端、身份验证模块、CloudFront+OAC、GitHub Actions)保持不变。


代币使用和成本

场景 典型代币 估计成本(GPT-4o)
简单查询(1-2次工具调用) ~15K-30K ~0.03-0.07美元
复杂任务(3-5次工具调用) ~40K-80K ~0.10-0.20美元
最大预算(20次迭代) ~200K ~0.50-0.75美元

令牌使用情况记录到 CloudWatch日志 结构化JSON格式:

{
  "type": "token_usage",
  "iteration": 2,
  "input_tokens": 12500,
  "output_tokens": 850,
  "total_tokens": 13350,
  "cumulative_tokens": 26700
}

在以下位置查找日志: 云监控→ 日志组→ /aws/lambda/reso-online-store-mcp-prod (日志组由CloudFormation预先创建,保留30天)