Skip to content

Commit f28d1b4

Browse files
committed
fix(core): serialize MCP endpoint startup
1 parent b81be46 commit f28d1b4

1 file changed

Lines changed: 6 additions & 2 deletions

File tree

‎packages/core/src/mcp/index.ts‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ type ServerEntry = {
8181
// persisted session row, so their forms are owned by this opaque sentinel session identifier.
8282
const GLOBAL_ELICITATION_SESSION_ID = "global"
8383
const URL_ELICITATION_FIELD_KEY = "elicitation"
84+
// Connections remain Location-scoped, but shared remote endpoints should not receive concurrent startup bursts.
85+
const endpointLoads = KeyedMutex.makeUnsafe<string>()
8486

8587
type Data = {
8688
servers: Map<ServerName, Types.DeepMutable<Mcp.ServerConfig>>
@@ -387,7 +389,7 @@ export const layer = (options?: Options) =>
387389
const { McpClient } = yield* Effect.promise(() => import("./client.js"))
388390
// List tools as part of connect so a failure here marks the server failed rather than
389391
// leaving it connected with a silently empty tool list and no path to recover.
390-
const result = yield* McpClient.connect(
392+
const load = McpClient.connect(
391393
name,
392394
entry.config,
393395
location.directory,
@@ -399,8 +401,10 @@ export const layer = (options?: Options) =>
399401
// A stdio server is spawned on this location's execution plane, not the host's.
400402
Effect.provideService(Environment.Service, environment),
401403
Scope.provide(scope),
402-
Effect.exit,
403404
)
405+
const result = yield* (
406+
entry.config.type === "remote" ? endpointLoads.withLock(entry.config.url)(load) : load
407+
).pipe(Effect.exit)
404408
if (Exit.isSuccess(result)) {
405409
entry.client = result.value.connection
406410
entry.tools = result.value.tools.map((tool) => toTool(name, entry, tool))

0 commit comments

Comments
 (0)