发布时间:2026/9/5 19:51:14
MCP Everything Reference Server 工作原理详解:条件工具注册、资源订阅、会话级资源与模拟日志机制 MCP Everything Reference Server 工作原理详解条件工具注册、资源订阅、会话级资源与模拟日志机制【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers本文基于 modelcontextprotocol servers 仓库中 Everything Server 的官方文档 how-it-works.md 及其对应源码深入讲解该参考服务器的四大核心运行机制基于客户端能力协商的条件工具注册、按 URI 追踪订阅者的资源订阅管理、仅存活于会话生命周期的会话级资源注册以及尊重客户端日志级别设置的模拟日志推送。读完本文你将理解 Everything Server 如何在 MCP 初始化握手之后完成能力协商与延迟注册以及如何通过会话级状态管理安全地模拟订阅更新与日志流。Everything Server 是整个 MCP servers 仓库中用于演示协议全部能力的参考实现。它的工厂函数createServer()定义在 server/index.ts由三种传输管理器stdio / sse / streamableHttp入口见 index.ts调用。工厂执行期间完成以下工作对应 startup.md 中的“Server Factory”部分创建McpServer实例声明能力集tools、prompts、resources含subscribe: true与listChanged: true、logging、tasks并附带从 instructions.md 加载的服务器说明立即注册全部工具registerTools、资源registerResources与提示词registerPrompts安装资源订阅处理器setSubscriptionHandlers(server)返回server实例与cleanup(sessionId?)回调用于在会话结束时停止所有模拟定时器并清理会话级状态。这里有一个关键设计前提大部分工具在 Server Factory 执行期间就立即注册早于任何客户端连接但有少数工具依赖客户端是否声明了特定能力只能等初始化握手完成后才注册。以下按文档脉络逐节展开。条件工具注册Conditional Tool Registration问题背景客户端能力在握手之前不可知MCP 协议中部分工具只有在客户端具备相应能力时才有意义get-roots-list需要客户端支持 roots 能力trigger-elicitation-request需要客户端支持 elicitation向用户发起交互请求能力trigger-sampling-request需要客户端支持 sampling由客户端调用 LLM 采样能力。而客户端的能力声明capabilities只有在初始化握手完成后才能从initialize响应中得知因此这些工具不能在工厂执行阶段就注册。实现oninitialized处理器中延迟注册源码 src/everything/tools/index.ts 将注册拆分为两个函数registerTools(server)负责在工厂阶段注册的 13 个“无条件”工具echo、get-env、get-sum、get-tiny-image、gzip-file-as-resource 等registerConditionalTools(server)负责需要能力协商的工具。延迟注册的触发点在 src/everything/server/index.ts// Perform post-initialization operations server.server.oninitialized async () { // Register conditional tools now that client capabilities are known. // This finishes before the notifications/initialized handler finishes. registerConditionalTools(server); // Sync roots if the client supports them. // This is delayed until after the notifications/initialized handler finishes, // otherwise, the request gets lost. const sessionId server.server.transport?.sessionId; initializeTimeout setTimeout(() syncRoots(server, sessionId), 350); };两个值得注意的细节注册时机registerConditionalTools在oninitialized中同步调用保证在notifications/initialized处理器结束前完成因此配合服务器声明的tools.listChanged: true能力客户端可以在工具列表变化时收到通知并重新拉取roots 同步需要额外延迟从源码结构看syncRoots实现在 roots.ts通过 350ms 的setTimeout推迟到initialized通知处理完毕之后再发起请求注释明确指出“否则请求会丢失”。这个定时器在cleanup()中通过clearTimeout(initializeTimeout)回收避免会话结束时悬挂。文档列出的三个条件工具是机制的核心示例当前源码中registerConditionalTools注册的成员更多还包括trigger-url-elicitation、trigger-sampling-request-async、trigger-elicitation-request-async以及基于实验性 tasks API 的simulate-research-query——这说明该注册机制是可扩展的任何依赖客户端能力的工具都应归入这一函数。资源订阅Resource Subscriptions数据模型按 URI 追踪订阅者订阅状态维护在 src/everything/resources/subscriptions.ts 的两个模块级 Map 中// Track subscriber session id lists by URI const subscriptions: Mapstring, Setstring | undefined new Map(); // Interval to send notifications to subscribers const subsUpdateIntervals: Mapstring | undefined, NodeJS.Timeout | undefined new Map();subscriptions以资源 URI 为键值为订阅该 URI 的会话 ID 集合Mapuri, SetsessionId与文档描述一致。注意会话 ID 允许为undefined——stdio 传输下没有会话 IDsubsUpdateIntervals记录每个会话的模拟更新定时器保证同一会话最多只有一个活跃 interval。订阅/退订处理器setSubscriptionHandlers工厂函数在 server/index.ts 中调用setSubscriptionHandlers(server)为SubscribeRequestSchema和UnsubscribeRequestSchema两类请求安装处理器Subscribe 处理器从请求中提取uri从extra.sessionId提取会话 ID先发一条 info 级日志确认收到订阅然后把会话 ID 加入对应 URI 的订阅者集合Unsubscribe 处理器同样先记录日志再从对应 URI 的集合中移除该会话 ID。按需启停的模拟更新toggle-subscriber-updates工具订阅本身是被动状态模拟更新则由工具 tools/toggle-subscriber-updates.ts 控制工具内部维护clients: Setstring | undefined记录当前处于“开启”状态的会话会话首次调用时执行beginSimulatedResourceUpdates(server, sessionId)立即发送一轮更新然后以5 秒周期setInterval持续推送再次调用则stopSimulatedResourceUpdates(sessionId)清除定时器工具返回文本会明确提示“Started/Stopped simulated resource updates for session …”会话断开或cleanup(sessionId?)被调用时stopSimulatedResourceUpdates(sessionId)会清除 interval 并移除该会话的会话级状态。sendSimulatedResourceUpdates的推送逻辑subscriptions.ts值得细看它遍历subscriptions中的全部 URI若某 URI 的订阅者集合包含目标会话则通过server.server.notification({ method: notifications/resources/updated, params: { uri } })发送资源更新通知若集合中已不包含该会话则顺手将其删除——这是一种被动清理已断连订阅者的机制。相关行为在tests/resources.test.ts 中有对应测试覆盖setSubscriptionHandlers、beginSimulatedResourceUpdates、stopSimulatedResourceUpdates均被导入验证。会话级资源Session‑scoped ResourcesURI 生成与注册resources/session.ts会话级资源的实现在 src/everything/resources/session.ts提供两个导出函数1.getSessionResourceURI(name)—— 构造固定格式的会话资源 URIexport const getSessionResourceURI (name: string): string { return demo://resource/session/${name}; };2.registerSessionResource(server, resource, type, payload)—— 注册一个仅存活于当前会话的资源返回resource_linkresource对象携带uri、name、mimeType还可含description、title、annotations、icons、_meta等元数据type只接受text | blobpayload作为字符串传入内容会装入对应字段text 资源返回{ uri, mimeType, text: payload }blob 资源返回{ uri, mimeType, blob: payload }base64资源通过server.registerResource(...)注册读取回调直接从内存中的resourceContent返回——内容不落盘、不持久化仅服务于会话生命周期。一个容易踩坑的细节在源码注释中写得很清楚session.ts模块内维护registeredResources: Mapstring, RegisteredResource注册前若发现同一 URI 已存在会先调用existingResource.remove()再重新注册。这是为了避免工具在会话内多次用相同 URI 创建资源时抛出 “Resource already registered” 错误。设计意图工具按需产出会话内工件文档给出的典型用法正是 tools/gzip-file-as-resource.ts 实现的gzip-file-as-resource工具拉取一个 URL 的内容用 Node.js 内置gzipSync压缩以mimeType: application/gzip注册为会话资源并按参数outputType二选一返回resourceLink默认返回resource_link客户端可以在会话内的后续请求中通过该链接读取资源resource直接在工具结果中内联返回完整资源对象{ uri, mimeType, blob }。该工具的输入 schema 与可调参数gzip-file-as-resource.ts参数类型默认值说明namestringREADME.md.gz输出文件名用于拼接会话资源 URIdataurl仓库 README 的 raw 地址要压缩的文件内容来源支持 http/https/data URIoutputTypeenumresourceLinkresourceLink返回可后续读取的链接resource返回完整内联资源抓取环节还有三组环境变量控制的安全边界环境变量默认值作用GZIP_MAX_FETCH_SIZE10 MB单次抓取允许的最大字节数GZIP_MAX_FETCH_TIME_MILLIS30000抓取超时毫秒GZIP_ALLOWED_DOMAINS空允许所有域名逗号分隔的域名白名单支持子域匹配从源码结构看fetchSafely先校验协议仅 http/https/data与域名白名单再同时检查Content-Length头与实际读取字节数——注释明确指出不能信任对端返回的 Content-Length必须监控实际读取量超限即取消流并抛错。这套“会话级资源 安全抓取”的组合展示了 MCP 工具如何在不持久化的前提下产出可被客户端二次读取的大对象。模拟日志Simulated Logging实现server/logging.ts模拟日志实现在 src/everything/server/logging.ts模块级 MaplogsUpdateIntervals记录每个会话的日志定时器const logsUpdateIntervals: Mapstring | undefined, NodeJS.Timeout | undefined new Mapstring | undefined, NodeJS.Timeout | undefined();beginSimulatedLogging(server, sessionId?)的工作方式构造 8 个不同级别的日志消息池debug、info、notice、warning、error、critical、alert、emergency每条消息若携带 sessionId 会追加- SessionId id后缀便于演示时区分来源会话若该会话尚无 interval则立即发送一条随后setInterval每5 秒随机抽取一条发送发送统一走server.sendLoggingMessage({ level, data }, sessionId?)。这一点是文档强调的关键通过 SDK 的sendLoggingMessage发送客户端配置的最低日志级别会被 SDK 自动遵守低于该级别的模拟消息不会下发到客户端stopSimulatedLogging(sessionId?)则清除对应 interval 并从 Map 中删除记录。触发与清理链路按需启停由工具 tools/toggle-simulated-logging.tstoggle-simulated-logging调用上述 begin/stop 函数切换传输断开兜底任意传输stdio 收到SIGINT、SSE 的onclose、Streamable HTTP 的DELETE等见 startup.md 的传输管理器说明断开时都会触发工厂返回的cleanup(sessionId?)。工厂函数中的cleanup一次性完成四件事server/index.tscleanup: (sessionId?: string) { // Stop any simulated logging or resource updates that may have been initiated. stopSimulatedLogging(sessionId); stopSimulatedResourceUpdates(sessionId); // Clean up task store timers taskStore.cleanup(); if (initializeTimeout) clearTimeout(initializeTimeout); },这正是文档所述“transport disconnect triggerscleanup()which also stops any active intervals”的代码依据模拟日志与模拟订阅更新不会在客户端断开后继续空转tasks 定时器和 roots 同步定时器也一并回收。小结与延伸阅读Everything Server 用一个参考实现串起了 MCP 协议的几类易错机制how-it-works.md 所覆盖的四个主题可以归纳为两条主线能力协商时序工厂阶段能做的立即做无条件工具、资源、提示词、订阅处理器必须等握手完成的条件工具、roots 同步放进oninitialized并借助listChanged能力让客户端感知工具列表变化会话级状态治理订阅表、模拟更新 interval、日志 interval、会话资源注册表全部按sessionId允许undefined以兼容 stdio为键管理并通过统一的cleanup(sessionId?)在连接断开时集中回收防止定时器泄漏与重复注册错误。验证方面仓库提供了配套测试resources.test.ts 覆盖会话资源与订阅管理server.test.ts、tools.test.ts、registrations.test.ts 分别覆盖服务器工厂、工具注册与整体注册行为。更多背景可继续阅读该目录下的配套文档architecture.md、structure.md、startup.md、features.md、extension.md 与 instructions.md。【免费下载链接】serversModel Context Protocol Servers项目地址: https://gitcode.com/GitHub_Trending/se/servers创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

2026/9/5 20:46:17

10行代码跑通大模型调用:Agent开发的第一站与避坑指南

1. 为什么我用 10 行代码作为 Agent 开发的第一站先说结论:Agent 项目不管吹得多花哨,落地时都要先去问一句“大模型到底能不能按我的要求稳定返回结果”。而验证这件事,10 行代码完全够了。很多人一上来就想整 Agent 框架,什么编…

2026/9/5 20:46:17

手撸大模型API调用:10行代码跑通,Agent底层循环与踩坑全复盘

1. 为什么我坚持“手撸”而不是直接用 LangChain 先说个背景。我接触 AI Agent 这个概念有半年多了,但一直处于“看文章很懂、动手就废”的状态。市面上的教程分两种:一种是讲概念讲得天花乱坠,什么规划、记忆、工具调用、多智能体协作&#…

2026/9/5 20:46:17

AI编程中的Skills技能包:从原理剖析到实战编写与避坑指南

最近 AI 编程圈里,不管你在哪个开发者群潜水,应该都被 “Skills” 这个词刷屏了。Claude Code 在推 Skills,Codex 在推 Skills,Cursor 和 OpenCode 也都跟进,GitHub 上几乎每天都有新的 AI Skills 仓库冒出来。更夸张的…

2026/9/5 20:41:17

南卡Clip E耳夹式耳机体验:AI实时翻译能否成为旅行翻译官?

从一个很常见的画面说起:你刚落地,左手拖着行李箱,右手举着护照。柜台对面的工作人员语速很快,口音也很重,你听到三四个单词,但没抓住完整意思。你想确认是不是这个登机口、是不是需要先取行李,…

2026/9/5 2:46:54

vSound小提琴数字处理器实操指南:从接线到演出的完整配置

电小提琴或者原声小提琴插电演出,第一个绕不开的坎就是声音难听。原声琴的共鸣和空气感一旦进了拾音器,出来的往往是一坨干瘪、发尖、带着奇怪塑料味的信号。我当初第一次把琴接上乐队调音台,直接被主唱吐槽"你这声音像在锯钢丝"。…

2026/9/5 2:46:52

传感器接口IC如何攻克生物化学传感的微弱信号难题?

1. 从电极到比特流:为什么生物化学传感必须依赖专用接口IC 做生物化学传感的人都有过类似的经历:明明传感器本身性能很好,信号输出却一塌糊涂——噪声大、漂移明显、重复性差,怎么调都达不到预期。很多时候问题并不在传感器&#…

2026/9/5 2:44:34

STM32F411CEU6多通道ADC采集:扫描模式+DMA实现详解

1. 多通道 ADC 的用武之地把“Multichannel ADC”和“STM32F411CEU6”这两个关键字放在一起,其实就是嵌入式开发里最常遇到的一类需求:用一块不算贵的 MCU,同时采集多路模拟信号。STM32F411CEU6 是 48 引脚的 Cortex-M4F 主控,主频…

2026/9/5 0:04:47

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流

流式背压机制:避免前端渲染卡死与内存暴涨的滑动窗口限流在大模型流式输出(Streaming)与智能体实时推流的架构中,生产环境中经常出现一种“上下游生产消费速率严重失衡”的极端情况: 生产端极速产出:大模型…

2026/9/5 2:45:13

USB Type-C PCB布局分区设计:电源、高速信号与PD协议全攻略

做硬件这行,Type-C接口算是典型的“看着简单,做起来全坑”的东西。光引脚就24个,高低速信号、电源、控制线全部塞在一个小小的连接器里,如果PCB布局不做规划,打样回来基本就是“插上没反应”、“高速掉线”、“静电一打…

2026/9/5 2:30:42

系统编程学习原型如何补齐稳定性边界

系统编程学习原型如何补齐稳定性边界预算有限时&#xff0c;我先优化明显多余的复制&#xff0c;而不是猜测性地换容器。用借用传递只读数据通常就能减少分配&#xff1a; fn parse(line: &str) -> Result<Item, Error> { /* ... */ }用基准确认热点确实在分配&am…

2026/9/5 2:46:50

雨花区哪家财务公司代理记账比较好?

在雨花区&#xff0c;企业处理财税事务常常面临诸多挑战&#xff0c;选择一家靠谱的财务公司至关重要。湖南巨勤财务管理咨询有限公司就是本地正规实体财税服务机构&#xff0c;深耕本地工商财税行业多年&#xff0c;熟悉当地工商局、税务局最新政策与申报流程。主营公司注册、…