diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 9a750d0b2600..3e08ec5954a9 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -399,7 +399,12 @@ const layer = Layer.effect( if (!mcpClient) { if (status.status !== "connected" && status.status !== "disabled") { - yield* Effect.logWarning("server unavailable", { key, type: mcp.type, status: status.status }) + yield* Effect.logWarning("server unavailable", { + key, + type: mcp.type, + status: status.status, + error: "error" in status ? status.error : undefined, + }) } return { status } satisfies CreateResult } diff --git a/packages/opencode/test/mcp/lifecycle.test.ts b/packages/opencode/test/mcp/lifecycle.test.ts index 7a3048716aa9..d74edd0220b1 100644 --- a/packages/opencode/test/mcp/lifecycle.test.ts +++ b/packages/opencode/test/mcp/lifecycle.test.ts @@ -14,7 +14,7 @@ import { type Tool, } from "@modelcontextprotocol/sdk/types.js" import { LayerNode } from "@opencode-ai/core/effect/layer-node" -import { Cause, Effect, Exit } from "effect" +import { Cause, Effect, Exit, Logger } from "effect" import type { MCP as MCPNS } from "../../src/mcp/index" import { MCP } from "../../src/mcp/index" import { McpOAuthCallback } from "../../src/mcp/oauth-callback" @@ -493,6 +493,30 @@ it.instance("unavailable remote server is marked failed without tools", () => }), ) +it.instance("unavailable remote server surfaces the underlying error in the log", () => + Effect.gen(function* () { + const server = yield* Effect.acquireRelease( + Effect.sync(() => Bun.serve({ port: 0, fetch: () => new Response("unavailable", { status: 503 }) })), + (http) => Effect.promise(() => http.stop(true)), + ) + const mcp = yield* MCP.Service + const captured: Array = [] + const capture = Logger.make((options) => { + captured.push(options.message) + }) + + yield* mcp + .add("unlogged", remote(server.url.toString(), 500)) + .pipe(Effect.provide(Logger.layer([capture], { mergeWithExisting: true }))) + + const logged = captured.find((item) => Array.isArray(item) && item[0] === "server unavailable") + expect(logged).toBeDefined() + if (!Array.isArray(logged)) return + const fields: unknown = logged[1] + expect(fields).toMatchObject({ status: "failed", error: expect.any(String) }) + }), +) + it.instance("tools() prefixes sanitized server and tool names", () => Effect.gen(function* () { const server = yield* lifecycleServer({ capabilities: { tools: {} } })