Skip to content

Commit db3e4eb

Browse files
committed
feat(satellite): implement unified tool discovery manager for stdio and remote mcp
1 parent dfd2c87 commit db3e4eb

File tree

5 files changed

+399
-21
lines changed

5 files changed

+399
-21
lines changed

services/satellite/README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,11 +141,13 @@ The satellite will **fail to start** with clear error messages if required varia
141141
### System Prerequisites
142142

143143
**Local Development:**
144+
144145
- No special requirements - stdio MCP server process management works without isolation
145146
- Plain Node.js spawn() used for easy debugging
146147
- Works on macOS, Windows, and Linux
147148

148149
**Production (Docker):**
150+
149151
- nsjail automatically installed in Docker image for secure process isolation
150152
- See Dockerfile for configuration details
151153

services/satellite/src/server.ts

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ import { BackendClient } from './services/backend-client';
1010
import { HeartbeatService } from './services/heartbeat-service';
1111
import { HttpProxyManager } from './services/http-proxy-manager';
1212
import { RemoteToolDiscoveryManager } from './services/remote-tool-discovery-manager';
13+
import { StdioToolDiscoveryManager } from './services/stdio-tool-discovery-manager';
14+
import { UnifiedToolDiscoveryManager } from './services/unified-tool-discovery-manager';
15+
import { ProcessManager } from './process/manager';
16+
import { RuntimeState } from './process/runtime-state';
1317
import { McpProtocolHandler } from './services/mcp-protocol-handler';
1418
import { CommandPollingService, ConfigurationUpdate } from './services/command-polling-service';
1519
import { DynamicConfigManager } from './services/dynamic-config-manager';
@@ -204,8 +208,31 @@ export async function createServer() {
204208
const httpProxyManager = new HttpProxyManager(server, server.log);
205209
httpProxyManager.setConfigManager(dynamicConfigManager);
206210

207-
// Initialize Remote Tool Discovery Manager with dynamic config
208-
const toolDiscoveryManager = new RemoteToolDiscoveryManager(server.log);
211+
// Initialize Process Manager and Runtime State for stdio subprocess servers
212+
const runtimeState = new RuntimeState();
213+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
214+
const processManager = new ProcessManager(server.log as any); // Fastify logger is compatible with pino Logger
215+
216+
// Initialize Remote Tool Discovery Manager for HTTP/SSE remote servers
217+
const remoteToolDiscoveryManager = new RemoteToolDiscoveryManager(server.log);
218+
remoteToolDiscoveryManager.setConfigManager(dynamicConfigManager);
219+
220+
// Initialize Stdio Tool Discovery Manager for stdio subprocess servers
221+
const stdioToolDiscoveryManager = new StdioToolDiscoveryManager(
222+
processManager,
223+
runtimeState,
224+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
225+
server.log as any // Fastify logger is compatible with pino Logger
226+
);
227+
228+
// Initialize Unified Tool Discovery Manager to coordinate both transport types
229+
const toolDiscoveryManager = new UnifiedToolDiscoveryManager(
230+
remoteToolDiscoveryManager,
231+
stdioToolDiscoveryManager,
232+
processManager,
233+
runtimeState,
234+
server.log
235+
);
209236
toolDiscoveryManager.setConfigManager(dynamicConfigManager);
210237

211238
// Set up configuration change handler for tool discovery
@@ -236,6 +263,8 @@ export async function createServer() {
236263
server.decorate('mcpProtocolHandler', mcpProtocolHandler);
237264
server.decorate('dynamicConfigManager', dynamicConfigManager);
238265
server.decorate('commandProcessor', commandProcessor);
266+
server.decorate('processManager', processManager);
267+
server.decorate('runtimeState', runtimeState);
239268

240269
server.log.info({
241270
operation: 'handlers_initialized',

services/satellite/src/services/mcp-protocol-handler.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* eslint-disable @typescript-eslint/no-explicit-any */
22
import { FastifyBaseLogger } from 'fastify';
33
import { HttpProxyManager } from './http-proxy-manager';
4-
import { RemoteToolDiscoveryManager } from './remote-tool-discovery-manager';
4+
import { UnifiedToolDiscoveryManager } from './unified-tool-discovery-manager';
55
import { ProxyRequestContext } from '../types/mcp-server';
66

77
/**
@@ -11,9 +11,9 @@ import { ProxyRequestContext } from '../types/mcp-server';
1111
export class McpProtocolHandler {
1212
private logger: FastifyBaseLogger;
1313
private httpProxyManager: HttpProxyManager;
14-
private toolDiscoveryManager: RemoteToolDiscoveryManager;
14+
private toolDiscoveryManager: UnifiedToolDiscoveryManager;
1515

16-
constructor(httpProxyManager: HttpProxyManager, toolDiscoveryManager: RemoteToolDiscoveryManager, logger: FastifyBaseLogger) {
16+
constructor(httpProxyManager: HttpProxyManager, toolDiscoveryManager: UnifiedToolDiscoveryManager, logger: FastifyBaseLogger) {
1717
this.httpProxyManager = httpProxyManager;
1818
this.toolDiscoveryManager = toolDiscoveryManager;
1919
this.logger = logger.child({ component: 'McpProtocolHandler' });
@@ -126,14 +126,14 @@ export class McpProtocolHandler {
126126
}
127127

128128
/**
129-
* Handle tools/list request - returns cached discovered tools from remote MCP servers
129+
* Handle tools/list request - returns cached discovered tools from both stdio and remote MCP servers
130130
*/
131131
private async handleToolsList(): Promise<any> {
132132
this.logger.debug({
133133
operation: 'mcp_tools_list'
134-
}, 'Listing cached discovered tools from remote MCP servers');
134+
}, 'Listing cached discovered tools from stdio and remote MCP servers');
135135

136-
const cachedTools = this.toolDiscoveryManager.getCachedTools();
136+
const cachedTools = this.toolDiscoveryManager.getAllTools();
137137

138138
const tools = cachedTools.map(tool => ({
139139
name: tool.namespacedName,
@@ -149,7 +149,7 @@ export class McpProtocolHandler {
149149
tools: tools.map(t => t.name),
150150
discovery_ready: this.toolDiscoveryManager.isReady(),
151151
result_object: result
152-
}, `Returning ${tools.length} cached tools from remote MCP servers`);
152+
}, `Returning ${tools.length} cached tools from stdio and remote MCP servers`);
153153

154154
this.logger.debug({
155155
operation: 'mcp_tools_list_debug',
@@ -188,11 +188,11 @@ export class McpProtocolHandler {
188188
const serverSlug = namespacedToolName.substring(0, dashIndex);
189189

190190
// Find the cached tool to get the original tool name and verify it exists
191-
const cachedTools = this.toolDiscoveryManager.getCachedTools();
192-
const cachedTool = cachedTools.find(tool => tool.namespacedName === namespacedToolName);
191+
const cachedTool = this.toolDiscoveryManager.getTool(namespacedToolName);
193192

194193
if (!cachedTool) {
195-
throw new Error(`Tool not found: ${namespacedToolName}. Available tools: ${cachedTools.map(t => t.namespacedName).join(', ')}`);
194+
const allTools = this.toolDiscoveryManager.getAllTools();
195+
throw new Error(`Tool not found: ${namespacedToolName}. Available tools: ${allTools.map(t => t.namespacedName).join(', ')}`);
196196
}
197197

198198
// Use OAuth team context to find the correct server instance

services/satellite/src/services/team-aware-mcp-handler.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import { FastifyBaseLogger } from 'fastify';
33
import { McpProtocolHandler } from './mcp-protocol-handler';
44
import { DynamicConfigManager } from './dynamic-config-manager';
5-
import { RemoteToolDiscoveryManager } from './remote-tool-discovery-manager';
5+
import { UnifiedToolDiscoveryManager } from './unified-tool-discovery-manager';
66

77
/**
88
* Team-Aware MCP Protocol Handler
@@ -12,12 +12,12 @@ export class TeamAwareMcpHandler {
1212
private logger: FastifyBaseLogger;
1313
private baseHandler: McpProtocolHandler;
1414
private configManager: DynamicConfigManager;
15-
private toolDiscoveryManager: RemoteToolDiscoveryManager;
15+
private toolDiscoveryManager: UnifiedToolDiscoveryManager;
1616

1717
constructor(
1818
baseHandler: McpProtocolHandler,
1919
configManager: DynamicConfigManager,
20-
toolDiscoveryManager: RemoteToolDiscoveryManager,
20+
toolDiscoveryManager: UnifiedToolDiscoveryManager,
2121
logger: FastifyBaseLogger
2222
) {
2323
this.baseHandler = baseHandler;
@@ -141,8 +141,8 @@ export class TeamAwareMcpHandler {
141141
return { tools: [] };
142142
}
143143

144-
// Get all cached tools from tool discovery
145-
const allCachedTools = this.toolDiscoveryManager.getCachedTools();
144+
// Get all cached tools from tool discovery (both stdio and remote)
145+
const allCachedTools = this.toolDiscoveryManager.getAllTools();
146146

147147
// Get team's allowed MCP servers from configuration
148148
const teamAllowedServers = this.getTeamAllowedServers(teamId);
@@ -207,11 +207,11 @@ export class TeamAwareMcpHandler {
207207
}, `Team-aware tool call: ${namespacedToolName} for team ${teamId}`);
208208

209209
// Find the cached tool to get the actual server name and verify team access
210-
const allCachedTools = this.toolDiscoveryManager.getCachedTools();
211-
const cachedTool = allCachedTools.find(tool => tool.namespacedName === namespacedToolName);
210+
const cachedTool = this.toolDiscoveryManager.getTool(namespacedToolName);
212211

213212
if (!cachedTool) {
214-
throw new Error(`Tool not found: ${namespacedToolName}. Available tools: ${allCachedTools.map(t => t.namespacedName).join(', ')}`);
213+
const allTools = this.toolDiscoveryManager.getAllTools();
214+
throw new Error(`Tool not found: ${namespacedToolName}. Available tools: ${allTools.map(t => t.namespacedName).join(', ')}`);
215215
}
216216

217217
// Use the actual server name from the cached tool (e.g., "context7-john-R36no6FGoMFEZO9nWJJLT")
@@ -362,7 +362,7 @@ export class TeamAwareMcpHandler {
362362
let teamStats = {};
363363
if (teamId) {
364364
const teamAllowedServers = this.getTeamAllowedServers(teamId);
365-
const allCachedTools = this.toolDiscoveryManager.getCachedTools();
365+
const allCachedTools = this.toolDiscoveryManager.getAllTools();
366366
const teamFilteredTools = allCachedTools.filter(tool =>
367367
teamAllowedServers.includes(tool.serverName)
368368
);

0 commit comments

Comments
 (0)