Everything MCP Server 架构深度解析:Server 工厂、三种传输层与多客户端会话管理
本篇基于 servers 仓库中 src/everything 包的官方架构文档及其配套文档,系统讲解 Everything MCP Server 的高层设计、目录结构、启动流程、Server 工厂机制与多客户端会话管理原理,并结合 server/index.ts、transports/*、index.ts 等源码逐层印证。读完本文,你可以掌握该 MCP 参考服务器的完整运行时架构,理解 tools/prompts/resources 的注册链路、条件注册机制以及 cleanup(sessionId) 会话清理设计,从而能够按仓库规范正确扩展自己的 MCP Server。
一、高层架构概览
1.1 定位:覆盖 MCP 核心特性的参考服务器
根据 README 与 架构文档 的说明,Everything Server 是一个“最小化、模块化”的 MCP(Model Context Protocol)服务器,其目标不是成为实用的业务服务器,而是作为 MCP 客户端开发者的测试服务器:它刻意暴露了简单但完整的 tools、prompts、resources,并支持 STDIO、SSE、Streamable HTTP 三种传输方式,用以演练 MCP 协议的全部核心能力。package.json 中的描述同样印证了这一定位:"MCP server that exercises all the features of the MCP protocol",当前版本为 2.0.0,依赖 @modelcontextprotocol/sdk ^1.30.0、express ^5.2.1、zod ^4.0.0 等。
1.2 设计要点:Server 工厂 + 独立传输入口 + 原语子模块
架构文档给出的设计原则可以归纳为三条:
- Server 工厂(Server Factory):一个小型工厂函数负责构造
McpServer实例并注册全部功能原语(tools、prompts、resources); - 传输层解耦:每种传输(STDIO / SSE / Streamable HTTP)是独立入口模块,负责创建/连接 server 并处理各自的网络细节;
- 原语模块化:tools、prompts、resources 各自组织为独立子目录,每个原语一个文件,通过
index.ts聚合注册。
1.3 多客户端支持
该服务器支持多个并发客户端连接。架构文档指出,按会话(session)跟踪数据的能力通过两个演示机制体现:资源订阅(resource subscriptions)与模拟日志(simulated logging)。具体而言,HTTP 类传输会将每个客户端的 transport 映射到 sessionId,并为每个会话维护独立的定时器与内存状态;会话结束时统一通过 cleanup(sessionId) 回收。
二、构建与分发
架构文档的 “Build and Distribution” 一节说明了产物的构建方式,结合 package.json 可以确认以下事实:
- 编译:TypeScript 源码通过
npm run build(实际执行tsc && shx cp -r docs dist/ && shx chmod +x dist/*.js)编译到dist/; - 文档随包发布:
build脚本会把docs/整目录复制到dist/,使得说明文件(尤其是instructions.md,见 5.1 节)能够与编译后的服务器一起分发并被运行时读取; - CLI 入口:
package.json的bin字段将mcp-server-everything指向dist/index.js,因此安装后既可用npx -y @modelcontextprotocol/server-everything [stdio|sse|streamableHttp]直接运行,也可在客户端配置(如 Claude Desktop、VS Code 的 MCP 配置)中声明该命令; - 启动脚本:
start:stdio、start:sse、start:streamableHttp三个 npm script 分别执行node dist/index.js <transport>,用于从源码构建后本地调试 HTTP 传输; - 容器化:仓库提供 Dockerfile,README 同时给出
docker run -i --rm mcp/everything的接入方式。
三、项目结构与模块划分
项目结构文档 给出了完整目录树,这里按其脉络整理各模块职责(该目录树继承自 架构文档 的导航骨架):
src/everything
├── index.ts # CLI 入口,按第一个参数选择传输模块
├── AGENTS.md # 面向 Agent/LLM 的编码规范与扩展指南
├── package.json
├── docs/ # 架构/结构/启动/特性/扩展/原理 六篇文档
├── prompts/ # 4 个演示 prompt
├── resources/ # 资源模板、静态文档、会话资源、订阅跟踪
├── server/ # Server 工厂 + 模拟日志 + roots 同步
├── tools/ # 17+ 个演示工具
└── transports/ # stdio.ts / sse.ts / streamableHttp.ts
各目录核心文件说明(依据 structure.md 与源码):
3.1 index.ts:CLI 入口
解析命令行参数并按第一个参数动态 import 对应传输模块,默认 stdio。源码中的注释说明了动态导入的用意:只加载被请求的模块,避免所有模块全部初始化。未知参数时打印用法并以退出码 1 终止(见 index.ts)。
3.2 server/:服务器核心
server/index.ts:Server 工厂,创建带能力声明的McpServer,加载指令(instructions),注册 tools/prompts/resources 并设置资源订阅处理器(详见第五节);server/logging.ts:实现模拟日志——按会话以随机级别周期性发送日志消息,由专用工具按需启停;server/roots.ts:提供syncRoots(server, sessionId),在初始化完成后向客户端同步 roots 请求。
3.3 prompts/ 与 tools/ 与 resources/
- prompts:
simple-prompt(无参数)、args-prompt(city必填、state可选)、completable-prompt(用 SDKcompletable(...)助手实现参数自动补全)、resource-prompt(内嵌动态生成的资源)。 - tools:见 tools/index.ts 中的编排函数,涵盖 echo、结构化内容、Zod 校验求和、tiny PNG 图片、长时任务进度通知、sampling/elicitation 触发器、URL 模式 elicitation、MCP Tasks(SEP-1686)等(特性清单见 5.3 节)。
- resources:按 structure.md 的说明,包含四类——
- 动态 Text:
demo://resource/dynamic/text/{index}(text/plain,内容按请求带时间戳生成,{index}须为有限正整数); - 动态 Blob:
demo://resource/dynamic/blob/{index}(application/octet-stream,Base64 载荷); - 静态文档:
demo://resource/static/document/<filename>,将docs/目录下的文件作为文件型资源服务,按扩展名映射 MIME(markdown/json/txt 等); - 会话级:
demo://resource/session/<name>,由工具在运行期动态注册、仅存活于当前会话(典型使用者是gzip-file-as-resource工具,将压缩内容以application/gzip注册为会话资源)。server/index.ts中对../resources/subscriptions.js与../resources/index.js的导入(server/index.ts)印证了该模块在运行时被工厂直接装配。
- 动态 Text:
四、启动流程:从 CLI 到传输管理器
启动流程文档 将启动过程描述为三层:启动器(Launcher)→ 传输管理器(Transport Manager)→ 服务器工厂(Server Factory)。
4.1 启动器
用法为 node dist/index.js [stdio|sse|streamableHttp],参数缺省为 stdio,映射关系为:
| 参数 | 模块 |
|---|---|
stdio |
transports/stdio.ts |
sse |
transports/sse.ts |
streamableHttp |
transports/streamableHttp.ts |
对应源码即 index.ts 中的 switch 分支与动态 import。
4.2 传输管理器:三种传输的会话处理差异
每个传输管理器都会调用 createServer()(位于 server/index.ts)创建 server 实例,并通过 server.connect(transport) 连接到 MCP SDK 提供的具体传输类型。三种传输的关键差异(来自 startup.md 与传输源码):
STDIO(transports/stdio.ts)
- 单连接、进程绑定:直接
new StdioServerTransport()后await server.connect(transport); - 无 sessionId 概念,连接即
clientConnect()语义; - 监听
SIGINT:先server.close(),再调用cleanup()清空调度器,最后退出进程(stdio.ts)。
SSE(transports/sse.ts)
- 基于 Express,暴露两个端点:
GET /sse:每个新会话建立一条 SSE 流,创建SSEServerTransport("/message", res)并按sessionId存入transportsMap;POST /message:客户端 JSON-RPC 消息入口,按 query 中的sessionId找到对应 transport 调用handlePostMessage。
- 多客户端支持:客户端 transport 与
sessionId一一映射;server.server.onclose钩子在断连时从 Map 中删除该会话并调用cleanup(sessionId)(sse.ts)。
Streamable HTTP(transports/streamableHttp.ts)
- Express 应用仅暴露
/mcp单一端点,用三种 HTTP 方法承载协议:- POST:JSON-RPC 消息。无
mcp-session-id头视为初始化请求——现场创建 server、StreamableHTTPServerTransport(sessionIdGenerator: () => randomUUID(),并注入InMemoryEventStore以获得断线重放能力),onsessioninitialized时登记 transport,随后server.connect(transport)并transport.handleRequest(req, res); - GET:SSE 事件流,支持
Last-Event-ID头配合事件存储实现可恢复(resumable)会话; - DELETE:会话终止,触发
cleanup(sessionId)。
- POST:JSON-RPC 消息。无
- 会话管理细节值得注意:源码注释说明 transport 在
onsessioninitialized中才入 Map,是为了规避“会话尚未登记、请求已到达”的竞态(streamableHttp.ts);InMemoryEventStore是仓库内自实现的最小EventStore——storeEvent以randomUUID()作为 eventId 存入 Map,replayEventsAfter按存储顺序从lastEventId之后逐条重放(streamableHttp.ts)。 - 两个 HTTP 传输都通过
process.env.PORT || 3001决定监听端口,并配置了宽松 CORS(注释明确“生产环境请慎用*”),方便用 MCP Inspector 直连调试。
4.3 多客户端的前置条件
startup.md 最后强调:能够支持多客户端的传输,必须把每会话的数据映射到 session 标识——这是整个多客户端架构的公理,后续的订阅表、日志定时器、会话资源都以此为键。
五、Server 工厂:createServer() 全解析
5.1 能力声明、任务存储与指令加载
server/index.ts 是工厂的实现。createServer() 的完整装配序列如下:
- 读取指令:
readInstructions()从 docs 目录加载instructions.md作为 server instructions(这就是构建时要把docs/复制进dist/的原因——运行时按相对路径读取它,并在 initialize 交互中返回给客户端); - 任务基础设施:创建
InMemoryTaskStore与InMemoryTaskMessageQueue(来自 SDK 的experimental/tasks),用于 MCP Tasks(SEP-1686)的生命周期与消息管理; - 创建
McpServer:身份信息为name: "mcp-servers/everything"、title: "Everything Reference Server"、version: "2.0.0",能力声明包括:tools: { listChanged: true }、prompts: { listChanged: true }——原语列表变化时可发变更通知;resources: { subscribe: true, listChanged: true }——显式声明subscribe: true正是资源订阅功能的前提;logging: {}——启用日志能力;tasks: { list, cancel, requests: { tools: { call } } }——声明 Tasks 能力(对应 features.md 中“Capabilities advertised”一节)。
- 注册三大原语:
registerTools(server)→registerResources(server)→registerPrompts(server); - 安装订阅处理器:
setSubscriptionHandlers(server)(来自resources/subscriptions.ts,见 6.2 节)。
5.2 条件注册:等握手完成才知道客户端能力
how-it-works.md 解释了该服务器一个重要的机制——条件工具注册:部分工具只有在客户端支持相应能力时才有意义(如 get-roots-list、trigger-elicitation-request、trigger-sampling-request),而客户端能力要到初始化握手完成后才可知。因此工厂在 server.server.oninitialized 回调中延迟调用 registerConditionalTools(server)。
从 tools/index.ts 的源码可以看到两组注册的精确划分:
registerTools(连接前立即注册):echo、get-annotated-message、get-env、get-resource-links、get-resource-reference、get-structured-content、get-sum、get-tiny-image、gzip-file-as-resource、toggle-simulated-logging、toggle-subscriber-updates、trigger-long-running-operation 共 12 个不依赖客户端能力的工具;registerConditionalTools(oninitialized 中注册):get-roots-list、trigger-elicitation-request、trigger-url-elicitation、trigger-sampling-request、simulate-research-query,以及两个双向任务演示工具trigger-sampling-request-async/trigger-elicitation-request-async。
oninitialized 中还有第二件事:延迟 350ms 后调用 syncRoots(server, sessionId)。server/index.ts 的注释说明了原因——roots 同步必须等 notifications/initialized 处理器完全结束之后发出,否则请求会丢失;这属于典型的“初始化时序敏感”细节,也解释了为什么需要一个可被 cleanup 清掉的 initializeTimeout。
5.3 注册的原语特性清单
features.md 列出了全部已实现能力,是理解这个服务器“能演示什么”的权威清单:
Tools(19 个)
| 工具 | 演示的协议能力 |
|---|---|
echo |
基础工具调用,Zod 输入校验 |
get-annotated-message |
内容级 annotations(priority、audience,随 messageType 变化),可选附带小图片 |
get-env |
返回进程环境变量 JSON,用于调试配置 |
get-resource-links |
文本 + 多个 resource_link 混合响应 |
get-resource-reference |
返回具体动态资源的 resource 内容块 |
get-roots-list |
返回客户端最近一次发送的 roots 列表 |
gzip-file-as-resource |
拉取 URL/data URI → 压缩 → 注册为会话资源并返回 link/inline resource |
get-structured-content |
content(文本内嵌 JSON)+ 经 outputSchema 校验的 structuredContent 双返回 |
get-sum |
Zod schema 定义的双数求和 |
get-tiny-image |
返回 tiny PNG 的 image 内容项 |
trigger-long-running-operation |
多步长任务,客户端提供 progressToken 时发 notifications/progress |
toggle-simulated-logging |
按会话启停随机级别日志,尊重客户端 logging/setLevel |
toggle-subscriber-updates |
按会话启停资源更新通知模拟 |
trigger-elicitation-request |
form 模式 elicitation/create(字符串/数字/布尔/枚举/格式校验) |
trigger-url-elicitation |
URL 模式 elicitation,或抛出 -32042(UrlElicitationRequiredError)错误路径;前置 elicitation 指向不同 URL 以避免客户端在同一错误上循环 |
trigger-sampling-request |
向客户端/LLM 发 sampling/createMessage |
simulate-research-query |
服务端 Tasks(SEP-1686):多阶段研究任务 + 状态更新,ambiguous: true 时中途发 elicitation 澄清 |
trigger-sampling-request-async |
双向任务:服务端发采样请求、客户端作后台任务执行,服务端轮询 tasks/get |
trigger-elicitation-request-async |
双向任务:elicitation 版,需客户端声明 tasks.requests.elicitation.create |
Prompts(4 个):simple-prompt、args-prompt、completable-prompt、resource-prompt(内嵌动态资源)。
Resources(4 类 URI):动态 Text / 动态 Blob / 静态文档 / 会话级资源(见 3.3 节)。
Tasks(SEP-1686)生命周期:客户端以 task: true 调用 tools/call → 服务端返回带 taskId 的 CreateTaskResult 而非即时结果 → 客户端轮询 tasks/get 获取状态与 statusMessage → 状态为 completed 后调 tasks/result 取最终结果。状态集合为 working / input_required / completed / failed / cancelled;双向任务方向与演示工具的对应关系为:tools/call(服务端执行,simulate-research-query)、sampling/createMessage 与 elicitation/create(客户端执行,两个 -async 工具)。
其他行为约定(features.md):模拟资源更新通知与模拟日志默认均关闭(opt-in,由对应 toggle 工具启停);多客户端并发下,每个客户端的订阅按会话独立跟踪、独立投递。
六、会话状态与清理:多客户端的内存模型
how-it-works.md 按模块说明了会话状态的四条主线,它们共同构成了“多客户端”承诺的落地方式:
6.1 条件工具(见 5.2 节)
registerConditionalTools(server) 由 oninitialized 触发,保证依赖客户端能力的工具只在握手完成后出现。
6.2 资源订阅(resources/subscriptions.ts)
- 维护
Map<uri, Set<sessionId>>的每 URI 订阅者表; setSubscriptionHandlers(server)(在工厂中被调用,见 server/index.ts)安装 subscribe/unsubscribe 处理器并保持表更新;toggle-subscriber-updates工具调用beginSimulatedResourceUpdates(server, sessionId)/stopSimulatedResourceUpdates(sessionId)启停每会话定时器,仅向该会话已订阅的 URI 发送notifications/resources/updated { uri };cleanup(sessionId)会调用stopSimulatedResourceUpdates(sessionId)清掉定时器与会话级状态。
6.3 会话级资源(resources/session.ts)
getSessionResourceURI(name) 构造 demo://resource/session/<name>;registerSessionResource(server, resource, type, payload) 按 text | blob 类型在内存中登记内容并返回 resource_link,内容仅在当前会话生命周期内可读取——工具可以借此创建“一次性”产物而不落盘(如 gzip-file-as-resource 的 gzip 会话资源)。
6.4 模拟日志(server/logging.ts)
- 周期性发送随机级别(debug 至 emergency 全八级)日志消息,可携带 sessionId 便于演示辨识;
- 由
toggle-simulated-logging工具调用beginSimulatedLogging(server, sessionId?)/stopSimulatedLogging(sessionId?)启停; - 通过
server.sendLoggingMessage({ level, data }, sessionId?)发送,因此客户端配置的最小日志级别由 SDK 侧强制执行; - 传输断连触发
cleanup(),同样会停止活跃的定时器。
6.5 cleanup() 的统一收尾
回到工厂返回值:server/index.ts 中 cleanup(sessionId?) 依次执行——stopSimulatedLogging(sessionId) → stopSimulatedResourceUpdates(sessionId) → taskStore.cleanup()(清理任务存储定时器)→ 清除 pending 的 initializeTimeout。三种传输的收尾路径最终都汇聚到它:stdio 在 SIGINT 时(无 sessionId,清理全部),SSE 在 onclose 时(带 sessionId),Streamable HTTP 在 DELETE / onclose 与进程 SIGINT 遍历所有会话时。
七、扩展点:向服务器添加新能力
扩展点文档 给出了三条标准扩展路径,与“原语模块化 + index 编排”的架构完全一致:
- 添加工具:在
tools/下新建文件,实现registerXTool(server)并通过server.registerTool(...)注册;然后在 tools/index.ts 的registerTools(server)(不依赖客户端能力)或registerConditionalTools(server)(依赖客户端能力)中导出并调用。 - 添加 Prompt:在
prompts/下新建registerXPrompt(server)(server.registerPrompt(...)),接入prompts/index.ts的registerPrompts(server)。 - 添加资源:在
resources/下新建registerXResources(server)(可配合ResourceTemplate做动态模板资源),接入resources/index.ts的registerResources(server)。
仓库中的 AGENTS.md 进一步面向 Agent/LLM 提供了编码规范与“如何恰当扩展服务器”的指导;src/everything/__tests__/ 下的 registrations.test.ts、tools.test.ts、resources.test.ts、prompts.test.ts、server.test.ts(以 vitest 运行,见 vitest.config.ts)则为注册完整性与工厂行为提供了回归验证——扩展时保持这些测试通过,是符合仓库规范的重要检查项。
八、小结
Everything Server 的架构可以概括为一句话:“一个工厂造服务器,三个入口管传输,所有会话状态按 sessionId 建账并随 cleanup 销账”。
- 工厂层(
server/index.ts)声明能力、加载指令、注册原语、安装订阅处理器,并通过oninitialized完成依赖客户端能力的条件注册; - 传输层(
transports/*)各自处理网络协议与多路复用,把每个客户端映射为一个 session,并在断连/终止时回调统一清理; - 原语层(
tools/、prompts/、resources/)保持单文件单原语的扁平结构,使扩展点始终只有两步:写注册函数、挂到index.ts编排器。
对 MCP 客户端开发者而言,这个仓库的价值正在于此:它用最小化的代码把协议特性逐条“立牌”出来,配合 docs/ 下 架构、结构、启动、特性、扩展、原理 六篇文档,构成了一份可直接阅读源码核实的 MCP 行为参考实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00