From 07b983e82fcb808101593a5268c10e6e38ed9976 Mon Sep 17 00:00:00 2001 From: Aiden Cline <63023139+rekram1-node@users.noreply.github.com> Date: Wed, 10 Jun 2026 14:59:36 -0500 Subject: [PATCH] feat(mcp): support server log notifications (#31752) --- packages/opencode/src/mcp/index.ts | 29 +++++++++++++++++++- packages/opencode/test/mcp/lifecycle.test.ts | 3 +- 2 files changed, 30 insertions(+), 2 deletions(-) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 0ff39589a2..5a73322510 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -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( Effect.fn("MCP.state")(function* () { const cfg = yield* cfgSvc.get() diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index 004909fb6e..0aef477d14 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -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?.())