-
-
Save Richard-Weiss/95f8bf90b55a3a41b4ae0ddd7a614942 to your computer and use it in GitHub Desktop.
Claude MCP auto approve
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Trusted servers, all tools are allowed by default | |
const trustedServers = [ | |
'file-system-windows-python', | |
]; | |
// Tools to explicitly allow | |
const trustedTools = [ | |
'google_search' | |
]; | |
// Tools to explictly block, useful with server combination | |
const blockedTools = [ | |
'write-file' | |
]; | |
// Cooldown tracking | |
let lastClickTime = 0; | |
const COOLDOWN_MS = 1000; // 1 second cooldown | |
// Log throttling | |
const logHistory = {}; | |
const LOG_THROTTLE_MS = 5000; // Only log the same message every 5 seconds | |
// Smart logging with throttling | |
function throttledLog(level, message, ...args) { | |
const key = message + JSON.stringify(args); | |
const now = Date.now(); | |
if (logHistory[key] && now - logHistory[key] < LOG_THROTTLE_MS) { | |
return; | |
} | |
logHistory[key] = now; | |
console[level](message, ...args); | |
} | |
const log = { | |
debug: (message, ...args) => throttledLog('debug', `[AutoApprove] ${message}`, ...args), | |
log: (message, ...args) => throttledLog('log', `[AutoApprove] ${message}`, ...args), | |
warn: (message, ...args) => throttledLog('warn', `[AutoApprove] ${message}`, ...args), | |
error: (message, ...args) => throttledLog('error', `[AutoApprove] ${message}`, ...args) | |
}; | |
const observer = new MutationObserver((mutations) => { | |
// Check if we're still in cooldown | |
const now = Date.now(); | |
if (now - lastClickTime < COOLDOWN_MS) { | |
log.debug('🕒 Still in cooldown period, skipping...'); | |
return; | |
} | |
// Find the dialog using its role attribute | |
const dialog = document.querySelector('[role="dialog"][data-state="open"]'); | |
if (!dialog) { | |
log.debug('Dialog not found'); | |
return; | |
} | |
log.debug('🔍 Dialog found, checking content...'); | |
// --- FIXED SELECTOR LOGIC --- | |
let toolName = null; | |
let serverName = null; | |
// Look for the specific element structure in the dialog | |
// First, find the flex column container that holds both tool and server info | |
const flexColumn = dialog.querySelector('.flex.flex-col.py-1'); | |
if (flexColumn) { | |
// Extract tool name (looking for the font-mono element within the flex column) | |
const toolElement = flexColumn.querySelector('.font-mono'); | |
if (toolElement && toolElement.textContent) { | |
toolName = toolElement.textContent.trim(); | |
log.log('🛠️ Tool name extracted:', toolName); | |
} else { | |
log.warn('⚠️ Tool name element not found or empty.'); | |
} | |
// Extract server name (looking for the font-medium element within the flex column) | |
const serverElement = flexColumn.querySelector('.font-medium'); | |
if (serverElement && serverElement.textContent) { | |
serverName = serverElement.textContent.trim(); | |
log.log('🌐 Server name extracted:', serverName); | |
} else { | |
log.warn('⚠️ Server name element not found or empty.'); | |
} | |
} else { | |
log.warn('⚠️ Could not find the flex column container for tool and server info.'); | |
} | |
// If neither name could be extracted, we can't make a decision. | |
if (!toolName && !serverName) { | |
log.error('❌ Both tool and server name extraction failed. Cannot proceed.'); | |
return; | |
} else if (!toolName) { | |
log.warn('⚠️ Tool name extraction failed, decision based on server name only.'); | |
} else if (!serverName) { | |
log.warn('⚠️ Server name extraction failed, decision based on tool name only.'); | |
} | |
// Decision logic (prioritizing server, then tool) | |
let shouldApprove = false; | |
if (serverName && trustedServers.includes(serverName)) { | |
// Server is trusted | |
if (toolName && blockedTools.includes(toolName)) { | |
log.log(`🚫 Tool '${toolName}' is explicitly blocked for trusted server '${serverName}'. Declining.`); | |
shouldApprove = false; | |
} else { | |
log.log(`✅ Server '${serverName}' is trusted. Approving (Tool: ${toolName || 'N/A'})`); | |
shouldApprove = true; | |
} | |
} else if (toolName && trustedTools.includes(toolName)) { | |
// Server isn't trusted (or not found), but tool is allowed | |
log.log(`✅ Tool '${toolName}' is explicitly allowed. Approving (Server: ${serverName || 'N/A'})`); | |
shouldApprove = true; | |
} else { | |
log.log(`❌ Approval criteria not met (Server: ${serverName || 'N/A'}, Tool: ${toolName || 'N/A'})`); | |
shouldApprove = false; | |
} | |
// Find and click "Allow always" button if approval is decided | |
if (shouldApprove) { | |
// Looking for the button with exact text "Allow always" | |
const buttons = Array.from(dialog.querySelectorAll('button')); | |
const allowAlwaysButton = buttons.find(button => | |
button.textContent && button.textContent.trim() === 'Allow always' | |
); | |
if (allowAlwaysButton) { | |
log.log('🚀 Auto-approving request...'); | |
lastClickTime = now; // Set cooldown *before* clicking | |
allowAlwaysButton.click(); | |
log.log('🖱️ Clicked "Allow always" button.'); | |
} else { | |
log.error("⚠️ Could not find the 'Allow always' button!"); | |
} | |
} else { | |
log.log("❌ Request not approved based on rules."); | |
} | |
}); | |
// Start observing the body for changes | |
console.log('[AutoApprove] 👀 Starting observer...'); | |
console.log('[AutoApprove] Trusted Servers:', trustedServers); | |
console.log('[AutoApprove] Trusted Tools:', trustedTools); | |
console.log('[AutoApprove] Blocked Tools:', blockedTools); | |
observer.observe(document.body, { | |
childList: true, | |
subtree: true | |
}); |
Great work, I've tried this for the past week but today it seems like it doesn't work anymore with today's new UI update?
@dinhphieu They've changed that whole approval dialog, I'll need some time to to update and test it, would ping you when it's working again.
@dinhphieu Should work again now, just gave Gemini 2.5 Pro the last script and new HTML and did some small manual adjustments and testing, Claude 3.7 Sonnet changed too much stuff for me one shot.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for the tool! It makes the inject even more convenient.