diff --git a/apps/sim/app/api/permission-groups/[id]/route.ts b/apps/sim/app/api/permission-groups/[id]/route.ts index 16a18596e7..5e1486ff26 100644 --- a/apps/sim/app/api/permission-groups/[id]/route.ts +++ b/apps/sim/app/api/permission-groups/[id]/route.ts @@ -17,6 +17,14 @@ const configSchema = z.object({ allowedIntegrations: z.array(z.string()).nullable().optional(), allowedModelProviders: z.array(z.string()).nullable().optional(), hideTraceSpans: z.boolean().optional(), + hideKnowledgeBaseTab: z.boolean().optional(), + hideCopilot: z.boolean().optional(), + hideApiKeysTab: z.boolean().optional(), + hideEnvironmentTab: z.boolean().optional(), + hideFilesTab: z.boolean().optional(), + disableMcpTools: z.boolean().optional(), + disableCustomTools: z.boolean().optional(), + hideTemplates: z.boolean().optional(), }) const updateSchema = z.object({ diff --git a/apps/sim/app/api/permission-groups/route.ts b/apps/sim/app/api/permission-groups/route.ts index 1e513442a9..a3c3a7512b 100644 --- a/apps/sim/app/api/permission-groups/route.ts +++ b/apps/sim/app/api/permission-groups/route.ts @@ -18,6 +18,14 @@ const configSchema = z.object({ allowedIntegrations: z.array(z.string()).nullable().optional(), allowedModelProviders: z.array(z.string()).nullable().optional(), hideTraceSpans: z.boolean().optional(), + hideKnowledgeBaseTab: z.boolean().optional(), + hideCopilot: z.boolean().optional(), + hideApiKeysTab: z.boolean().optional(), + hideEnvironmentTab: z.boolean().optional(), + hideFilesTab: z.boolean().optional(), + disableMcpTools: z.boolean().optional(), + disableCustomTools: z.boolean().optional(), + hideTemplates: z.boolean().optional(), }) const createSchema = z.object({ diff --git a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx index 6be9f4ef93..442984fb3d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-modal/components/access-control/access-control.tsx @@ -112,7 +112,8 @@ export function AccessControl() { const [integrationSearchTerm, setIntegrationSearchTerm] = useState('') const allBlocks = useMemo(() => { - const blocks = getAllBlocks().filter((b) => !b.hideFromToolbar) + // Filter out hidden blocks and start_trigger (which should never be disabled) + const blocks = getAllBlocks().filter((b) => !b.hideFromToolbar && b.type !== 'start_trigger') return blocks.sort((a, b) => { // Group by category: triggers first, then blocks, then tools const categoryOrder = { triggers: 0, blocks: 1, tools: 2 } @@ -525,7 +526,7 @@ export function AccessControl() {
- +

- Select which integrations are visible in the toolbar. All are visible by - default. + Select which blocks are visible in the toolbar. All are visible by default.

setIntegrationSearchTerm(e.target.value)} className='h-auto flex-1 border-0 bg-transparent p-0 font-base text-[13px] leading-none placeholder:text-[var(--text-tertiary)] focus-visible:ring-0 focus-visible:ring-offset-0' @@ -633,7 +633,7 @@ export function AccessControl() {
{/* Sidebar */}
- + Sidebar
@@ -649,7 +649,7 @@ export function AccessControl() { /> @@ -666,7 +666,7 @@ export function AccessControl() { /> @@ -676,7 +676,7 @@ export function AccessControl() { {/* Workflow Panel */}
- + Workflow Panel
@@ -692,7 +692,7 @@ export function AccessControl() { /> @@ -702,7 +702,7 @@ export function AccessControl() { {/* Settings Tabs */}
- + Settings Tabs
@@ -718,7 +718,7 @@ export function AccessControl() { /> @@ -735,7 +735,7 @@ export function AccessControl() { /> @@ -752,7 +752,7 @@ export function AccessControl() { /> @@ -762,7 +762,7 @@ export function AccessControl() { {/* Tools */}
- + Tools
@@ -778,7 +778,7 @@ export function AccessControl() { /> @@ -795,7 +795,7 @@ export function AccessControl() { /> @@ -805,7 +805,7 @@ export function AccessControl() { {/* Logs */}
- + Logs
@@ -821,7 +821,7 @@ export function AccessControl() { /> diff --git a/apps/sim/executor/handlers/agent/agent-handler.ts b/apps/sim/executor/handlers/agent/agent-handler.ts index 206684b169..6640546f4a 100644 --- a/apps/sim/executor/handlers/agent/agent-handler.ts +++ b/apps/sim/executor/handlers/agent/agent-handler.ts @@ -25,7 +25,12 @@ import type { BlockHandler, ExecutionContext, StreamingExecution } from '@/execu import { collectBlockData } from '@/executor/utils/block-data' import { buildAPIUrl, buildAuthHeaders, extractAPIErrorMessage } from '@/executor/utils/http' import { stringifyJSON } from '@/executor/utils/json' -import { validateBlockType, validateModelProvider } from '@/executor/utils/permission-check' +import { + validateBlockType, + validateCustomToolsAllowed, + validateMcpToolsAllowed, + validateModelProvider, +} from '@/executor/utils/permission-check' import { executeProviderRequest } from '@/providers' import { getProviderFromModel, transformBlockTool } from '@/providers/utils' import type { SerializedBlock } from '@/serializer/types' @@ -51,6 +56,9 @@ export class AgentBlockHandler implements BlockHandler { const filteredTools = await this.filterUnavailableMcpTools(ctx, inputs.tools || []) const filteredInputs = { ...inputs, tools: filteredTools } + // Validate tool permissions before processing + await this.validateToolPermissions(ctx, filteredInputs.tools || []) + const responseFormat = this.parseResponseFormat(filteredInputs.responseFormat) const model = filteredInputs.model || AGENT.DEFAULT_MODEL @@ -147,6 +155,21 @@ export class AgentBlockHandler implements BlockHandler { return undefined } + private async validateToolPermissions(ctx: ExecutionContext, tools: ToolInput[]): Promise { + if (!Array.isArray(tools) || tools.length === 0) return + + const hasMcpTools = tools.some((t) => t.type === 'mcp') + const hasCustomTools = tools.some((t) => t.type === 'custom-tool') + + if (hasMcpTools) { + await validateMcpToolsAllowed(ctx.userId) + } + + if (hasCustomTools) { + await validateCustomToolsAllowed(ctx.userId) + } + } + private async filterUnavailableMcpTools( ctx: ExecutionContext, tools: ToolInput[] diff --git a/apps/sim/executor/utils/permission-check.ts b/apps/sim/executor/utils/permission-check.ts index 50eef90882..14205c1e26 100644 --- a/apps/sim/executor/utils/permission-check.ts +++ b/apps/sim/executor/utils/permission-check.ts @@ -26,6 +26,20 @@ export class IntegrationNotAllowedError extends Error { } } +export class McpToolsNotAllowedError extends Error { + constructor() { + super('MCP tools are not allowed based on your permission group settings') + this.name = 'McpToolsNotAllowedError' + } +} + +export class CustomToolsNotAllowedError extends Error { + constructor() { + super('Custom tools are not allowed based on your permission group settings') + this.name = 'CustomToolsNotAllowedError' + } +} + export async function getUserPermissionConfig( userId: string ): Promise { @@ -99,3 +113,37 @@ export async function validateBlockType( throw new IntegrationNotAllowedError(blockType) } } + +export async function validateMcpToolsAllowed(userId: string | undefined): Promise { + if (!userId) { + return + } + + const config = await getUserPermissionConfig(userId) + + if (!config) { + return + } + + if (config.disableMcpTools) { + logger.warn('MCP tools blocked by permission group', { userId }) + throw new McpToolsNotAllowedError() + } +} + +export async function validateCustomToolsAllowed(userId: string | undefined): Promise { + if (!userId) { + return + } + + const config = await getUserPermissionConfig(userId) + + if (!config) { + return + } + + if (config.disableCustomTools) { + logger.warn('Custom tools blocked by permission group', { userId }) + throw new CustomToolsNotAllowedError() + } +}