feat(mcp): support server log notifications (#31752)

This commit is contained in:
Aiden Cline 2026-06-10 14:59:36 -05:00 committed by GitHub
parent c51a1588c7
commit 07b983e82f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 30 additions and 2 deletions

View file

@ -7,7 +7,12 @@ import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/
import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"
import { UnauthorizedError } from "@modelcontextprotocol/sdk/client/auth.js"
import { type Tool as MCPToolDef, ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
import {
type LoggingMessageNotification,
LoggingMessageNotificationSchema,
type Tool as MCPToolDef,
ToolListChangedNotificationSchema,
} from "@modelcontextprotocol/sdk/types.js"
import { Config } from "@/config/config"
import { ConfigMCPV1 } from "@opencode-ai/core/v1/config/mcp"
import { NamedError } from "@opencode-ai/core/util/error"
@ -391,6 +396,10 @@ export const layer = Layer.effect(
)
function watch(s: State, name: string, client: MCPClient, bridge: EffectBridge.Shape, timeout?: number) {
client.setNotificationHandler(LoggingMessageNotificationSchema, (notification) =>
bridge.promise(serverLog(name, notification.params)),
)
if (!client.getServerCapabilities()?.tools) return
client.setNotificationHandler(ToolListChangedNotificationSchema, async () => {
if (s.clients[name] !== client || s.status[name]?.status !== "connected") return
@ -404,6 +413,24 @@ export const layer = Layer.effect(
})
}
function serverLog(name: string, params: LoggingMessageNotification["params"]) {
const fields = { server: name, logger: params.logger, level: params.level, data: params.data }
switch (params.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 state = yield* InstanceState.make<State>(
Effect.fn("MCP.state")(function* () {
const cfg = yield* cfgSvc.get()

View file

@ -1,4 +1,5 @@
import { expect, mock, beforeEach } from "bun:test"
import { ToolListChangedNotificationSchema } from "@modelcontextprotocol/sdk/types.js"
import { Cause, Effect, Exit } from "effect"
import type { MCP as MCPNS } from "../../src/mcp/index"
import { testEffect } from "../lib/effect"
@ -394,7 +395,7 @@ it.instance(
{ name: "next_tool", description: "next", inputSchema: { type: "object", properties: {} } },
]
const handler = Array.from(serverState.notificationHandlers.values())[0]
const handler = serverState.notificationHandlers.get(ToolListChangedNotificationSchema)
expect(handler).toBeDefined()
yield* Effect.promise(() => handler?.())