diff --git a/src/utils/tools-loader.ts b/src/utils/tools-loader.ts index ab61d055..954fcfa9 100644 --- a/src/utils/tools-loader.ts +++ b/src/utils/tools-loader.ts @@ -72,13 +72,6 @@ export async function loadToolsFromInput( const categoryTools = toolCategories[selector as ToolCategory]; - // Handler client capabilities logic for 'actors' category to swap call-actor for add-actor - // if client supports dynamic tools. - if (selector === 'actors' && doesMcpClientSupportDynamicTools(initializeRequestData)) { - internalSelections.push(...categoryTools.filter((t) => t.tool.name !== HelperTools.ACTOR_CALL)); - internalSelections.push(addTool); - continue; - } if (categoryTools) { internalSelections.push(...categoryTools); continue; @@ -115,7 +108,7 @@ export async function loadToolsFromInput( } // else: selectors provided but none are actors => do not load defaults // Compose final tool list - const result: ToolEntry[] = []; + let result: ToolEntry[] = []; // Internal tools if (selectorsProvided) { @@ -142,13 +135,23 @@ export async function loadToolsFromInput( * If there is any tool that in some way, even indirectly (like add-actor), allows calling * Actor, then we need to ensure the get-actor-output tool is available. */ - const hasCallActor = result.some((entry) => entry.tool.name === 'call-actor'); + const hasCallActor = result.some((entry) => entry.tool.name === HelperTools.ACTOR_CALL); const hasActorTools = result.some((entry) => entry.type === 'actor'); - const hasAddActorTool = result.some((entry) => entry.tool.name === 'add-actor'); + const hasAddActorTool = result.some((entry) => entry.tool.name === HelperTools.ACTOR_ADD); if (hasCallActor || hasActorTools || hasAddActorTool) { result.push(getActorOutput); } + // Handle client capabilities logic for 'actors' category to swap call-actor for add-actor + // if client supports dynamic tools. + const selectorContainsCallActor = selectors?.some((s) => s === HelperTools.ACTOR_CALL); + if (doesMcpClientSupportDynamicTools(initializeRequestData) && hasCallActor && !selectorContainsCallActor) { + // Remove call-actor + result = result.filter((entry) => entry.tool.name !== HelperTools.ACTOR_CALL); + // Replace with add-actor if not already present + if (!hasAddActorTool) result.push(addTool); + } + // De-duplicate by tool name for safety const seen = new Set(); const filtered = result.filter((entry) => !seen.has(entry.tool.name) && seen.add(entry.tool.name)); diff --git a/tests/integration/suite.ts b/tests/integration/suite.ts index 9955b1ce..fcf178be 100644 --- a/tests/integration/suite.ts +++ b/tests/integration/suite.ts @@ -989,6 +989,26 @@ export function createIntegrationTestsSuite( await client.close(); }); + it.runIf(options.transport === 'streamable-http')(`should swap call-actor for add-actor when client supports dynamic tools for default tools`, async () => { + client = await createClientFn({ clientName: 'Visual Studio Code' }); + const names = getToolNames(await client.listTools()); + + // should not contain call-actor but should contain add-actor + expect(names).not.toContain('call-actor'); + expect(names).toContain('add-actor'); + + await client.close(); + }); + it.runIf(options.transport === 'streamable-http')(`should NOT swap call-actor for add-actor when client supports dynamic tools when using the call-actor explicitly`, async () => { + client = await createClientFn({ clientName: 'Visual Studio Code', tools: ['call-actor'] }); + const names = getToolNames(await client.listTools()); + + // should not contain call-actor but should contain add-actor + expect(names).toContain('call-actor'); + expect(names).not.toContain('add-actor'); + + await client.close(); + }); it('should return error message when tryging to call MCP server Actor without tool name in actor parameter', async () => { client = await createClientFn({ tools: ['actors'] });