MCP Server 不再只是给本地 IDE 塞几个工具函数。这个方案把电商场景里的商品搜索、下单、评论提交和退货处理做成可被 Agent 调用的工具,并用 Amazon Bedrock AgentCore 承载运行时,用 Amazon Cognito 和双层 JWT 做身份边界,最后连接到 Mistral AI Studio 的 Vibe。重点不是“能跑一个 demo”,而是把数据、身份、部署和连接器一起拉到接近生产环境的形态。
这套架构解决的不是聊天,而是可控执行
电商 MCP Server 的核心价值在于:Agent 不直接碰数据库,也不绕过业务规则。它只能调用服务暴露的 MCP tools,比如:
search_products:按关键词、分类或价格范围检索商品。place_order:创建订单,并校验用户身份与库存逻辑。submit_review:提交商品评价,通常需要确认购买关系或登录态。process_return:处理退货申请,受订单状态、时间窗口和策略约束。
底层数据使用 Amazon DynamoDB,身份管理交给 Amazon Cognito。Bedrock AgentCore 负责把 MCP Server 运行起来并暴露给 Agent 生态,Mistral Vibe 则作为上层 AI Studio 里的连接入口。
这里的边界很关键:MCP tool 应该是业务 API 的薄封装,而不是让模型拼 DynamoDB 查询。模型负责意图理解,服务负责授权、校验、幂等和审计。
双层 JWT:用户身份和连接器身份要分开
文章强调了两层 JWT 认证,这一点非常适合生产 MCP Server。
一层用于确认“调用者是谁”。比如用户通过 Cognito 登录后拿到 access token,MCP Server 根据 token 判断这个用户能不能下单、退货或提交评论。
另一层用于确认“连接器是不是可信”。Mistral Vibe 连接 MCP Server 时,服务端还需要验证连接器级别的 token,避免任何拿到 endpoint 的客户端都能调用工具。
可以这样实践:把请求头拆成两个明确的身份来源。
POST /mcp HTTP/1.1
Host: mcp.example.com
Content-Type: application/json
Authorization: Bearer <cognito_user_access_token>
X-Connector-Authorization: Bearer <vibe_connector_jwt>
{
"jsonrpc": "2.0",
"id": "req-001",
"method": "tools/call",
"params": {
"name": "search_products",
"arguments": {
"query": "wireless keyboard",
"limit": 5
}
}
}
服务端校验时也要保持两套配置,不要把用户池 token 和连接器 token 混在一个 verifier 里。下面是一个可改造的 Node.js 示例,假设你使用 jose 校验 JWT。运行前需要替换 COGNITO_JWKS_URL、CONNECTOR_JWKS_URL、COGNITO_ISSUER 和 CONNECTOR_ISSUER。
mkdir mcp-auth-demo
cd mcp-auth-demo
npm init -y
npm install express jose
// server.js
const express = require("express");
const { createRemoteJWKSet, jwtVerify } = require("jose");
const app = express();
app.use(express.json());
const userJwks = createRemoteJWKSet(new URL(process.env.COGNITO_JWKS_URL));
const connectorJwks = createRemoteJWKSet(new URL(process.env.CONNECTOR_JWKS_URL));
function bearer(value) {
if (!value || !value.startsWith("Bearer ")) {
throw new Error("missing bearer token");
}
return value.slice("Bearer ".length);
}
async function verifyJwt(token, jwks, issuer, audience) {
const result = await jwtVerify(token, jwks, {
issuer,
audience
});
return result.payload;
}
app.post("/mcp", async (req, res) => {
try {
const userToken = bearer(req.headers.authorization);
const connectorToken = bearer(req.headers["x-connector-authorization"]);
const [user, connector] = await Promise.all([
verifyJwt(userToken, userJwks, process.env.COGNITO_ISSUER, process.env.COGNITO_AUDIENCE),
verifyJwt(connectorToken, connectorJwks, process.env.CONNECTOR_ISSUER, process.env.CONNECTOR_AUDIENCE)
]);
const toolName = req.body?.params?.name;
if (toolName === "place_order" && !user.sub) {
return res.status(403).json({ error: "user identity required" });
}
res.json({
jsonrpc: "2.0",
id: req.body.id,
result: {
content: [
{
type: "text",
text: `Tool ${toolName} accepted for user ${user.sub} via connector ${connector.sub}`
}
]
}
});
} catch (err) {
res.status(401).json({ error: err.message });
}
});
app.listen(3000, () => {
console.log("MCP auth demo listening on http://localhost:3000/mcp");
});
export COGNITO_JWKS_URL="https://cognito-idp.<region>.amazonaws.com/<user-pool-id>/.well-known/jwks.json"
export COGNITO_ISSUER="https://cognito-idp.<region>.amazonaws.com/<user-pool-id>"
export COGNITO_AUDIENCE="<app-client-id>"
export CONNECTOR_JWKS_URL="https://<connector-issuer>/.well-known/jwks.json"
export CONNECTOR_ISSUER="https://<connector-issuer>"
export CONNECTOR_AUDIENCE="<mcp-server-audience>"
node server.js
这段代码不是原方案的完整实现,但它展示了生产 MCP Server 里最容易被低估的一件事:同一次工具调用里可能同时有“用户授权”和“连接器授权”。两者失败任何一个,都不应该继续执行业务动作。
MCP tool 设计要像业务接口,不要像数据库快捷方式
电商工具很容易被设计得过宽。比如 update_order 这种工具看起来通用,实际会把状态机、退款策略、客服权限和库存回滚全部暴露给模型。更稳妥的方式是把工具收敛成明确动作:place_order、process_return、submit_review。
可以这样定义工具 schema:
{
"name": "process_return",
"description": "Create a return request for an existing order item after validating user ownership and return policy.",
"inputSchema": {
"type": "object",
"required": ["orderId", "itemId", "reason"],
"properties": {
"orderId": {
"type": "string",
"description": "The customer order ID"
},
"itemId": {
"type": "string",
"description": "The order item ID to return"
},
"reason": {
"type": "string",
"enum": ["DAMAGED", "WRONG_ITEM", "NOT_AS_DESCRIBED", "NO_LONGER_NEEDED"]
}
}
}
}
几个工程判断值得坚持:
- 参数里使用业务 ID,不让模型传 DynamoDB partition key 结构。
- 返回值给人类可读摘要,也保留机器可判断的状态码或枚举。
- 写操作必须做幂等,例如下单请求可以带
clientRequestId。 - 所有写工具记录审计日志,包括 user id、connector id、tool name、请求摘要和结果。
用 CDK 管住资源生命周期
原文方案使用 AWS CDK 部署,这对 MCP Server 很重要。因为它至少涉及计算运行时、DynamoDB 表、Cognito 用户池、权限策略和连接器配置。手工点控制台能跑通一次,但很难复现、审计和清理。
下面是一个可改造的 CDK TypeScript 片段,展示 DynamoDB 和 Cognito 的基础资源。它不是完整 Bedrock AgentCore 部署模板,但适合作为电商 MCP Server 的数据与身份底座。
mkdir ecommerce-mcp-infra
cd ecommerce-mcp-infra
npx cdk init app --language typescript
npm install aws-cdk-lib constructs
// lib/ecommerce-mcp-infra-stack.ts
import * as cdk from "aws-cdk-lib";
import { Construct } from "constructs";
import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
import * as cognito from "aws-cdk-lib/aws-cognito";
export class EcommerceMcpInfraStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const products = new dynamodb.Table(this, "ProductsTable", {
partitionKey: { name: "productId", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY
});
const orders = new dynamodb.Table(this, "OrdersTable", {
partitionKey: { name: "userId", type: dynamodb.AttributeType.STRING },
sortKey: { name: "orderId", type: dynamodb.AttributeType.STRING },
billingMode: dynamodb.BillingMode.PAY_PER_REQUEST,
removalPolicy: cdk.RemovalPolicy.DESTROY
});
const userPool = new cognito.UserPool(this, "CustomersUserPool", {
selfSignUpEnabled: true,
signInAliases: { email: true }
});
const appClient = userPool.addClient("McpClient", {
authFlows: {
userPassword: true,
userSrp: true
}
});
new cdk.CfnOutput(this, "ProductsTableName", { value: products.tableName });
new cdk.CfnOutput(this, "OrdersTableName", { value: orders.tableName });
new cdk.CfnOutput(this, "UserPoolId", { value: userPool.userPoolId });
new cdk.CfnOutput(this, "UserPoolClientId", { value: appClient.userPoolClientId });
}
}
npm run build
npx cdk bootstrap
npx cdk deploy
生产环境里不要直接使用 RemovalPolicy.DESTROY。上面的设置方便实验后清理;真实订单、评论和退货数据应该启用保留策略、备份、加密和更严格的 IAM 权限。
连接到 Mistral Vibe 前,先把契约压实
Vibe 连接 MCP Server 时,最容易出问题的不是“连不上”,而是工具语义不稳定。建议在接入前完成这几项检查:
- 每个工具的
description都说明何时使用、何时不要使用。 - 写操作工具返回明确结果,比如
ORDER_CREATED、RETURN_REJECTED_POLICY_EXPIRED。 - 错误信息能给模型下一步行动线索,但不泄露内部表名、策略 ARN 或 token 细节。
- 连接器 JWT 和用户 JWT 的过期时间、audience、issuer 都有独立校验。
- DynamoDB 访问路径来自服务端逻辑,而不是模型可控字段。
一个实用的系统提示词可以这样写,作为连接器侧的行为约束:
You are an ecommerce assistant connected to MCP tools.
Use search_products before recommending unavailable products.
Use place_order only after the user confirms product, quantity, shipping address, and payment intent.
Use process_return only for an authenticated user's own order.
Never invent order status, return eligibility, inventory, or review submission results. Call the relevant tool instead.
落地建议:把 Agent 当成新客户端,而不是新后门
这类电商 MCP Server 的正确打开方式,是把 Agent 当成一个高权限、需要严密约束的新客户端。它可以提升搜索、下单和售后流程的自然语言体验,但不能绕过已有的业务边界。
采用时可以按这个顺序推进:先实现只读的 search_products,验证 Vibe 到 Bedrock AgentCore 的连接和认证;再接入低风险写操作,比如 submit_review;最后才开放 place_order 和 process_return 这类会影响订单、库存和退款的工具。
上线前的最低检查清单:JWT 双层校验、工具级授权、写操作幂等、审计日志、DynamoDB 最小权限、CDK 可重复部署、清理流程可执行。做到这些,MCP Server 才不像一个漂亮的 demo,而更像一个能接入真实电商系统的服务边界。