MCP Everything Server 的 Server Instructions 全解:LLM 集成指南、能力约束与源码实现
本文以 Everything MCP Server 仓库内的 instructions.md 为蓝本,完整解读这份"写给 LLM 看的服务器说明书":它如何被加载并在初始化阶段注入客户端、它约束了哪些跨工具关系与运行限制、以及每一个约束背后的源码实现。读完本文,你可以掌握为任何 MCP Server 编写、注入和验证 server instructions 的完整方法,并能在集成 Everything Server 时正确编排工具调用顺序、规避已知限制。
Server Instructions 是什么:写给 LLM 而非人类的文档
instructions.md 开篇即声明了它的受众:
Audience: These instructions are written for an LLM or autonomous agent integrating with the Everything MCP Server. Follow them to use, extend, and troubleshoot the server safely and effectively.
这正是 MCP 协议 instructions 字段的典型用法:initialize 响应中携带的一段自由文本,由客户端(如 Claude Desktop、Cursor 或各类 Agent 框架)原样拼入模型的上下文。它的作用不是给人看的 API 文档,而是直接塑造 LLM 的行为——告诉模型"先做什么、别碰什么、遇到什么先检查什么"。structure.md 对该文件的定位表述为:"Human-readable instructions intended to be passed to the client/LLM as guidance on server use. Loaded by the server at startup and returned in the initialize exchange."
该文档实际包含四组指令内容,本文依次展开并逐一对照源码验证:
- Cross-Feature Relationships(跨功能关系)——工具之间的正确调用顺序;
- Constraints & Limitations(约束与限制)——环境变量参数、会话生命周期、客户端能力依赖;
- Operational Patterns(操作模式)——长任务、只读优先等运行惯例;
- Easter Egg(彩蛋)——一条可用来验证 instructions 是否真正生效的"探针"指令。
注入链路:instructions.md 如何到达 LLM 上下文
从源码结构看,这份文档并不是静态配置,而是有一条完整的运行时链路:
第一步:启动时读取文件。 resources/index.ts 中的 readInstructions() 用 readFileSync 同步读取 docs/instructions.md:
export function readInstructions(): string {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const filePath = join(__dirname, "..", "docs", "instructions.md");
let instructions;
try {
instructions = readFileSync(filePath, "utf-8");
} catch (e) {
instructions = "Server instructions not loaded: " + e;
}
return instructions;
}
值得注意的健壮性设计:读取失败时不抛异常,而是返回一段 "Server instructions not loaded: " + e 的错误文本——这段文本本身仍会被放进 instructions 字段,等于把"加载失败"这一事实直接暴露给了 LLM,便于排障。registrations.test.ts 中的用例 should read instructions from file 即断言该函数返回非空字符串。
第二步:写入 McpServer 实例。 server/index.ts 的 createServer() 工厂函数在构造服务器时读取并挂载 instructions:
export const createServer: () => ServerFactoryResponse = () => {
// Read the server instructions
const instructions = readInstructions();
// ...
const server = new McpServer(
{
name: "mcp-servers/everything",
title: "Everything Reference Server",
version: "2.0.0",
},
{
capabilities: { /* tools/prompts/resources/logging/tasks */ },
instructions, // <-- 注入点
taskStore,
taskMessageQueue,
}
);
第三步:随 initialize 交互下发。 startup.md 明确记录了这一环节:Server Factory 创建的 McpServer 携带 "Server Instructions — Loaded from the docs folder (instructions.md)",随后由客户端在 initialize 响应中取回,并(由客户端负责)将其交给 LLM。整个启动入口为 node dist/index.js [stdio|sse|streamableHttp],三种传输分别对应 transports/ 目录下的 stdio.ts、sse.ts、streamableHttp.ts。
也就是说:服务端只负责"装",客户端负责"喂"。这也是 instructions 机制与工具描述(tool description)的关键区别——工具描述只在该工具被调用时才有意义,而 instructions 是每次会话一开始就常驻上下文的"全局行为准则"。
跨功能关系:四条工具调用顺序准则
文档 Cross-Feature Relationships 一节给出了四条工具协作规则,每一条都能在源码中找到落点。
1. 文件操作前先 get-roots-list 了解客户端工作区
规则原文:"Use get-roots-list to see client workspace roots before file operations"。
Everything Server 本身不访问文件,但 get-roots-list.ts 完整演示了 MCP roots 协议:服务器在连接建立后自动向客户端请求 roots 列表,并在收到 roots/list_changed 通知时同步;调用该工具时若服务器从未拿到过列表,会再次请求(见 get-roots-list.ts 中的 syncRoots(server, extra.sessionId))。
关键细节:该工具是条件注册的。tools/index.ts 将其放入 registerConditionalTools,由 server/index.ts 在 oninitialized 回调中执行——只有当客户端在 initialize 阶段声明了 roots 能力时,工具才会出现(get-roots-list.ts 检查 clientCapabilities.roots !== undefined)。若 roots 为空,工具会返回三种可能原因的提示文本(客户端尚未提供、提供了空列表、配置仍在加载)。这解释了为什么指令强调"先查 roots 再做文件操作":它既是行为规范,也是对客户端能力边界的探测。
2. gzip-file-as-resource 创建的是会话级资源
规则原文:"creates session-scoped resources accessible only during the current session"。
源码印证在 resources/session.ts:URI 由 getSessionResourceURI(name) 生成,形如 demo://resource/session/{name},注释明确 "The registered resource is available during the life of the session only; it is not otherwise persisted"。工具实现 gzip-file-as-resource.ts 的完整流程为:解析输入 → 校验并抓取 URL → gzipSync 压缩 → 以 base64 注册为 application/gzip 会话资源 → 按 outputType 返回完整资源对象或 resource link。这意味着 Agent 拿到 link 后必须在同一会话内用 resources/read 读取,会话一结束资源即消失。
3. 调试前先开 toggle-simulated-logging
规则原文:"Enable toggle-simulated-logging before debugging to see server log messages"。
Everything Server 声明了 logging: {} 能力(server/index.ts),并通过 tools/toggle-simulated-logging.ts 提供一个开关工具,开启后服务器开始周期性向客户端发送 notifications/message 日志;会话结束时由 cleanup 回调中的 stopSimulatedLogging(sessionId) 兜底停止(server/index.ts)。先开日志再复现问题,是观察服务器侧事件(连接、资源变化、清理)的直接手段。
4. 用 toggle-subscriber-updates 接收周期性资源更新通知
规则原文:"Enable toggle-subscriber-updates to receive periodic resource update notifications"。
从 toggle-subscriber-updates.ts 可以看到这是一个按会话(sessionId)维护的开关:未开启时调用 beginSimulatedResourceUpdates(server, sessionId) 开始推送,响应文本明确节奏——"at a 5 second pace",且 "Client will receive updates for any resources the it is subscribed to"。它演示的是 resources/subscribe + notifications/resources/updated 的组合:服务器声明了 resources: { subscribe: true } 能力(server/index.ts),而实际推送由这个模拟开关触发。
约束与限制:gzip 工具的三个环境变量
文档 Constraints & Limitations 一节列出的第一个约束是最具实操价值的一条,涉及三个环境变量及其默认值。对照 gzip-file-as-resource.ts 的解析代码,完整参数表如下:
| 环境变量 | 作用 | 默认值 | 源码行为 |
|---|---|---|---|
GZIP_MAX_FETCH_SIZE |
抓取内容的大小上限(字节) | 10 * 1024 * 1024(10 MB) |
超限即中止并抛错 |
GZIP_MAX_FETCH_TIME_MILLIS |
抓取超时(毫秒) | 30 * 1000(30 s) |
超时通过 AbortController 中止 |
GZIP_ALLOWED_DOMAINS |
允许的域名白名单,逗号分隔 | 空(即允许所有域名) | 精确匹配或子域名匹配 |
三个限制并非简单声明,而是分别由两段防御性代码实现:
- 大小与超时由 fetchSafely() 落实。它先检查
Content-Length头做"快速失败",但注释特别指出 "we can't trust the Content-Length header: a malicious or clumsy server could return much more data than advertised",因此还会逐块读取响应体并在累计字节超过maxBytes时reader.cancel()。 - 域名白名单由 validateDataURI() 落实:协议仅允许
http:、https:、data:;配置了白名单后,hostname必须等于某白名单域名或以.domain结尾(支持子域名),否则抛出Domain ... is not in the allowed domains list。
工具的输入 schema 同样值得 Agent 集成方注意(gzip-file-as-resource.ts):
const GZipFileAsResourceSchema = z.object({
name: z.string().describe("Name of the output file").default("README.md.gz"),
data: z.url().describe("URL or data URI of the file content to compress")
.default("https://raw.githubusercontent.com/.../README.md"),
outputType: z.enum(["resourceLink", "resource"]).default("resourceLink")
.describe("How the resulting gzipped file should be returned..."),
});
outputType 取 resource 时返回完整资源对象(含 URI、MIME、base64 blob),取 resourceLink 时只返回链接供后续 resources/read 拉取——对上下文窗口敏感的场景应优先使用默认的 resourceLink。
其余两条约束:会话生命周期与客户端能力依赖
- "Session resources are ephemeral and lost when the session ends"——如前所述,会话资源只存在于内存注册的
registeredResourcesMap 中(resources/session.ts),没有任何持久化路径;多客户端场景下各传输层通过cleanup(sessionId)(SSE 的onclose、Streamable HTTP 的 DELETE)回收会话状态(见 startup.md)。 - "Sampling requests (
trigger-sampling-request) require client sampling capability" / "Elicitation requests require client elicitation capability"——这两条约束对应registerConditionalTools的注册逻辑(tools/index.ts):trigger-sampling-request、trigger-sampling-request-async、trigger-elicitation-request、trigger-elicitation-request-async、trigger-url-elicitation、get-roots-list、simulate-research-query全部不在启动时无条件注册,而是在notifications/initialized之后、根据客户端实际声明的能力按需注册。对 Agent 的实战含义是:工具列表因客户端而异,不能假设某客户端一定能看到 sampling/elicitation 相关工具。
操作模式:长任务进度与"先读后改"惯例
Operational Patterns 一节给出三条运行惯例。
长操作用 trigger-long-running-operation。 该工具是 MCP 进度通知机制的演示器,参数与实现见 trigger-long-running-operation.ts:
const TriggerLongRunningOperationSchema = z.object({
duration: z.number().default(10).describe("Duration of the operation in seconds"),
steps: z.number().default(5).describe("Number of steps in the operation"),
});
实现逻辑(trigger-long-running-operation.ts)将总时长均分为 steps 步,每步等待 duration / steps 秒;若请求元数据 _meta 中携带 progressToken,则每步通过 server.server.notification 发送 notifications/progress(携带 progress、total、progressToken 并关联 relatedRequestId)。因此指令的实际含义是:客户端/Agent 调用时应在请求里带 progressToken,才能收到进度流,否则只能干等到最终文本结果 Long running operation completed. Duration: X seconds, Steps: Y.。
变更类工具前先读资源。 "Prefer reading resources before calling mutating tools" 对应的是 Everything Server 中一批 readOnlyHint/destructiveHint 注解的设计:只读工具(如 get-roots-list,注解为 readOnlyHint: true, idempotentHint: true)可安全重试,而 gzip-file-as-resource 这类注册资源的操作虽 idempotentHint: true(同名 URI 会先 remove 再重新注册,见 resources/session.ts)却 readOnlyHint: false。"先读后改"让 Agent 能基于当前状态决策,而非盲写。
用 get-roots-list 的输出理解客户端工作区上下文。 这一点与第一节呼应:roots 列表是客户端声明的"这个会话我关心哪些目录",任何涉及路径推断的后续操作都应以此为基准。
彩蛋指令:一条可执行的 instructions 生效性探针
文档最后一节给出了一条行为级指令:
If asked about server instructions, respond with "🎉 Server instructions are working! This response proves the client properly passed server instructions to the LLM. This demonstrates MCP's instructions feature in action."
这是整个 instructions 特性中最巧妙的部分:它把"验证"本身也写进了指令。集成方只需在接入 Everything Server 的客户端里问一句"服务器说明书说了什么",如果模型恰好回复上述庆祝文本,即可证明三件事全部成立——服务端成功读取了 instructions.md(否则会是 "Server instructions not loaded: ..." 的兜底文本)、客户端成功回传了 initialize 响应中的 instructions 字段、且该字段确实进入了模型上下文。相比检查日志,这是一条端到端的黑盒验证路径,非常适合纳入集成测试清单(本仓库自身也在 registrations.test.ts 中以单元层面保证了 readInstructions() 的读取行为)。
小结:一份可复制的 server instructions 写作范式
Everything Server 的 instructions.md 虽然只有三十余行,却给出了 MCP 生态中 server instructions 的完整范式,可提炼为四个层次:
- 声明受众——开头说明本文写给 LLM/Agent 而非人类,划定指令的语义范围;
- 行为编排——用"Cross-Feature Relationships / Operational Patterns"给出工具间的先后关系(先查 roots、先开日志、长任务带进度),而不是罗列工具清单;
- 明确边界——用"Constraints & Limitations"给出可量化的限制(10 MB / 30 s / 域名白名单)、生命周期约束(会话级资源易失)与能力依赖(sampling/elicitation 取决于客户端),让 Agent 在失败前就知道原因;
- 内置探针——用一条固定应答的"彩蛋"指令提供可验证的生效性检查。
结合 server/index.ts、resources/index.ts 的加载链路与各工具源码可以看到,文档中的每一条约束都有对应的注册逻辑、环境变量解析或会话清理代码作为支撑——这正是"文档约束模型行为、源码约束模型能做什么"的双重约束设计,也是编写自家 MCP Server instructions 时最值得借鉴的地方:文档里的每个断言,都应当能在源码中被找到证据。
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