十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Effect 4.0 MCP 服务器日志能力:从 logging 能力广告到按客户端日志级别过滤的完整实现

Effect 4.0 MCP 服务器日志能力:从 logging 能力广告到按客户端日志级别过滤的完整实现 Effect 4.0 MCP 服务器日志能力从 logging 能力广告到按客户端日志级别过滤的完整实现【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect导读本篇文章围绕 Effect 仓库中 changeset.changeset/pre/fair-logs-listen.md所记录的effect: patch——MCP servers now advertise logging and honor each clients selected log level when sending log notifications——深入剖析 Effect 4.0 中 MCP 服务器日志功能的完整实现链路。你将看到MCP 协议中日志能力如何被声明、客户端如何通过logging/setLevel选择接收级别、服务器如何按客户端阈值过滤notifications/message通知以及这一切如何与 Effect 自身的LogLevel体系衔接。读完本文你将能在自己的 MCP 服务器中实现按客户端定制日志输出的生产级能力。一次 patch 背后的能力闭环changeset 是 Changesets 驱动的版本发布声明fair-logs-listen.md描述的是effect包的一次patch级变更其核心语义可以拆解为两个动作advertise loggingMCP 服务器在initialize握手阶段向客户端声明自己支持日志发送能力capabilities.logging。honor each clients selected log level服务器在发送日志通知时会尊重每个客户端各自通过logging/setLevel选择的日志级别阈值只投递达到或超过该阈值的日志。这两个动作虽然描述简短但对应了仓库中一条完整的实现链路协议 SchemaMcpSchema.ts→ 运行时生命周期internal/mcpRuntime.ts→ 会话级状态管理internal/mcpStatefulRuntime.ts→ 通知投影与投递internal/mcpProtocol.ts与McpServer.ts并有配套的 conformance 测试packages/effect/test/unstable/ai/McpServer/McpConformance/LoggingTest.ts逐条验证。下面沿这条链路展开。MCP 日志的协议基础三个 Schema 构件MCPModel Context Protocol规范将日志定义为一组独立于具体传输层的能力原语Effect 在 McpSchema.ts 中将其建模为三个部分。LoggingLevel映射 syslog 严重级别的 8 级字面量export const LoggingLevel: Schema.Literals debug | info | notice | warning | error | critical | alert | emergency Schema.Literals([ debug, info, notice, warning, error, critical, alert, emergency ])这 8 个级别直接对齐 RFC 5424 第 6.2.1 节定义的 syslog 严重级别源码注释明确给出该规范出处。从类型系统看LoggingLevel是Schema.Literals构造的联合字面量 Schema因此未知级别会在解码阶段就被拒绝——这为后文拒绝未知级别的测试行为提供了 Schema 层面的保证。SetLevel客户端到服务器的日志开关export class SetLevel extends Rpc.make(logging/setLevel, { payload: { ...RequestMeta.fields, level: LoggingLevel // 客户端希望接收的最低级别 }, success: Schema.Struct({}), error: McpError }) {}logging/setLevel是客户端 → 服务器方向的请求携带level字段。源码 JSDoc 明确了语义服务器应把等于该级别及更高更严重的日志作为notifications/message发送给客户端。也就是说这是一个阈值开关而不是只发某个级别的精确匹配。LoggingMessageNotification服务器到客户端的日志载体export class LoggingMessageNotification extends Rpc.make(notifications/message, { payload: Schema.Struct({ ...NotificationMeta.fields, level: LoggingLevel, // 该日志消息的严重级别 logger: optional(Schema.String), // 发出日志的 logger 名称可选 data: Schema.Any // 任意 JSON 可序列化数据 }) }) {}notifications/message是服务器 → 客户端方向的通知其 payload 由三部分构成字段类型说明levelLoggingLevel日志消息的严重级别loggerstring \| undefined可选发出日志的 logger 名称dataSchema.Any日志数据任何 JSON 可序列化类型字符串、数字、对象、数组等data: Schema.Any意味着日志内容极为灵活——一个纯字符串、一个结构化错误对象、甚至嵌套数组都可以直接投递测试用例也专门验证了这一点见下文测试验证。能力广告initialize 阶段声明 logging 支持MCP 客户端只有在明确知道服务器支持日志时才会发起logging/setLevel。这一能力声明发生在initialize握手阶段由服务器在其capabilities中携带logging字段。Effect 的ServerCapabilitiesSchema 在 McpSchema.ts 中为logging预留了位置export class ServerCapabilities extends Schema.OpaqueServerCapabilities()(Schema.Struct({ ... /** * Present if the server supports sending log messages to the client. */ logging: optional(Schema.Struct({})), ... })) {}真正的广告行为发生在运行时层。在 internal/mcpRuntime.ts 的initialize生命周期回调中服务器会构造自己的能力集const capabilities: McpCore.CanonicalServerCapabilities { completions: true, logging: true, // ← 无论是否注册了工具/资源/提示词logging 总是被声明 ...(presence.tools ? { tools: { listChanged: true } } : {}), ...(presence.resources ? { resources: { listChanged: true, subscribe: httpRequest undefined } } : {}), ...(presence.prompts ? { prompts: { listChanged: true } } : {}), ...(options.serverInfo.extensions ? { extensions: options.serverInfo.extensions } : {}) }注意这里的细节logging: true是无条件的不像tools、resources、prompts那样依赖注册情况。从源码结构看这体现了日志能力是 MCP 服务器的基础设施级能力——只要用 Effect 运行 MCP 服务器客户端就能在 initialize 响应中看到capabilities.logging并据此决定是否启用日志接收。conformance 测试也对此给出了硬性断言MUST advertise logging when log notifications are supported见下文。客户端选择日志级别setLevel 的会话级存储当客户端决定接收日志时会发送logging/setLevel请求。Effect 的服务端运行时在 internal/mcpRuntime.ts 将这一请求委托给有状态运行时的setLogLevelsetLogLevel: stateful.setLogLevel,其实现位于 internal/mcpStatefulRuntime.tssetLogLevel: (level, clientId, headers) Effect.sync(() { const session resolveSession(clientId, headers) if (session ! undefined) { session.logLevel { _tag: Mcp, level } } }),关键点在于日志级别是按会话session存储的。resolveSession会根据客户端 IDstdio/自定义传输或Mcp-Session-Id头HTTP 传输解析到当前客户端对应的会话对象然后把用户选择的级别写入该会话的logLevel字段。这意味着每个客户端的选择是相互隔离的服务器可以同时服务多个客户端各自拥有不同的日志阈值。这一点在测试should isolate background HTTP log levels when concurrent sessions select different thresholds中得到了显式验证两个并发 HTTP 会话分别选择debug和emergency同一服务器发出的同一条 debug 日志只有选择了debug的会话能收到选择了emergency的会话只能收到 emergency 级别的日志。阈值过滤canDeliver 的投递判定advertise logging与honor each clients selected log level之间的桥梁是canDeliver——服务器在把每条日志通知投递给某个客户端之前都会先经过这一判定。其实现同样在 internal/mcpStatefulRuntime.tscanDeliver: (clientId, headers, notification, fallbackLogLevel) { const session resolveSession(clientId, headers) if (notification._tag LoggingMessage) { const minimum session?.logLevel return minimum?._tag Mcp ? McpProtocol.mcpLogLevels[notification.level].order McpProtocol.mcpLogLevels[minimum.level].order : LogLevel.isGreaterThanOrEqualTo( McpProtocol.mcpLogLevels[notification.level].effect, minimum?.level ?? fallbackLogLevel ) } return notification._tag ! ResourceUpdated || session?.resourceSubscriptions?.has(notification.uri) true }逻辑分为两条路径客户端显式设置过 MCP 级别session.logLevel._tag Mcp比较两个级别的order数值。当前日志的order大于等于客户端所选级别的order才投递客户端没有显式设置退化为用 Effect 的LogLevel.isGreaterThanOrEqualTo与服务器默认日志级别fallbackLogLevel比较。这里的order来自 internal/mcpProtocol.ts 中维护的 MCP 级别 → Effect 日志级别的完整映射表MCP 级别Effect LogLevelorderdebugDebug0infoInfo1noticeInfo2warningWarn3errorError4criticalFatal5alertFatal6emergencyFatal7这张表同时服务于两个目的判定阈值order比较和级别转换effect字段后者用于把 MCP 级别映射为 Effect 自身的LogLevel使 MCP 日志能与 Effect 的日志系统无缝衔接。注意notice被映射为Info、alert/emergency被映射为Fatal——这是 MCP 8 级 syslog 语义到 Effect 5 级Debug/Info/Warn/Error/Fatal的归并。投递判定发生在通知发送路径上。服务器发出的日志通知首先进入notifications/message的投影管线makeNotificationProjector在 internal/mcpProtocol.ts 中把内部LoggingMessage通知投影为协议层的LoggingMessageNotification随后按目标客户端逐一分发canDeliver就是分发前的闸门。与 Effect 日志体系的衔接CurrentLogLevel除了通知投递的阈值过滤本次变更还涉及另一个方向当服务器在处理某个客户端的请求时Effect 代码中的日志应该以何种级别输出。在 McpServer.ts 中服务器启动时会读取当前的默认日志级别const defaultLogLevel yield* CurrentLogLevel这个默认值被传入运行时defaultLogLevel字段在 initialize 时作为会话的初始logLevel见mcpRuntime.ts第 498 行的logLevel: options.defaultLogLevel也是canDeliver的兜底比较基准。更重要的是在McpServer.ts的中间件层第 860-934 行每个来自客户端的请求都会被注入与客户端会话匹配的CurrentLogLevel服务return Effect.provideService( ..., CurrentLogLevel, runtime.effectLogLevel(client.id, headers, defaultLogLevel) )而effectLogLevel的实现internal/mcpStatefulRuntime.ts会将会话中存储的 MCP 级别转换为 Effect 级别const effectLogLevel (clientId, headers, fallback): LogLevel.LogLevel { const session resolveSession(clientId, headers) return session?.logLevel._tag Mcp ? McpProtocol.mcpLogLevels[session.logLevel.level].effect : session?.logLevel.level ?? fallback }由此形成完整闭环客户端设置 MCP 日志级别 → 会话存储 → 请求处理时转换为 Effect 的 CurrentLogLevel → Effect 日志 API 在该请求上下文中按此级别输出。conformance 测试SHOULD update the minimum level for subsequent operations验证的正是这一点调用logging/setLevel设置为debug后后续工具调用中通过 Effect 读取到的当前级别为Debug。现代协议2026-07-28的每请求日志级别上述会话级阈值主要服务于 2024-11-05 至 2025-11-25 的有状态协议规范。而在更新的2026-07-28协议适配器stateless modern中日志过滤还支持**请求级request-scoped**方式客户端可以在单个请求的_meta中携带io.modelcontextprotocol/logLevel字段。McpServer.ts的provideInvocationContext第 163-186 行处理了这一情形const requestMetadata invocation.requestContext.requestMetadata const logLevel Predicate.hasProperty(requestMetadata, io.modelcontextprotocol/logLevel) ? requestMetadata[io.modelcontextprotocol/logLevel] : undefined if (isLoggingLevel(logLevel)) { provided Effect.provideService( provided, CurrentLogLevel, McpProtocolInternal.mcpLogLevels[logLevel].effect ) }即如果请求元数据中声明了合法的 MCP 日志级别就用它覆盖当前请求上下文的CurrentLogLevel如果声明了未知级别isLoggingLevel基于Schema.is(McpSchema.LoggingLevel)判定失败最终由InvalidParams错误拒绝——测试should reject a request when its request-scoped log level is unknown验证了这一点。需要注意在 stateless 现代协议下日志通知不再像有状态协议那样按会话广播给所有已初始化客户端而是跟随产生它的请求/订阅流投递测试MUST not deliver logs from background or another request on an HTTP subscription stream专门约束了这一行为边界。测试验证LoggingTest 的 conformance 覆盖本次变更的协议行为并非只停留在实现层面仓库在 packages/effect/test/unstable/ai/McpServer/McpConformance/LoggingTest.ts 中提供了覆盖四个协议版本2024-11-05、2025-03-26、2025-06-18、2025-11-25与 stateless 现代协议的 conformance 测试套件。值得关注的断言包括能力广告MUST advertise logging when log notifications are supported——initialize 响应的capabilities必须包含logging。级别合法性MUST accept every specified log level8 个级别逐一通过与MUST reject an unknown log level如verbose返回INVALID_PARAMS_ERROR_CODE。阈值语义SHOULD send notifications at the selected level and higher与MUST not send notifications below the selected level——在 stdio 传输上设置warning后warning级别的日志被投递debug级别的日志被过滤。通知格式MUST emit log messages as notifications without an identifier——notifications/message是纯通知不带id与result字段MUST allow arbitrary JSON-compatible log data——data字段接受字符串、数字、布尔、null、数组、嵌套对象。传输完整性SCENARIO does not corrupt the stdio protocol stream with log output——日志通知以结构化帧发送不会污染 stdio 上的 JSON-RPC 请求/响应流。并发隔离should isolate background HTTP log levels when concurrent sessions select different thresholds——不同 HTTP 会话的日志阈值互不干扰。这些测试同时覆盖了 HTTP 与 stdio 两种传输形态说明日志能力在两种传输上行为一致。小结从 changeset 到可运行的服务器能力回看这次patch其价值可以总结为三层协议完备8 级 syslog 级别的 Schema、logging/setLevel请求与notifications/message通知构成了完整的 MCP 日志协议面McpSchema.ts实现严谨initialize 阶段无条件声明capabilities.logging会话级存储每个客户端的级别选择canDeliver按阈值过滤投递并利用mcpLogLevels映射表与 Effect 的CurrentLogLevel/LogLevel体系无缝衔接internal/mcpRuntime.ts、internal/mcpStatefulRuntime.ts行为可验证跨四个协议版本的 conformance 测试把广告能力接受/拒绝级别阈值投递会话隔离等行为固化为可回归的断言LoggingTest.ts。对于使用 Effect 构建 MCP 服务器的开发者这意味着服务器天然支持客户端按需调节日志输出无需在业务代码中手工维护每个客户端的日志开关——只需像测试中那样调用server.notifications[notifications/message]发送日志剩下的能力声明、阈值过滤与级别映射都由运行时完成。【免费下载链接】effectBuild production-ready applications in TypeScript项目地址: https://gitcode.com/GitHub_Trending/ef/effect创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表