Skip to content

Commit 7adc7b4

Browse files
author
Lasim
committed
feat(satellite): implement disabled tool management and filtering
1 parent a4f8713 commit 7adc7b4

File tree

5 files changed

+315
-20
lines changed

5 files changed

+315
-20
lines changed

services/satellite/src/core/mcp-server-wrapper.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,10 +260,64 @@ export class McpServerWrapper {
260260
arguments: toolArguments
261261
}, `Tool arguments for ${toolPath}`);
262262

263+
// Check if tool exists and get cached tool info for disabled check
264+
const cachedTool = this.toolDiscoveryManager.getTool(namespacedToolName);
265+
if (!cachedTool) {
266+
const allTools = this.toolDiscoveryManager.getAllTools();
267+
throw new Error(`Tool not found: ${namespacedToolName}. Available tools: ${allTools.map(t => t.namespacedName).join(', ')}`);
268+
}
269+
270+
// Check if tool is disabled
271+
if (this.dynamicConfigManager) {
272+
const config = this.dynamicConfigManager.getMcpServerConfig(cachedTool.serverName);
273+
if (config?.installation_id) {
274+
const isDisabled = this.toolDiscoveryManager.isToolDisabled(
275+
config.installation_id,
276+
cachedTool.originalName
277+
);
278+
279+
if (isDisabled) {
280+
this.logger.warn({
281+
operation: 'execute_mcp_tool_disabled',
282+
tool_path: toolPath,
283+
installation_id: config.installation_id,
284+
tool_name: cachedTool.originalName
285+
}, `Tool execution blocked - tool is disabled: ${toolPath}`);
286+
287+
return this.createDisabledToolResponse(toolPath);
288+
}
289+
}
290+
}
291+
263292
// Route to executeToolCall with namespaced format
264293
return await this.executeToolCall(namespacedToolName, toolArguments);
265294
}
266295

296+
/**
297+
* Create LLM-friendly error response for disabled tools
298+
*/
299+
private createDisabledToolResponse(toolPath: string): any {
300+
const message = [
301+
`Tool '${toolPath}' has been disabled by the team administrator.`,
302+
'',
303+
'This tool is currently unavailable and cannot be executed.',
304+
'',
305+
'Recommended actions:',
306+
'1. Use discover_mcp_tools to find alternative tools that can accomplish your task',
307+
'2. Contact your team administrator if you need this tool enabled',
308+
'',
309+
`Disabled tool: ${toolPath}`
310+
].join('\n');
311+
312+
return {
313+
content: [{
314+
type: 'text',
315+
text: message
316+
}],
317+
isError: true
318+
};
319+
}
320+
267321
/**
268322
* Check for dormant stdio processes and respawn them before tool discovery
269323
*/

services/satellite/src/server.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -265,10 +265,11 @@ export async function createServer() {
265265

266266
// Initialize Tool Search Service for hierarchical router (discover_mcp_tools meta-tool)
267267
const toolSearchService = new ToolSearchService(toolDiscoveryManager, server.log);
268-
268+
toolSearchService.setConfigManager(dynamicConfigManager);
269+
269270
server.log.info({
270271
operation: 'tool_search_service_initialized'
271-
}, 'Tool Search Service initialized for hierarchical MCP router');
272+
}, 'Tool Search Service initialized for hierarchical MCP router (disabled tool filtering enabled)');
272273

273274
// Initialize MCP Server Wrapper with official SDK (replaces custom transport handlers)
274275
const mcpServerWrapper = new McpServerWrapper(server.log);
@@ -424,10 +425,14 @@ export async function createServer() {
424425
stdioToolDiscoveryManager
425426
);
426427

428+
// Set UnifiedToolDiscoveryManager for disabled tools tracking
429+
commandProcessor.setUnifiedToolDiscoveryManager(toolDiscoveryManager);
430+
427431
server.log.info({
428432
operation: 'command_processor_initialized',
429-
stdio_support: true
430-
}, 'Command Processor initialized with stdio process management support');
433+
stdio_support: true,
434+
disabled_tools_support: true
435+
}, 'Command Processor initialized with stdio process management and disabled tools support');
431436

432437
// Initialize Command Polling Service (will be started after registration)
433438
let commandPollingService: CommandPollingService | undefined;

services/satellite/src/services/command-processor.ts

Lines changed: 120 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import { DynamicConfigManager } from './dynamic-config-manager';
44
import { ProcessManager } from '../process/manager';
55
import { RuntimeState } from '../process/runtime-state';
66
import { StdioToolDiscoveryManager } from './stdio-tool-discovery-manager';
7+
import { UnifiedToolDiscoveryManager } from './unified-tool-discovery-manager';
78
import { MCPServerConfig } from '../process/types';
89
import { maskUrlForLogging } from '../utils/log-masker';
910

@@ -24,6 +25,7 @@ export class CommandProcessor {
2425
private processManager: ProcessManager | null;
2526
private runtimeState: RuntimeState | null;
2627
private stdioDiscoveryManager: StdioToolDiscoveryManager | null;
28+
private unifiedToolDiscoveryManager: UnifiedToolDiscoveryManager | null = null;
2729
private processes: Map<string, ProcessInfo> = new Map();
2830
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2931
private onConfigurationUpdate?: (config: any) => Promise<void>;
@@ -50,6 +52,13 @@ export class CommandProcessor {
5052
this.onConfigurationUpdate = handler;
5153
}
5254

55+
/**
56+
* Set unified tool discovery manager for disabled tools tracking
57+
*/
58+
setUnifiedToolDiscoveryManager(manager: UnifiedToolDiscoveryManager): void {
59+
this.unifiedToolDiscoveryManager = manager;
60+
}
61+
5362
/**
5463
* Resolve installation_id or server_name to actual server name
5564
*/
@@ -159,9 +168,17 @@ export class CommandProcessor {
159168
}
160169

161170
/**
162-
* Handle configure command - update MCP server configuration
171+
* Handle configure command - update MCP server configuration or handle specific actions
163172
*/
164173
private async handleConfigureCommand(command: SatelliteCommand): Promise<CommandResult> {
174+
const payload = command.payload;
175+
176+
// Check if this is an update_tool_status action
177+
if (payload.action === 'update_tool_status') {
178+
return await this.handleUpdateToolStatus(command);
179+
}
180+
181+
// Default behavior: trigger configuration refresh
165182
this.logger.info({
166183
operation: 'command_configure',
167184
command_id: command.id
@@ -174,7 +191,7 @@ export class CommandProcessor {
174191
operation: 'command_configure_trigger',
175192
command_id: command.id
176193
}, 'Triggering configuration update from backend');
177-
194+
178195
// This will fetch fresh config from backend and update all services
179196
await this.onConfigurationUpdate({});
180197
} else {
@@ -185,7 +202,7 @@ export class CommandProcessor {
185202
}
186203

187204
const currentStats = this.configManager.getStats();
188-
205+
189206
return {
190207
command_id: command.id,
191208
status: 'completed',
@@ -198,17 +215,115 @@ export class CommandProcessor {
198215
};
199216
} catch (error) {
200217
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
201-
218+
202219
this.logger.error({
203220
operation: 'command_configure_failed',
204221
command_id: command.id,
205222
error: errorMessage
206223
}, `Configure command failed: ${errorMessage}`);
207-
224+
208225
throw error; // Re-throw to be handled by main command processor
209226
}
210227
}
211228

229+
/**
230+
* Handle update_tool_status action - enable/disable a specific tool
231+
*/
232+
private async handleUpdateToolStatus(command: SatelliteCommand): Promise<CommandResult> {
233+
const payload = command.payload;
234+
const { installation_id, tool_name, is_disabled, team_id, server_slug } = payload;
235+
236+
this.logger.info({
237+
operation: 'update_tool_status',
238+
command_id: command.id,
239+
installation_id,
240+
tool_name,
241+
is_disabled,
242+
team_id,
243+
server_slug
244+
}, `Processing update_tool_status: ${is_disabled ? 'disabling' : 'enabling'} tool ${tool_name}`);
245+
246+
// Validate required fields
247+
if (!installation_id || !tool_name || typeof is_disabled !== 'boolean') {
248+
const errorMsg = 'Missing required fields: installation_id, tool_name, or is_disabled';
249+
this.logger.error({
250+
operation: 'update_tool_status_validation_failed',
251+
command_id: command.id,
252+
installation_id,
253+
tool_name,
254+
is_disabled
255+
}, errorMsg);
256+
257+
return {
258+
command_id: command.id,
259+
status: 'failed',
260+
error: errorMsg
261+
};
262+
}
263+
264+
// Check if UnifiedToolDiscoveryManager is available
265+
if (!this.unifiedToolDiscoveryManager) {
266+
const errorMsg = 'UnifiedToolDiscoveryManager not available - cannot update tool status';
267+
this.logger.error({
268+
operation: 'update_tool_status_no_manager',
269+
command_id: command.id
270+
}, errorMsg);
271+
272+
return {
273+
command_id: command.id,
274+
status: 'failed',
275+
error: errorMsg
276+
};
277+
}
278+
279+
try {
280+
// Update the tool status in the unified discovery manager
281+
this.unifiedToolDiscoveryManager.setToolDisabled(
282+
installation_id as string,
283+
tool_name as string,
284+
is_disabled as boolean
285+
);
286+
287+
const action = is_disabled ? 'disabled' : 'enabled';
288+
this.logger.info({
289+
operation: 'update_tool_status_success',
290+
command_id: command.id,
291+
installation_id,
292+
tool_name,
293+
is_disabled,
294+
team_id,
295+
server_slug
296+
}, `Tool ${tool_name} ${action} successfully`);
297+
298+
return {
299+
command_id: command.id,
300+
status: 'completed',
301+
result: {
302+
tool_name,
303+
is_disabled,
304+
message: `Tool ${action} successfully`
305+
}
306+
};
307+
308+
} catch (error) {
309+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
310+
311+
this.logger.error({
312+
operation: 'update_tool_status_failed',
313+
command_id: command.id,
314+
installation_id,
315+
tool_name,
316+
error: errorMessage
317+
}, `Failed to update tool status: ${errorMessage}`);
318+
319+
return {
320+
command_id: command.id,
321+
status: 'failed',
322+
error: errorMessage
323+
};
324+
}
325+
}
326+
212327
/**
213328
* Handle spawn command - dispatches to HTTP or stdio handler based on transport_type
214329
*/

0 commit comments

Comments
 (0)