Skip to content

Instantly share code, notes, and snippets.

@doobidoo
Last active January 15, 2026 19:00
Show Gist options
  • Select an option

  • Save doobidoo/fa84d31c0819a9faace345ca227b268f to your computer and use it in GitHub Desktop.

Select an option

Save doobidoo/fa84d31c0819a9faace345ca227b268f to your computer and use it in GitHub Desktop.
Universal Permission Request Hook for Claude Code - Auto-approve safe MCP tools (Now integrated in MCP Memory Service v8.73.0)

Universal Permission Request Hook for Claude Code

Auto-approves read-only MCP tools to eliminate constant permission dialogs.


🆕 Official Integration Update (January 2026)

This hook is now officially integrated into the MCP Memory Service project as of v8.73.0!

Recommended Installation: Use the Repository Version

For production use, we recommend installing from the official repository:

git clone https://github.com/doobidoo/mcp-memory-service.git
cd mcp-memory-service/claude-hooks
python install_hooks.py  # Installs all hooks including permission-request

Why We Recommend the Repository Version

The repository version offers significant advantages:

  1. Integrated Ecosystem - Works seamlessly with memory awareness hooks, natural triggers, and session management
  2. Unified Configuration - Single config.json for all Claude Code hooks with intelligent defaults
  3. Automated Installation - One command installs and configures everything correctly
  4. Version Control - Track updates alongside the MCP Memory Service
  5. Extensible Configuration - Add custom safe/destructive patterns via config without editing code
  6. Community Support - Issues, PRs, and discussions in active repository
  7. Production Tested - Tested with complete MCP Memory Service stack across platforms

Repository Links

When to Use This Standalone Gist

This gist remains useful for:

  • Quick evaluation - Test the hook concept without full repository setup
  • Standalone deployment - Use only the permission hook without memory service
  • Learning reference - Study the implementation in isolation
  • Custom modifications - Fork and modify for specific needs

Migration from Gist to Repository

If you installed from this gist, migrate to benefit from integrated features:

# Backup your custom configuration (if any)
cp ~/.claude/hooks/config.json ~/claude-hooks-config.backup.json

# Install from repository
git clone https://github.com/doobidoo/mcp-memory-service.git
cd mcp-memory-service/claude-hooks
python install_hooks.py

# Restore custom patterns if needed
# Edit ~/.claude/hooks/config.json and merge your customSafePatterns/customDestructivePatterns

Original Standalone Installation (Still Supported)

The Problem

Claude Code asks for permission on every MCP tool call - even harmless read operations like retrieve_memory, search_code, or list_memories. This breaks your flow when you're just retrieving context.

The Solution

A PermissionRequest hook with pattern-based auto-approval that:

  • ✅ Auto-approves safe read-only operations
  • ⚠️ Still prompts for destructive operations
  • 🌐 Works with ALL MCP servers (not just one)
  • 🔒 Safe-by-default: unknown patterns require confirmation

Installation

  1. Copy the hook:

    mkdir -p ~/.claude/hooks/core
    curl -o ~/.claude/hooks/core/permission-request.js https://gist.githubusercontent.com/doobidoo/fa84d31c0819a9faace345ca227b268f/raw/permission-request.js
    chmod +x ~/.claude/hooks/core/permission-request.js
  2. Enable in config: Add to ~/.claude/hooks/config.json:

    {
      "hooks": {
        "permissionRequest": {
          "enabled": true,
          "timeout": 2000,
          "priority": "high"
        }
      }
    }
  3. Restart Claude Code or reconnect MCP servers

How It Works

Auto-Approved Patterns (Safe)

  • get, list, read, retrieve, fetch
  • search, find, query, recall
  • check, status, health, stats, analyze
  • view, show, describe, inspect

Blocked Patterns (Require Confirmation)

  • delete, remove, destroy, drop, clear, wipe, purge, forget, erase
  • reset, update, modify, edit, change
  • write, create, deploy, publish
  • execute, run, eval, consolidate

Examples

Auto-approved (no dialog):

  • mcp__memory__retrieve_memory → ✅ Allowed
  • mcp__shodh-cloudflare__recall → ✅ Allowed
  • mcp__code-context__search_code → ✅ Allowed

Requires confirmation (shows dialog):

  • mcp__memory__store_memory⚠️ Prompt
  • mcp__memory__delete_memory⚠️ Prompt
  • mcp__memory__update_memory⚠️ Prompt

Technical Details

  • Language: Node.js
  • Input: JSON via stdin (from Claude Code)
  • Output: Decision via stdout (allow, prompt, or deny)
  • Pattern Matching: Regex-based tool name extraction
  • Safe-by-default: Unknown patterns require confirmation

Tool Name Extraction

Strips MCP prefix automatically:

mcp__memory__retrieve_memory → retrieve_memory
mcp__shodh-cloudflare__recall → recall

Then checks against safe/destructive patterns.

Configuration

The hook uses two pattern lists (configurable in the source):

const DESTRUCTIVE_PATTERNS = [
    'delete', 'remove', 'update', 'write', 'create', ...
];

const SAFE_PATTERNS = [
    'get', 'list', 'read', 'retrieve', 'search', ...
];

Customize these arrays to match your security requirements.

Compatibility

  • Claude Code: All versions with hook support
  • MCP Servers: Universal (works with any MCP server)
  • OS: macOS, Linux, Windows (with Node.js)

Security Considerations

This hook uses a safe-by-default approach:

  1. First checks for destructive patterns → blocks if found
  2. Then checks for safe patterns → allows if found
  3. Unknown patterns → prompts user (safer)

You can audit the allowed operations by reviewing the SAFE_PATTERNS list.

Troubleshooting

Hook not triggering:

  • Ensure chmod +x was run on the hook file
  • Check ~/.claude/hooks/config.json has "enabled": true
  • Restart Claude Code after installation
  • Check hook logs: tail -f ~/.claude/hooks/claude-hooks.log

Still getting prompts for safe tools:

  • Verify tool name matches a safe pattern
  • Check if tool name contains destructive pattern (takes precedence)
  • Enable debug logging to see decision reasoning

Hook failing silently:

  • Test manually: echo '{"tool_name":"test_retrieve","server_name":"test"}' | node ~/.claude/hooks/core/permission-request.js
  • Expected output: {"hookSpecificOutput":{...}}

Contributing

Found a tool that should be auto-approved? Submit an issue with:

  • Tool name
  • Server name
  • Why it's safe (read-only, no side effects)

License

MIT - Feel free to use, modify, and distribute.

Author

Henry Krupp - @y_c_t_y_e

Senior Technical Consultant | DevOps Engineer | AI Systems Developer

Related Projects


Star ⭐ this gist if it helped you!

#!/usr/bin/env node
/**
* Claude Code PermissionRequest Hook
* Auto-approves non-destructive MCP tools from all servers
*
* This hook intercepts permission requests and automatically approves
* read-only operations (tools with readOnlyHint or without destructiveHint),
* eliminating the need for manual user confirmation on safe operations.
*
* Updated: 2026-01-08 - Universal MCP server support
* Created: 2026-01-08
* Related: MCP Tool Annotations (readOnlyHint, destructiveHint)
*/
// Common destructive patterns to block (always require confirmation)
const DESTRUCTIVE_PATTERNS = [
'delete',
'remove',
'destroy',
'drop',
'clear',
'wipe',
'purge',
'forget',
'erase',
'reset',
'update', // Can be destructive
'modify',
'edit',
'change',
'write', // Can overwrite
'create', // Can create unwanted resources
'deploy',
'publish',
'execute', // Code execution can be dangerous
'run',
'eval',
'consolidate' // Modifies memories
];
// Safe read-only patterns (can be auto-approved)
const SAFE_PATTERNS = [
'get',
'list',
'read',
'retrieve',
'fetch',
'search',
'find',
'query',
'recall',
'check',
'status',
'health',
'stats',
'analyze',
'view',
'show',
'describe',
'inspect'
];
/**
* Main hook entry point
* Receives JSON via stdin, processes permission request, returns decision
*/
async function main() {
try {
// Read stdin input
const input = await readStdin();
const payload = JSON.parse(input);
// Check if this is an MCP tool call
if (isMCPToolCall(payload)) {
const toolName = extractToolName(payload);
// Check if tool is safe (non-destructive)
if (isSafeTool(toolName)) {
// Auto-approve safe tools
outputDecision('allow', {
reason: `Auto-approved safe tool: ${toolName}`,
auto_approved: true,
server: payload.server_name,
tool_name: toolName
});
} else {
// Require confirmation for potentially destructive tools
outputDecision('prompt');
}
} else {
// Not an MCP tool call, show normal dialog
outputDecision('prompt');
}
} catch (error) {
// On error, fall back to prompting user
console.error('[PermissionRequest Hook] Error:', error.message);
outputDecision('prompt');
}
}
/**
* Check if the payload represents an MCP tool call
*/
function isMCPToolCall(payload) {
return payload && (
payload.hook_event_name === 'PermissionRequest' ||
payload.type === 'mcp_tool_call' ||
(payload.tool_name && payload.server_name)
);
}
/**
* Extract clean tool name from payload (strip mcp__ prefix)
*/
function extractToolName(payload) {
let toolName = payload.tool_name || '';
// Strip mcp__servername__ prefix if present
// Examples: mcp__memory__retrieve_memory -> retrieve_memory
// mcp__shodh-cloudflare__recall -> recall
const mcpPrefix = /^mcp__[^_]+__/;
toolName = toolName.replace(mcpPrefix, '');
return toolName.toLowerCase();
}
/**
* Check if the tool is safe (non-destructive) based on naming patterns
*/
function isSafeTool(toolName) {
if (!toolName) {
return false;
}
// First check: Does the name contain any destructive pattern?
for (const pattern of DESTRUCTIVE_PATTERNS) {
if (toolName.includes(pattern)) {
return false; // Destructive - require confirmation
}
}
// Second check: Does the name match a safe pattern?
for (const pattern of SAFE_PATTERNS) {
if (toolName.includes(pattern)) {
return true; // Safe - auto-approve
}
}
// Unknown pattern - require confirmation (safer default)
return false;
}
/**
* Output the decision in Claude Code hook format
*/
function outputDecision(behavior, metadata = {}) {
const decision = {
hookSpecificOutput: {
hookEventName: 'PermissionRequest',
decision: {
behavior: behavior // 'allow', 'deny', or 'prompt'
}
}
};
// Add metadata if provided (for logging/debugging)
if (Object.keys(metadata).length > 0 && behavior !== 'prompt') {
decision.hookSpecificOutput.metadata = metadata;
}
console.log(JSON.stringify(decision));
}
/**
* Read all data from stdin
*/
function readStdin() {
return new Promise((resolve, reject) => {
let data = '';
process.stdin.setEncoding('utf8');
process.stdin.on('readable', () => {
let chunk;
while ((chunk = process.stdin.read()) !== null) {
data += chunk;
}
});
process.stdin.on('end', () => {
resolve(data);
});
process.stdin.on('error', (error) => {
reject(error);
});
// Timeout after 1 second
setTimeout(() => {
if (data.length === 0) {
reject(new Error('Timeout reading stdin'));
}
}, 1000);
});
}
// Run main
main().catch(error => {
console.error('[PermissionRequest Hook] Fatal error:', error);
outputDecision('prompt');
process.exit(1);
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment