Fine-grained control over tool execution.
type PermissionMode =
| 'default' // Prompt for dangerous operations
| 'acceptEdits' // Auto-accept file edits
| 'bypassPermissions' // Bypass all checks
| 'plan' // Planning mode, no execution
| 'dontAsk'; // Deny if not pre-approvedUsage:
{permissionMode: 'acceptEdits'}type CanUseTool = (
toolName: string,
input: Record<string, unknown>,
options: {
signal: AbortSignal;
suggestions?: PermissionUpdate[];
blockedPath?: string;
decisionReason?: string;
toolUseID: string;
agentID?: string;
}
) => Promise<PermissionResult>;
type PermissionResult =
| {behavior: 'allow'; updatedInput: Record<string, unknown>; updatedPermissions?: PermissionUpdate[]; toolUseID?: string}
| {behavior: 'deny'; message: string; interrupt?: boolean; toolUseID?: string};Example:
canUseTool: async (toolName, input, {decisionReason}) => {
// Block dangerous commands
if (toolName === 'Bash' && (input.command as string).includes('rm -rf')) {
return {
behavior: 'deny',
message: 'Dangerous command not allowed',
interrupt: true
};
}
// Block sensitive files
if ((toolName === 'Write' || toolName === 'Edit') &&
(input.file_path as string).includes('.env')) {
return {
behavior: 'deny',
message: 'Cannot modify .env files'
};
}
// Allow with optional updates
return {
behavior: 'allow',
updatedInput: input,
updatedPermissions: suggestions // Apply suggestions
};
}type PermissionUpdate =
| {type: 'addRules'; rules: PermissionRuleValue[]; behavior: PermissionBehavior; destination: PermissionUpdateDestination}
| {type: 'replaceRules'; rules: PermissionRuleValue[]; behavior: PermissionBehavior; destination: PermissionUpdateDestination}
| {type: 'removeRules'; rules: PermissionRuleValue[]; behavior: PermissionBehavior; destination: PermissionUpdateDestination}
| {type: 'setMode'; mode: PermissionMode; destination: PermissionUpdateDestination}
| {type: 'addDirectories'; directories: string[]; destination: PermissionUpdateDestination}
| {type: 'removeDirectories'; directories: string[]; destination: PermissionUpdateDestination};
interface PermissionRuleValue {
toolName: string;
ruleContent?: string;
}
type PermissionBehavior = 'allow' | 'deny' | 'ask';
type PermissionUpdateDestination = 'userSettings' | 'projectSettings' | 'localSettings' | 'session' | 'cliArg';Route prompts through custom MCP tool:
{
permissionPromptToolName: 'my-permission-handler',
mcpServers: {
'my-server': {
command: 'node',
args: ['./server.js']
}
}
}const allowedCommands = new Set(['ls', 'pwd', 'cat', 'grep']);
const result = query({
prompt: 'Task',
options: {
permissionMode: 'default',
canUseTool: async (toolName, input, {blockedPath}) => {
console.log(`Permission: ${toolName}`);
if (blockedPath) console.log(` Path: ${blockedPath}`);
// Bash whitelist
if (toolName === 'Bash') {
const cmd = (input.command as string).trim().split(' ')[0];
if (!allowedCommands.has(cmd)) {
return {
behavior: 'deny',
message: `Command "${cmd}" not allowed. Use: ${Array.from(allowedCommands).join(', ')}`
};
}
}
// File protection
if (toolName === 'Write' || toolName === 'Edit') {
const path = input.file_path as string;
if (path.includes('.env') || path.includes('secrets')) {
return {behavior: 'deny', message: 'Cannot modify sensitive files'};
}
if (path.startsWith('/etc/') || path.startsWith('/sys/')) {
return {behavior: 'deny', message: 'Cannot modify system files', interrupt: true};
}
}
return {behavior: 'allow', updatedInput: input};
}
}
});
for await (const msg of result) {
if (msg.type === 'result' && msg.permission_denials.length > 0) {
console.log('Denied operations:');
msg.permission_denials.forEach(d => {
console.log(` ${d.tool_name}:`, d.tool_input);
});
}
}interface SDKPermissionDenial {
tool_name: string;
tool_use_id: string;
tool_input: Record<string, unknown>;
}