From cf00c4fd83626f87ecb37b3b4df2fe8394c5e9ea Mon Sep 17 00:00:00 2001 From: Marco Beretta <81851188+berry-13@users.noreply.github.com> Date: Fri, 3 Jul 2026 19:24:55 +0200 Subject: [PATCH] fix: hide plugin tools from the marketplace when the tools capability is off buildCatalog gated built-ins, MCP, and skills on their capabilities and permissions but pushed regular plugin tools unconditionally, so deployments that removed the tools capability still offered attachable tool cards in the marketplace. The loop now requires AgentCapabilities.tools, matching the old Add Tools gate. --- .../__tests__/ToolsMarketplaceDialog.spec.tsx | 2 +- .../Tools/items/__tests__/catalog.spec.ts | 25 ++++++++++++++----- .../SidePanel/Agents/Tools/items/catalog.ts | 22 ++++++++-------- 3 files changed, 32 insertions(+), 17 deletions(-) diff --git a/client/src/components/SidePanel/Agents/Tools/__tests__/ToolsMarketplaceDialog.spec.tsx b/client/src/components/SidePanel/Agents/Tools/__tests__/ToolsMarketplaceDialog.spec.tsx index f9a9aee91a..404963505e 100644 --- a/client/src/components/SidePanel/Agents/Tools/__tests__/ToolsMarketplaceDialog.spec.tsx +++ b/client/src/components/SidePanel/Agents/Tools/__tests__/ToolsMarketplaceDialog.spec.tsx @@ -29,7 +29,7 @@ jest.mock('react-hook-form', () => ({ jest.mock('~/Providers', () => ({ useAgentPanelContext: () => ({ - agentsConfig: { capabilities: ['execute_code'] }, + agentsConfig: { capabilities: ['execute_code', 'tools'] }, regularTools: [{ pluginKey: 'dalle', name: 'DALL-E', description: 'Images' }], mcpServersMap: new Map(), actions: [], diff --git a/client/src/components/SidePanel/Agents/Tools/items/__tests__/catalog.spec.ts b/client/src/components/SidePanel/Agents/Tools/items/__tests__/catalog.spec.ts index 05c123a9e9..91db59db56 100644 --- a/client/src/components/SidePanel/Agents/Tools/items/__tests__/catalog.spec.ts +++ b/client/src/components/SidePanel/Agents/Tools/items/__tests__/catalog.spec.ts @@ -12,6 +12,11 @@ const emptyInputs: BuildCatalogInputs = { permissions: { mcp: true, skills: true }, }; +const toolInputs: BuildCatalogInputs = { + ...emptyInputs, + agentsConfig: { capabilities: [AgentCapabilities.tools] }, +}; + describe('buildCatalog', () => { test('returns empty when nothing is enabled', () => { expect(buildCatalog(emptyInputs)).toEqual([]); @@ -103,18 +108,26 @@ describe('buildCatalog', () => { expect(skill?.name).toBe('Reviewer'); }); - test('emits tool items', () => { + test('emits tool items when the tools capability is enabled', () => { const items = buildCatalog({ - ...emptyInputs, + ...toolInputs, regularTools: [makePlugin({ pluginKey: 'dalle', name: 'DALL-E', description: 'Images' })], }); const tool = items.find((i) => i.kind === 'tool'); expect(tool?.id).toBe('dalle'); }); - test('marks an auth-requiring, unauthenticated tool as needs_setup', () => { + test('hides tool items when the admin disabled the tools capability', () => { const items = buildCatalog({ ...emptyInputs, + regularTools: [makePlugin({ pluginKey: 'dalle', name: 'DALL-E', description: 'Images' })], + }); + expect(items.find((i) => i.kind === 'tool')).toBeUndefined(); + }); + + test('marks an auth-requiring, unauthenticated tool as needs_setup', () => { + const items = buildCatalog({ + ...toolInputs, regularTools: [ makePlugin({ pluginKey: 'serpapi', @@ -131,7 +144,7 @@ describe('buildCatalog', () => { test('leaves status undefined for an authenticated auth-requiring tool', () => { const items = buildCatalog({ - ...emptyInputs, + ...toolInputs, regularTools: [ makePlugin({ pluginKey: 'serpapi', @@ -148,7 +161,7 @@ describe('buildCatalog', () => { test('leaves status undefined for a tool with no authConfig', () => { const items = buildCatalog({ - ...emptyInputs, + ...toolInputs, regularTools: [makePlugin({ pluginKey: 'dalle', name: 'DALL-E', description: 'Images' })], }); const tool = items.find((i) => i.kind === 'tool'); @@ -211,7 +224,7 @@ describe('buildCatalog', () => { map.set('srv', { serverName: 'srv', isConfigured: true, tools: [] }); const items = buildCatalog({ ...emptyInputs, - agentsConfig: { capabilities: [AgentCapabilities.execute_code] }, + agentsConfig: { capabilities: [AgentCapabilities.execute_code, AgentCapabilities.tools] }, regularTools: [makePlugin({ pluginKey: 't1', name: 'T1', description: '' })], mcpServersMap: map, skills: [makeSkill({ _id: 's1', name: 'S1', description: '' })], diff --git a/client/src/components/SidePanel/Agents/Tools/items/catalog.ts b/client/src/components/SidePanel/Agents/Tools/items/catalog.ts index 950e842a95..e471828146 100644 --- a/client/src/components/SidePanel/Agents/Tools/items/catalog.ts +++ b/client/src/components/SidePanel/Agents/Tools/items/catalog.ts @@ -148,16 +148,18 @@ export function buildCatalog(inputs: BuildCatalogInputs): AgentItem[] { } } - for (const plugin of inputs.regularTools) { - items.push({ - kind: 'tool', - id: plugin.pluginKey, - name: plugin.name ?? plugin.pluginKey, - description: plugin.description ?? '', - iconKey: 'tool', - plugin, - status: pluginNeedsAuth(plugin) ? 'needs_setup' : undefined, - }); + if (enabled.has(AgentCapabilities.tools)) { + for (const plugin of inputs.regularTools) { + items.push({ + kind: 'tool', + id: plugin.pluginKey, + name: plugin.name ?? plugin.pluginKey, + description: plugin.description ?? '', + iconKey: 'tool', + plugin, + status: pluginNeedsAuth(plugin) ? 'needs_setup' : undefined, + }); + } } if (inputs.permissions.skills) {