feat(core): log mcp server messages (#34529)

This commit is contained in:
Aiden Cline
2026-06-29 18:28:17 -05:00
committed by GitHub
parent d66c5f3bfa
commit 931323005c
2 changed files with 32 additions and 0 deletions
+13
View File
@@ -11,6 +11,8 @@ import {
CallToolResultSchema,
ListRootsRequestSchema,
ListToolsResultSchema,
type LoggingMessageNotification,
LoggingMessageNotificationSchema,
ToolListChangedNotificationSchema,
ToolSchema,
} from "@modelcontextprotocol/sdk/types.js"
@@ -54,6 +56,12 @@ export interface CallToolResult {
readonly content: ReadonlyArray<CallToolContent>
}
export interface LogMessage {
readonly level: LoggingMessageNotification["params"]["level"]
readonly logger: LoggingMessageNotification["params"]["logger"]
readonly data: LoggingMessageNotification["params"]["data"]
}
/** Handle over a connected MCP server that keeps the SDK `Client` out of the rest of core. */
export interface Connection {
/** Server-supplied usage instructions from the initialize result, if any. */
@@ -66,6 +74,8 @@ export interface Connection {
readonly args?: Record<string, unknown>
}) => Effect.Effect<CallToolResult, Error>
readonly onClose: (callback: () => void) => void
/** Registers a callback fired when the server emits an MCP logging notification. */
readonly onLog: (callback: (message: LogMessage) => void) => void
/** Registers a callback fired when the server announces its tool list changed; no-op if unsupported. */
readonly onToolsChanged: (callback: () => void) => void
}
@@ -186,6 +196,9 @@ export const connect = Effect.fnUntraced(function* (
onClose: (callback) => {
client.onclose = callback
},
onLog: (callback) => {
client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) => callback(notification.params))
},
onToolsChanged: (callback) => {
if (!client.getServerCapabilities()?.tools?.listChanged) return
client.setNotificationHandler(ToolListChangedNotificationSchema, async () => callback())
+19
View File
@@ -251,6 +251,7 @@ export const layer = Layer.effect(
entry.status = { status: "failed", error: "Connection closed" }
fork(events.publish(McpEvent.ToolsChanged, { server: name }).pipe(Effect.ignore))
})
connection.onLog((message) => fork(serverLog(name, message).pipe(Effect.ignore)))
connection.onToolsChanged(() => {
fork(
refreshTools(name, entry, connection).pipe(
@@ -261,6 +262,24 @@ export const layer = Layer.effect(
})
}
const serverLog = (server: ServerName, message: MCPClient.LogMessage) => {
const fields = { server, logger: message.logger, level: message.level, data: message.data }
switch (message.level) {
case "debug":
return Effect.logDebug("MCP server log", fields)
case "info":
case "notice":
return Effect.logInfo("MCP server log", fields)
case "warning":
return Effect.logWarning("MCP server log", fields)
case "error":
case "critical":
case "alert":
case "emergency":
return Effect.logError("MCP server log", fields)
}
}
const startServer = (name: ServerName, entry: ServerEntry) =>
Effect.gen(function* () {
const scope = yield* Scope.fork(root)