mcp init is so slowly in bun, but fastly in nodejs #1597

Closed
opened 2026-02-16 17:31:42 -05:00 by yindo · 10 comments
Owner

Originally created by @zWingz on GitHub (Sep 4, 2025).

Originally assigned to: @thdxr on GitHub.

Image

my code

import { experimental_createMCPClient } from "ai"
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js"

import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js"
const url = "http://drop.qcloud.com:8888/sse"
console.time("transport")
const transport = new SSEClientTransport(new URL(url), {
  requestInit: {
    // headers: mcp.headers,
  },
})
await transport.start()
console.timeEnd("transport")

Originally created by @zWingz on GitHub (Sep 4, 2025). Originally assigned to: @thdxr on GitHub. <img width="999" height="347" alt="Image" src="https://github.com/user-attachments/assets/364b902d-d0d2-4cbc-97c4-1e922084f241" /> my code ```js import { experimental_createMCPClient } from "ai" import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js" import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js" const url = "http://drop.qcloud.com:8888/sse" console.time("transport") const transport = new SSEClientTransport(new URL(url), { requestInit: { // headers: mcp.headers, }, }) await transport.start() console.timeEnd("transport") ```
yindo closed this issue 2026-02-16 17:31:42 -05:00
Author
Owner

@zWingz commented on GitHub (Sep 4, 2025):

upstream https://github.com/oven-sh/bun/issues/22396

@zWingz commented on GitHub (Sep 4, 2025): upstream https://github.com/oven-sh/bun/issues/22396
Author
Owner

@zWingz commented on GitHub (Sep 4, 2025):

Can the MCP client be started on demand?

@zWingz commented on GitHub (Sep 4, 2025): Can the MCP client be started on demand?
Author
Owner

@rekram1-node commented on GitHub (Sep 4, 2025):

started on demand? wdym here

it cant be started “as it is needed” because it needs to be started to get the tools from it

@rekram1-node commented on GitHub (Sep 4, 2025): started on demand? wdym here it cant be started “as it is needed” because it needs to be started to get the tools from it
Author
Owner

@zWingz commented on GitHub (Sep 4, 2025):

@rekram1-node maybe we can use the enabledTools to select which agent shoud be started

Image

mcp client will be started after call tools
Image

class LazyMCPClient {
    private deferred?: Deferred<MCPClient | null>
    private client: MCPClient | null = null
    constructor(
      public key: string,
      private mcp: Config.Mcp,
    ) {}
    public async get() {
      if (this.deferred) {
        return this.deferred.promise()
      }
      this.deferred = new Deferred<MCPClient | null>()
      const client = await this.createClient()
      this.deferred.resolve(client)
      return client
    }
    public close() {
      if (!this.client) {
        return
      }
      this.client.close()
    }
    public async tools() {
      const client = await this.get()
      if (!client) {
        return {}
      }
      return client.tools()
    }
    private async createClient() {
      const { key, mcp } = this
      if (mcp.enabled === false) {
        log.info("mcp server disabled", { key })
        return null
      }
      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 client = await experimental_createMCPClient({
            name: key,
            transport,
          }).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,
            })
            return null
          })
          if (client) {
            log.debug("transport connection succeeded", { key, transport: name })
            return client
          }
        }
        const errorMessage = lastError
          ? `MCP server ${key} failed to connect: ${lastError.message}`
          : `MCP server ${key} failed to connect to ${mcp.url}`
        log.error("remote mcp connection failed", { key, url: mcp.url, error: lastError?.message })
        Bus.publish(Session.Event.Error, {
          error: {
            name: "UnknownError",
            data: {
              message: errorMessage,
            },
          },
        })
        return null
      } else if (mcp.type === "local") {
        const [cmd, ...args] = mcp.command
        return await experimental_createMCPClient({
          name: key,
          transport: new StdioClientTransport({
            stderr: "ignore",
            command: cmd,
            args,
            env: {
              ...process.env,
              ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}),
              ...mcp.environment,
            },
          }),
        }).catch((error) => {
          const errorMessage =
            error instanceof Error
              ? `MCP server ${key} failed to start: ${error.message}`
              : `MCP server ${key} failed to start`
          log.error("local mcp startup failed", {
            key,
            command: mcp.command,
            error: error instanceof Error ? error.message : String(error),
          })
          Bus.publish(Session.Event.Error, {
            error: {
              name: "UnknownError",
              data: {
                message: errorMessage,
              },
            },
          })
          return null
        })
      }
      return null
    }
  }
@zWingz commented on GitHub (Sep 4, 2025): @rekram1-node maybe we can use the `enabledTools` to select which agent shoud be started <img width="976" height="310" alt="Image" src="https://github.com/user-attachments/assets/9bf86001-052a-4653-9ff2-1b5cdc446888" /> mcp client will be started after call `tools` <img width="635" height="156" alt="Image" src="https://github.com/user-attachments/assets/c279ffd6-b82e-44ff-8f66-fc02f43ee5b2" /> ```ts class LazyMCPClient { private deferred?: Deferred<MCPClient | null> private client: MCPClient | null = null constructor( public key: string, private mcp: Config.Mcp, ) {} public async get() { if (this.deferred) { return this.deferred.promise() } this.deferred = new Deferred<MCPClient | null>() const client = await this.createClient() this.deferred.resolve(client) return client } public close() { if (!this.client) { return } this.client.close() } public async tools() { const client = await this.get() if (!client) { return {} } return client.tools() } private async createClient() { const { key, mcp } = this if (mcp.enabled === false) { log.info("mcp server disabled", { key }) return null } 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 client = await experimental_createMCPClient({ name: key, transport, }).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, }) return null }) if (client) { log.debug("transport connection succeeded", { key, transport: name }) return client } } const errorMessage = lastError ? `MCP server ${key} failed to connect: ${lastError.message}` : `MCP server ${key} failed to connect to ${mcp.url}` log.error("remote mcp connection failed", { key, url: mcp.url, error: lastError?.message }) Bus.publish(Session.Event.Error, { error: { name: "UnknownError", data: { message: errorMessage, }, }, }) return null } else if (mcp.type === "local") { const [cmd, ...args] = mcp.command return await experimental_createMCPClient({ name: key, transport: new StdioClientTransport({ stderr: "ignore", command: cmd, args, env: { ...process.env, ...(cmd === "opencode" ? { BUN_BE_BUN: "1" } : {}), ...mcp.environment, }, }), }).catch((error) => { const errorMessage = error instanceof Error ? `MCP server ${key} failed to start: ${error.message}` : `MCP server ${key} failed to start` log.error("local mcp startup failed", { key, command: mcp.command, error: error instanceof Error ? error.message : String(error), }) Bus.publish(Session.Event.Error, { error: { name: "UnknownError", data: { message: errorMessage, }, }, }) return null }) } return null } } ```
Author
Owner

@rekram1-node commented on GitHub (Sep 4, 2025):

@zWingz can you outline the flow you are trying to achieve? I just want to make sure I don't misinterpret you

@rekram1-node commented on GitHub (Sep 4, 2025): @zWingz can you outline the flow you are trying to achieve? I just want to make sure I don't misinterpret you
Author
Owner

@zWingz commented on GitHub (Sep 4, 2025):

Currently, it takes 15 seconds to start a remote MCPClient ( I guess this is a bug of bun ). If I configure multiple ones, the time might double.

I hope to initialize the MCPClient only after the MCPTools has been enabled in the agent that I have configured.

the configuration like

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "bash": {
      "git push": "deny",
      "*": "allow"
    }
  },
  "agent": {
    "build": {
      "tools": {
        "myMcp*": false
      }
    },
    "general": {
      "tools": {
        "myMcp_*": true
      }
    }
  },
  "mcp": {
    "myMcp": {
      "url": "http://drop.qcloud.com:8888/sse",
      "type": "remote"
    },
    "otherMcp": {
      "url": "http://mcp.example.com:8000/sse",
      "type": "remote"
    }
  }
}

@zWingz commented on GitHub (Sep 4, 2025): Currently, it takes 15 seconds to start a remote MCPClient ( I guess this is a bug of bun ). If I configure multiple ones, the time might double. I hope to initialize the MCPClient only after the MCPTools has been enabled in the agent that I have configured. the configuration like ```json { "$schema": "https://opencode.ai/config.json", "permission": { "bash": { "git push": "deny", "*": "allow" } }, "agent": { "build": { "tools": { "myMcp*": false } }, "general": { "tools": { "myMcp_*": true } } }, "mcp": { "myMcp": { "url": "http://drop.qcloud.com:8888/sse", "type": "remote" }, "otherMcp": { "url": "http://mcp.example.com:8000/sse", "type": "remote" } } } ```
Author
Owner

@rekram1-node commented on GitHub (Sep 4, 2025):

Yeah ig out of the box we don't support this currently, could be possible if mcp configuration was also possible at the agent level

@rekram1-node commented on GitHub (Sep 4, 2025): Yeah ig out of the box we don't support this currently, could be possible if mcp configuration was also possible at the agent level
Author
Owner

@zWingz commented on GitHub (Sep 4, 2025):

do you mean

{
"agent": {
    "build": {
      "mcp": {
        "myMcp": false
      }
    },
    "general": {
      "mcp": {
        "myMcp": false
      }
    }
  }
}
@zWingz commented on GitHub (Sep 4, 2025): do you mean ```json { "agent": { "build": { "mcp": { "myMcp": false } }, "general": { "mcp": { "myMcp": false } } } } ```
Author
Owner

@rekram1-node commented on GitHub (Sep 4, 2025):

Sorry I was being confusing, ignore what I said last. I understand what you are trying to accomplish

@rekram1-node commented on GitHub (Sep 4, 2025): Sorry I was being confusing, ignore what I said last. I understand what you are trying to accomplish
Author
Owner

@rekram1-node commented on GitHub (Dec 27, 2025):

[automated] Closing due to 90+ days of inactivity. Feel free to reopen if you still need this!

@rekram1-node commented on GitHub (Dec 27, 2025): [automated] Closing due to 90+ days of inactivity. Feel free to reopen if you still need this!
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#1597