首页
/ Everything MCP Server 架构深度解析:Server 工厂、三种传输层与多客户端会话管理

Everything MCP Server 架构深度解析:Server 工厂、三种传输层与多客户端会话管理

2026-09-04 19:24:42作者:明树来

本篇基于 servers 仓库中 src/everything 包的官方架构文档及其配套文档,系统讲解 Everything MCP Server 的高层设计、目录结构、启动流程、Server 工厂机制与多客户端会话管理原理,并结合 server/index.tstransports/*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.0express ^5.2.1zod ^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.jsonbin 字段将 mcp-server-everything 指向 dist/index.js,因此安装后既可用 npx -y @modelcontextprotocol/server-everything [stdio|sse|streamableHttp] 直接运行,也可在客户端配置(如 Claude Desktop、VS Code 的 MCP 配置)中声明该命令;
  • 启动脚本start:stdiostart:ssestart: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/

  • promptssimple-prompt(无参数)、args-promptcity 必填、state 可选)、completable-prompt(用 SDK completable(...) 助手实现参数自动补全)、resource-prompt(内嵌动态生成的资源)。
  • tools:见 tools/index.ts 中的编排函数,涵盖 echo、结构化内容、Zod 校验求和、tiny PNG 图片、长时任务进度通知、sampling/elicitation 触发器、URL 模式 elicitation、MCP Tasks(SEP-1686)等(特性清单见 5.3 节)。
  • resources:按 structure.md 的说明,包含四类——
    • 动态 Textdemo://resource/dynamic/text/{index}text/plain,内容按请求带时间戳生成,{index} 须为有限正整数);
    • 动态 Blobdemo://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)印证了该模块在运行时被工厂直接装配。

四、启动流程:从 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 存入 transports Map;
    • 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、StreamableHTTPServerTransportsessionIdGenerator: () => randomUUID(),并注入 InMemoryEventStore 以获得断线重放能力),onsessioninitialized 时登记 transport,随后 server.connect(transport)transport.handleRequest(req, res)
    • GET:SSE 事件流,支持 Last-Event-ID 头配合事件存储实现可恢复(resumable)会话
    • DELETE:会话终止,触发 cleanup(sessionId)
  • 会话管理细节值得注意:源码注释说明 transport 在 onsessioninitialized 中才入 Map,是为了规避“会话尚未登记、请求已到达”的竞态streamableHttp.ts);InMemoryEventStore 是仓库内自实现的最小 EventStore——storeEventrandomUUID() 作为 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() 的完整装配序列如下:

  1. 读取指令readInstructions() 从 docs 目录加载 instructions.md 作为 server instructions(这就是构建时要把 docs/ 复制进 dist/ 的原因——运行时按相对路径读取它,并在 initialize 交互中返回给客户端);
  2. 任务基础设施:创建 InMemoryTaskStoreInMemoryTaskMessageQueue(来自 SDK 的 experimental/tasks),用于 MCP Tasks(SEP-1686)的生命周期与消息管理;
  3. 创建 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”一节)。
  4. 注册三大原语registerTools(server)registerResources(server)registerPrompts(server)
  5. 安装订阅处理器setSubscriptionHandlers(server)(来自 resources/subscriptions.ts,见 6.2 节)。

5.2 条件注册:等握手完成才知道客户端能力

how-it-works.md 解释了该服务器一个重要的机制——条件工具注册:部分工具只有在客户端支持相应能力时才有意义(如 get-roots-listtrigger-elicitation-requesttrigger-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,或抛出 -32042UrlElicitationRequiredError)错误路径;前置 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-promptargs-promptcompletable-promptresource-prompt(内嵌动态资源)。

Resources(4 类 URI):动态 Text / 动态 Blob / 静态文档 / 会话级资源(见 3.3 节)。

Tasks(SEP-1686)生命周期:客户端以 task: true 调用 tools/call → 服务端返回带 taskIdCreateTaskResult 而非即时结果 → 客户端轮询 tasks/get 获取状态与 statusMessage → 状态为 completed 后调 tasks/result 取最终结果。状态集合为 working / input_required / completed / failed / cancelled;双向任务方向与演示工具的对应关系为:tools/call(服务端执行,simulate-research-query)、sampling/createMessageelicitation/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.tscleanup(sessionId?) 依次执行——stopSimulatedLogging(sessionId)stopSimulatedResourceUpdates(sessionId)taskStore.cleanup()(清理任务存储定时器)→ 清除 pending 的 initializeTimeout。三种传输的收尾路径最终都汇聚到它:stdio 在 SIGINT 时(无 sessionId,清理全部),SSE 在 onclose 时(带 sessionId),Streamable HTTP 在 DELETE / onclose 与进程 SIGINT 遍历所有会话时。

七、扩展点:向服务器添加新能力

扩展点文档 给出了三条标准扩展路径,与“原语模块化 + index 编排”的架构完全一致:

  1. 添加工具:在 tools/ 下新建文件,实现 registerXTool(server) 并通过 server.registerTool(...) 注册;然后在 tools/index.tsregisterTools(server)(不依赖客户端能力)或 registerConditionalTools(server)(依赖客户端能力)中导出并调用。
  2. 添加 Prompt:在 prompts/ 下新建 registerXPrompt(server)server.registerPrompt(...)),接入 prompts/index.tsregisterPrompts(server)
  3. 添加资源:在 resources/ 下新建 registerXResources(server)(可配合 ResourceTemplate 做动态模板资源),接入 resources/index.tsregisterResources(server)

仓库中的 AGENTS.md 进一步面向 Agent/LLM 提供了编码规范与“如何恰当扩展服务器”的指导;src/everything/__tests__/ 下的 registrations.test.tstools.test.tsresources.test.tsprompts.test.tsserver.test.ts(以 vitest 运行,见 vitest.config.ts)则为注册完整性与工厂行为提供了回归验证——扩展时保持这些测试通过,是符合仓库规范的重要检查项。

八、小结

Everything Server 的架构可以概括为一句话:“一个工厂造服务器,三个入口管传输,所有会话状态按 sessionId 建账并随 cleanup 销账”

  • 工厂层(server/index.ts)声明能力、加载指令、注册原语、安装订阅处理器,并通过 oninitialized 完成依赖客户端能力的条件注册;
  • 传输层(transports/*)各自处理网络协议与多路复用,把每个客户端映射为一个 session,并在断连/终止时回调统一清理;
  • 原语层(tools/prompts/resources/)保持单文件单原语的扁平结构,使扩展点始终只有两步:写注册函数、挂到 index.ts 编排器。

对 MCP 客户端开发者而言,这个仓库的价值正在于此:它用最小化的代码把协议特性逐条“立牌”出来,配合 docs/架构结构启动特性扩展原理 六篇文档,构成了一份可直接阅读源码核实的 MCP 行为参考实现。

登录后查看全文
热门项目推荐
相关项目推荐