From 087cd3e97b07ba762304668d45de746394c700b2 Mon Sep 17 00:00:00 2001 From: Aiden Cline Date: Tue, 28 Oct 2025 14:03:58 -0500 Subject: [PATCH] fix: port mcp logic --- packages/opencode/src/acp/agent.ts | 3 +- packages/opencode/src/mcp/index.ts | 259 +++++++++++++++++------------ 2 files changed, 153 insertions(+), 109 deletions(-) diff --git a/packages/opencode/src/acp/agent.ts b/packages/opencode/src/acp/agent.ts index 5d921735f..ae9a74a66 100644 --- a/packages/opencode/src/acp/agent.ts +++ b/packages/opencode/src/acp/agent.ts @@ -431,8 +431,7 @@ export namespace ACP { await Promise.all( Object.entries(mcpServers).map(async ([key, mcp]) => { - // TODO: add to mcp - // await MCP.add(key, mcp) + await MCP.add(key, mcp) }), ) diff --git a/packages/opencode/src/mcp/index.ts b/packages/opencode/src/mcp/index.ts index 96e91638e..e60c6a827 100644 --- a/packages/opencode/src/mcp/index.ts +++ b/packages/opencode/src/mcp/index.ts @@ -50,129 +50,174 @@ export namespace MCP { ref: "MCPStatus", }) export type Status = z.infer + type MCPClient = Awaited> const state = Instance.state( async () => { const cfg = await Config.get() + const config = cfg.mcp ?? {} const clients: Record = {} const status: Record = {} - for (const [key, mcp] of Object.entries(cfg.mcp ?? {})) { - if (mcp.enabled === false) { - log.info("mcp server disabled", { key }) - continue - } - log.info("found", { key, type: mcp.type }) - if (mcp.type === "remote") { - const transports = [ - { - name: "StreamableHTTP", - transport: new StreamableHTTPClientTransport(new URL(mcp.url), { - requestInit: { - headers: mcp.headers, - }, - }), - }, - { - name: "SSE", - transport: new SSEClientTransport(new URL(mcp.url), { - requestInit: { - headers: mcp.headers, - }, - }), - }, - ] - let lastError: Error | undefined - for (const { name, transport } of transports) { - const result = await experimental_createMCPClient({ - name: "opencode", - transport, - }) - .then((client) => { - log.info("connected", { key, transport: name }) - clients[key] = client - status[key] = { - status: "connected", - } - return true - }) - .catch((error) => { - lastError = error instanceof Error ? error : new Error(String(error)) - log.debug("transport connection failed", { - key, - transport: name, - url: mcp.url, - error: lastError.message, - }) - status[key] = { - status: "failed", - error: lastError.message, - } - return false - }) - if (result) break + + await Promise.all( + Object.entries(config).map(async ([key, mcp]) => { + const result = await create(key, mcp).catch(() => undefined) + if (!result) return + + status[key] = result.status + + if (result.mcpClient) { + clients[key] = result.mcpClient } - } - - if (mcp.type === "local") { - const [cmd, ...args] = mcp.command - await experimental_createMCPClient({ - name: "opencode", - transport: new StdioClientTransport({ - stderr: "ignore", - command: cmd, - args, - env: { - ...process.env, - ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), - ...mcp.environment, - }, - }), - }) - .then((client) => { - clients[key] = client - status[key] = { - status: "connected", - } - }) - .catch((error) => { - log.error("local mcp startup failed", { - key, - command: mcp.command, - error: error instanceof Error ? error.message : String(error), - }) - status[key] = { - status: "failed", - error: error instanceof Error ? error.message : String(error), - } - }) - } - } - - for (const [key, client] of Object.entries(clients)) { - log.info("checking tools", { key }) - const result = await withTimeout(client.tools(), 5000).catch(() => {}) - if (!result) { - client.close() - delete clients[key] - status[key] = { - status: "failed", - error: "Failed to get tools", - } - } - } - + }), + ) return { status, clients, } }, async (state) => { - for (const client of Object.values(state.clients)) { - client.close() - } + await Promise.all(Object.values(state.clients).map(async (client) => await client.close())) }, ) + export async function add(name: string, mcp: Config.Mcp) { + const s = await state() + const result = await create(name, mcp) + if (!result) return + if (!result.mcpClient) { + s.status[name] = result.status + return + } + s.clients[name] = result.mcpClient + s.status[name] = result.status + } + + async function create(key: string, mcp: Config.Mcp) { + if (mcp.enabled === false) { + log.info("mcp server disabled", { key }) + return + } + log.info("found", { key, type: mcp.type }) + let mcpClient: MCPClient | undefined + let status: Status | undefined + + if (mcp.type === "remote") { + const transports = [ + { + name: "StreamableHTTP", + transport: new StreamableHTTPClientTransport(new URL(mcp.url), { + requestInit: { + headers: mcp.headers, + }, + }), + }, + { + name: "SSE", + transport: new SSEClientTransport(new URL(mcp.url), { + requestInit: { + headers: mcp.headers, + }, + }), + }, + ] + let lastError: Error | undefined + for (const { name, transport } of transports) { + const result = await experimental_createMCPClient({ + name: "opencode", + transport, + }) + .then((client) => { + log.info("connected", { key, transport: name }) + mcpClient = client + status = { status: "connected" } + return true + }) + .catch((error) => { + lastError = error instanceof Error ? error : new Error(String(error)) + log.debug("transport connection failed", { + key, + transport: name, + url: mcp.url, + error: lastError.message, + }) + status = { + status: "failed", + error: lastError.message, + } + return false + }) + if (result) break + } + } + + if (mcp.type === "local") { + const [cmd, ...args] = mcp.command + await experimental_createMCPClient({ + name: "opencode", + transport: new StdioClientTransport({ + stderr: "ignore", + command: cmd, + args, + env: { + ...process.env, + ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), + ...mcp.environment, + }, + }), + }) + .then((client) => { + mcpClient = client + status = { + status: "connected", + } + }) + .catch((error) => { + log.error("local mcp startup failed", { + key, + command: mcp.command, + error: error instanceof Error ? error.message : String(error), + }) + status = { + status: "failed", + error: error instanceof Error ? error.message : String(error), + } + }) + } + + if (!status) { + status = { + status: "failed", + error: "Unknown error", + } + } + + if (!mcpClient) { + return { + mcpClient: undefined, + status, + } + } + + const result = await withTimeout(mcpClient.tools(), 5000).catch(() => {}) + if (!result) { + await mcpClient.close() + status = { + status: "failed", + error: "Failed to get tools", + } + return { + mcpClient: undefined, + status, + } + } + + return { + mcpClient, + status, + } + } + export async function status() { return state().then((state) => state.status) }