feat(core): require user consent before MCP server OAuth (#18132)

This commit is contained in:
Emily Hedlund
2026-02-03 16:26:00 -05:00
committed by GitHub
parent 1fc59484b1
commit 69f8273481
7 changed files with 255 additions and 138 deletions
+2 -48
View File
@@ -45,6 +45,7 @@ import {
exitAlternateScreen,
} from '../utils/terminal.js';
import { coreEvents, CoreEvent } from '../utils/events.js';
import { getConsentForOauth } from '../utils/authConsent.js';
export const authEvents = new EventEmitter();
@@ -269,7 +270,7 @@ async function initOauthClient(
await triggerPostAuthCallbacks(client.credentials);
} else {
const userConsent = await getConsentForOauth();
const userConsent = await getConsentForOauth('Code Assist login required.');
if (!userConsent) {
throw new FatalCancellationError('Authentication cancelled by user.');
}
@@ -377,53 +378,6 @@ async function initOauthClient(
return client;
}
export async function getConsentForOauth(): Promise<boolean> {
const prompt =
'Code Assist login required. Opening authentication page in your browser. ';
if (coreEvents.listenerCount(CoreEvent.ConsentRequest) === 0) {
if (!process.stdin.isTTY) {
throw new FatalAuthenticationError(
'Code Assist login required, but interactive consent could not be obtained.\n' +
'Please run Gemini CLI in an interactive terminal to authenticate, or use NO_BROWSER=true for manual authentication.',
);
}
return getOauthConsentNonInteractive(prompt);
}
return getOauthConsentInteractive(prompt);
}
async function getOauthConsentNonInteractive(prompt: string) {
const rl = readline.createInterface({
input: process.stdin,
output: createWorkingStdio().stdout,
terminal: true,
});
const fullPrompt = prompt + 'Do you want to continue? [Y/n]: ';
writeToStdout(`\n${fullPrompt}`);
return new Promise<boolean>((resolve) => {
rl.on('line', (answer) => {
rl.close();
resolve(['y', ''].includes(answer.trim().toLowerCase()));
});
});
}
async function getOauthConsentInteractive(prompt: string) {
const fullPrompt = prompt + '\n\nDo you want to continue?';
return new Promise<boolean>((resolve) => {
coreEvents.emitConsentRequest({
prompt: fullPrompt,
onConfirm: (confirmed: boolean) => {
resolve(confirmed);
},
});
});
}
export async function getOauthClient(
authType: AuthType,
config: Config,