Skip to content

Commit 700d1e2

Browse files
committed
fix: mcp hub tools/list empty after initialize
1 parent 8842577 commit 700d1e2

2 files changed

Lines changed: 106 additions & 4 deletions

File tree

packages/agent-infra/mcp-hub/src/mcp/server.ts

Lines changed: 102 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -239,8 +239,17 @@ export class MCPServerEndpoint {
239239
// Setup capability synchronization once
240240
this.setupCapabilitySync();
241241

242-
// Initial capability registration
243-
this.syncCapabilities();
242+
// Initial capability registration - only sync if there are already connected servers
243+
// This prevents empty tool lists during hub initialization
244+
const hasConnectedServers = Array.from(
245+
this.mcpHub.connections.values(),
246+
).some((conn) => conn.status === 'connected');
247+
248+
if (hasConnectedServers) {
249+
this.syncCapabilities();
250+
}
251+
// If no servers are connected yet, syncCapabilities will be called automatically
252+
// when servers connect via the hubStateChanged event listener
244253
}
245254

246255
getEndpointUrl(): string {
@@ -298,6 +307,13 @@ export class MCPServerEndpoint {
298307
// Setup list handler if schema exists
299308
if (capType.listSchema) {
300309
server.setRequestHandler(capType.listSchema, async () => {
310+
// Wait for servers to be ready before returning the list
311+
// This handles the case where endpoint was created before servers finished connecting
312+
await this.waitForServersReady();
313+
314+
// Sync capabilities after servers are ready
315+
this.syncCapabilities([capId]);
316+
301317
const capabilityMap = this.registeredCapabilities[capId];
302318
let capabilities = Array.from(capabilityMap.values());
303319
const searchTerms =
@@ -533,6 +549,90 @@ export class MCPServerEndpoint {
533549
});
534550
}
535551

552+
/**
553+
* Wait for all servers to be ready (connected or failed)
554+
* This prevents returning empty capability lists during server initialization
555+
*/
556+
async waitForServersReady(): Promise<void> {
557+
const timeout = 30000; // 30 seconds total timeout
558+
const startTime = Date.now();
559+
560+
// First, wait for connections to be created (if hub is still initializing)
561+
while (true) {
562+
const connections = Array.from(this.mcpHub.connections.values());
563+
564+
logger.debug(
565+
`waitForServersReady: checking ${connections.length} connections`,
566+
);
567+
568+
if (connections.length > 0) {
569+
// Connections exist, break out of waiting loop
570+
break;
571+
}
572+
573+
if (Date.now() - startTime > timeout) {
574+
// Timeout waiting for connections to be created
575+
logger.debug(
576+
'waitForServersReady: no connections created within timeout',
577+
);
578+
return;
579+
}
580+
581+
// Wait a bit and check again
582+
await new Promise((resolve) => setTimeout(resolve, 100));
583+
}
584+
585+
// Now wait for all connections to finish connecting
586+
const connections = Array.from(this.mcpHub.connections.values());
587+
588+
// Check if any servers are still connecting
589+
const connectingServers = connections.filter(
590+
(conn) => conn.status === 'connecting' && !conn.disabled,
591+
);
592+
593+
logger.debug(
594+
`waitForServersReady: ${connectingServers.length} servers still connecting`,
595+
{
596+
connecting: connectingServers.map((c) => c.name),
597+
},
598+
);
599+
600+
if (connectingServers.length === 0) {
601+
// All servers are already in a final state (connected/disconnected)
602+
logger.debug('waitForServersReady: all servers in final state');
603+
return;
604+
}
605+
606+
// Wait for all connecting servers to finish (reuse timeout from above)
607+
logger.debug('waitForServersReady: waiting for servers to finish...');
608+
609+
while (true) {
610+
const stillConnecting = Array.from(
611+
this.mcpHub.connections.values(),
612+
).filter((conn) => conn.status === 'connecting' && !conn.disabled);
613+
614+
if (stillConnecting.length === 0) {
615+
// All servers have finished connecting
616+
logger.debug('waitForServersReady: all servers finished connecting');
617+
break;
618+
}
619+
620+
if (Date.now() - startTime > timeout) {
621+
// Timeout - log warning but continue
622+
logger.warn(
623+
`Timeout waiting for ${stillConnecting.length} servers to connect`,
624+
{
625+
servers: stillConnecting.map((c) => c.name),
626+
},
627+
);
628+
break;
629+
}
630+
631+
// Wait a bit before checking again
632+
await new Promise((resolve) => setTimeout(resolve, 100));
633+
}
634+
}
635+
536636
/**
537637
* Synchronize the servers map with current connection states
538638
* Creates safe server IDs for namespacing capabilities

packages/agent-infra/mcp-hub/src/server.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,10 @@ class ServiceManager {
288288
this.broadcastSubscriptionEvent(SubscriptionTypes.SERVERS_UPDATED, data);
289289
});
290290

291-
// Initialize MCP server endpoint
291+
// Initialize MCP Hub and wait for all servers to be ready
292+
await this.mcpHub.initialize();
293+
294+
// Initialize MCP server endpoint AFTER all servers are connected
292295
try {
293296
mcpServerEndpoint = new MCPServerEndpoint(this.mcpHub, {
294297
stateless: Boolean(this.stateless),
@@ -308,7 +311,6 @@ class ServiceManager {
308311
);
309312
}
310313

311-
await this.mcpHub.initialize();
312314
this.setState(HubState.READY);
313315
}
314316

0 commit comments

Comments
 (0)