mcp-server
两个 MCP 服务器,一个产品,两个协议版本
同一个购物车,实现了两次:一次基于旧的有状态 MCP 规范(2025-11-25),一次基于新的无状态规范(2026-07-28)。让它们并排运行,然后看着其中一个崩溃。
目标不是写出能跑的代码——而是理解规范为什么变了。这里的每个实验都设计成让失败足够响亮、原因在线上清晰可见。
什么是 MCP?(三句话)
MCP——模型上下文协议(Model Context Protocol)——是一种让 AI 应用调用别人写的工具的标准方式。它把 N 个应用 × M 个集成替换成 N + M,就像语言服务器协议(Language Server Protocol)取代了每个编辑器各自实现 TypeScript 支持一样。具体来说,它就是带有约定方法名的 JSON-RPC 2.0 消息,通过 stdio 或 HTTP 传输。
如果上面说得太快,这里有更长的版本: docs/01 — MCP 为什么存在。
Related MCP server: Online Boutique AI Assistant MCP Server
这个仓库演示了什么
五个工具——catalog_list、cart_create、cart_add_item、cart_view、cart_checkout——在两个服务器中名称完全相同,业务逻辑也完全相同,都放在一个对 MCP 一无所知的共享 cart-core 包里。两个服务器之间唯一的区别就是协议层,而这正是要研究的东西。
你可以亲眼看到四件事发生:
server-old在一个普通的轮询(round-robin)负载均衡器后面连自己的握手都完不成。server-new则根本注意不到负载均衡器的存在。在对话中途重启
server-old,购物车就永久消失了,客户端发任何请求都无法恢复它。问一句"确认这个总额?"会让
server-old在整个人类思考时间内(实测 1522ms)保持一个打开的 socket。server-new用两个独立的请求就完成了,4ms + 15ms,而且可以在与开始时不同的机器上完成。稳定的列表排序和缓存提示——以及说明缺少一个
.sort()每年大约值 $4,200 的算术。
这两个服务器刻意没有重构为共享协议代码。它们之间的重复是有意为之,这样你可以从头到尾分别阅读每一个,然后做对比。
一定要看到线上数据
两个服务器都会在 HTTP 层打印每个请求:方法、路径、所有 MCP 头、JSON-RPC 方法和参数、由哪个实例处理、以及包含 resultType 的响应。没有任何东西被 SDK 抽象隐藏。实验运行时如果你只想读一样东西,就读那些彩色的日志行。
前置条件
Node.js 20 或更高版本(在 25.5 上开发)。用
node -v检查。一个能渲染 ANSI 颜色的终端——日志非常依赖颜色。
端口 3000–3002、3011、3012 空闲。
不需要数据库、不需要 Docker、不需要云账户。共享状态就是一个 JSON 文件。
这个仓库里没有任何 Python。
安装
git clone <this repo>
cd mcp-server
npm install
npm run typecheck # should print nothing and exit 0npm install 会建立一个 npm workspace,其中同时包含两代 MCP SDK。它们的包名不同,所以不需要任何别名技巧就能共存:
包 | 版本 | 使用者 |
| 1.30.0 |
|
| 2.0.0 |
|
四个实验,按顺序
每个都是一条命令。每个都会启动并停止自己的服务器——不需要第二个终端。运行之后再读链接的文章;每篇都会解释你刚刚看到了什么以及为什么。
顺序 | 命令 | 它教你什么 |
1 |
| 负载均衡器后面的两个实例 — 旧服务器连打招呼都完不成;新服务器毫不在意。从这里开始。 |
2 |
| 对话中途重启 — 购物车到底存在哪里,以及为什么"加个 Redis 就行"只解决了一半问题。 |
3 |
| 结账前确认 — 1522ms 的占用 socket 对比两个 4ms 的请求,以及为什么旧方式永远无法在 serverless 上运行。 |
4 |
| 缓存提示与稳定排序 — 用计数器而不是秒表来证明缓存命中,以及 |
然后阅读架构文档,它们把四个实验串在一起:
01 — MCP 为什么存在 — N×M 问题,以及 MCP 是什么、不是什么。如果你是 MCP 新手,先读这篇。
02 — 旧架构 — 握手、session id,以及每一个运维痛点如何追溯到其根源。
03 — 新架构 — handle、MRTR、缓存提示,以及你放弃了什么。
04 — 并排对比 — 该版本中的每一项变更、在这里哪里能找到,以及这个仓库诚实地没有覆盖什么。
手动驱动
至少值得手动做一次,因为你可以自己掌控节奏,并且可以逐行阅读每条日志。
# Old server (port 3001)
npm run old:server
npm run client -- --target old --scenario basic
npm run client -- --target old --scenario checkout
npm run client -- --target old --scenario checkout --decline
# New server (port 3002)
npm run new:server
npm run client -- --target new --scenario basic
npm run client -- --target new --scenario checkout
npm run client -- --target new --scenario discover # server/discover — new spec only手动运行两个实例加一个负载均衡器:
PORT=3002 INSTANCE_ID=A npm run new:server
PORT=3012 INSTANCE_ID=B npm run new:server
PORT=3000 TARGETS=http://localhost:3002,http://localhost:3012 npm run lb
npm run client -- --target new --url http://localhost:3000/mcp --scenario basic观察两个服务器终端:同一个 cart id 出现在各自处理的请求中,而两边都不在意。
用 curl 戳一戳
这是感受新规范请求自描述性最直接的方式。先运行 npm run new:server,然后:
# A complete, valid request — note how much has to be in it
curl -s -X POST http://localhost:3002/mcp \
-H 'content-type: application/json' \
-H 'accept: application/json, text/event-stream' \
-H 'mcp-protocol-version: 2026-07-28' \
-H 'mcp-method: tools/call' \
-H 'mcp-name: catalog_list' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{
"name":"catalog_list","arguments":{},
"_meta":{"io.modelcontextprotocol/protocolVersion":"2026-07-28",
"io.modelcontextprotocol/clientInfo":{"name":"curl","version":"1"},
"io.modelcontextprotocol/clientCapabilities":{}}}}'现在一次破坏一个部分,观察错误如何变化:
改动 | 预期 |
|
|
|
|
去掉 |
|
去掉 |
|
在 header 和 |
|
| 405 — GET 端点已经没了 |
对旧服务器做同样的操作,你会被告知先 initialize。
仓库布局
packages/
cart-core/ the actual product. zero MCP knowledge. shared by both servers.
server-old/ MCP 2025-11-25. sessions, handshake, held-open streams.
server-new/ MCP 2026-07-28. stateless, handles, MRTR, cache hints.
client-demo/ both clients — one per SDK generation.
round-robin/ ~50-line load balancer. no stickiness, on purpose.
experiments/ four runnable scripts + a write-up each.
docs/ the four architecture notes.
.cart-store/ server-new's shared state. a JSON file. delete it freely.代码阅读顺序:cart-core/src/cart.ts(产品做什么)→ server-old/src/index.ts → server-new/src/index.ts。两个服务器中协议层的代码都逐行注释了;管道代码没有注释。
npm run clean 会删除构建输出和 .cart-store。
术语表
全文使用的术语,按它们会咬到你的顺序排列。
负载均衡器(Load balancer) — 放在你的服务器多个相同副本前面的一台设备,把进来的请求分散到各个副本上。默认策略是轮询(round-robin):把每个请求发给列表中的下一个副本。它假设任何副本都能回答任何请求,而这正是旧 MCP 规范打破的假设。这里就是 packages/round-robin,大约 50 行。
会话(Session) — 服务器端对某个客户端的内存,跨越多个请求。在 2025-11-25 上,服务器在握手期间铸造了一个 Mcp-Session-Id,客户端在每个请求上都回显它,服务器把它当作内存中一个 map 的键。session id 是指向某个进程堆的指针,所有麻烦都从这里来。
无状态(Stateless) — 服务器在请求之间不保留任何东西。每个请求都携带服务它所需的一切。注意这不意味着什么:购物车仍然存在,而且仍然被存储。消失的是在某个特定进程中、隐式地、以连接为键持有的状态。共享数据库中的应用状态与无状态协议完全兼容。
粘性会话(Sticky session)(会话亲和性)— 配置负载均衡器,使来自同一个客户端的所有请求都回到同一个服务器副本,通常通过对 cookie 或 header 做哈希。这是对有状态协议的标准变通方案。它有效,但代价是均匀的负载分布、无痛部署、有用的自动扩缩容,以及一个不需要理解你的应用协议的负载均衡器。代价清单见 docs/02。
征询(Elicitation) — 服务器在操作中途向终端用户提问("总额是 $180.36,确认?")。在旧规范中,服务器通过一个保持打开的流自己向客户端发请求,并在工具处理器内部阻塞,等一个人类思考。这一个功能就需要一个活着的进程、一个打开的 socket,以及保证路由回同一台机器的能力。
MRTR(多往返请求,Multi Round-Trip Requests)— 2026-07-28 做征询的方式。服务器返回一个正常的 200,带 resultType: "input_required",问题放在 inputRequests 里,还有一个不透明的签名 requestState。这个请求已经结束了——没有任何东西被占用。客户端收集答案后发送一个新的请求(新的 JSON-RPC id),携带 inputResponses 和同一个 requestState。进行中的状态通过客户端传递,而不是坐在某个进程里,这就是为什么第二轮可以由完全不同的机器来服务。
Handle — 服务器铸造的标识符,作为普通工具输出返回,然后作为普通参数传回。cart_create 返回 cartId;cart_add_item 接收它。这就是 2026-07-28 取代会话状态的方式,与旧设计的区别在于谁持有钥匙:传输层,不可见地持有,对比客户端,在一个模型能读取并传递的值中持有。注意:单独的 handle 就是一个 bearer token——它需要被限定到已认证的用户,docs/04 诚实地讨论了这一点。
提示缓存(Prompt caching) — LLM 提供商会缓存提示的前缀:再次发送相同的开头字节,提供商会复用已计算的状态,而不是重新处理这些 token,价格大约是输入价格的十分之一。两个特性让它变得脆弱:匹配基于精确字节,而且是从前面开始按位置的。所以如果你的工具列表或目录在前缀里,而两个条目交换了位置,你就会失去交换之后每个 token 的折扣。这就是为什么 2026-07-28 说服务器 SHOULD 以确定性顺序返回列表,也是为什么 listProducts() 按唯一的 id 而不是按名称或价格排序——唯一键给出全序,排序实现不会遇到平局而做出不同决定。带价格的具体例子:实验 04。
如果只记住三件事
"无状态"不是说"没有状态"——而是说没有钉死在某个进程上的状态。 购物车仍然存在。它只是移到了任何实例都能到达的地方。
粘性会话是一个真实的修复,也有真实的代价,其中一个代价就是让你的基础设施去解析你的应用协议。
解锁 serverless 的是 MRTR,而不是无状态。 无状态让 MCP 能跑到负载均衡器后面。征询仍然需要一个进程在人类读对话框时保持存活——而这正是 serverless 所移除的。
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Connectors
Hosted MCP for e-commerce: live product catalog, stock, and pricing for AI agents.
Remote MCP for Living Stack offer discovery and buyer-authorized checkout preparation.
Remote MCP for Universal Cart merchant readiness MCP, structured receipts, audit logs, and reviewer-
Agent-native commerce with trusted catalog, durable carts, and Stripe Checkout via MCP and UCP.
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceA Model Context Protocol server that enables AI assistants to interact with a complete e-commerce application, providing authentication, product browsing, and shopping cart management through standardized MCP tools.
- AlicenseNot gradedqualityDmaintenanceMCP server for Online Boutique AI Assistant that exposes 18 e-commerce microservice functions via the Model Context Protocol, enabling any MCP client to manage products, carts, checkout, payments, and shipping.MIT
- FlicenseNot gradedqualityDmaintenanceEnables AI assistants to manage products, shopping carts, and orders in an online store through a well-defined MCP API.
- AlicenseAqualityCmaintenanceA UCP-compliant MCP storefront server that exposes product catalog operations (search, cart, checkout) as MCP tools, following UCP schema version 2026-04-08.5MIT
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ritik913553/mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server