diff --git a/src/__tests__/commands/add-mcp.test.ts b/src/__tests__/commands/add-mcp.test.ts index 78b42a0..023a1b4 100644 --- a/src/__tests__/commands/add-mcp.test.ts +++ b/src/__tests__/commands/add-mcp.test.ts @@ -55,7 +55,9 @@ describe('commands/add-mcp', ()=>{ let original_cwd = ''; let stdin_tty: PropertyDescriptor|undefined; let stdout_tty: PropertyDescriptor|undefined; - let original_api_key_env: string|undefined; + const key_env_vars = ['BRIGHTDATA_API_KEY', 'BRIGHTDATA_API_TOKEN', + 'API_TOKEN']; + let original_key_env: Record = {}; beforeEach(()=>{ vi.clearAllMocks(); @@ -72,10 +74,15 @@ describe('commands/add-mcp', ()=>{ process.chdir(project_dir); process.env['CODEX_HOME'] = codex_home; // Isolate from a real key in the environment: add-mcp now resolves the - // key via flag -> env -> stored credentials, so env must be cleared for - // the mocked credential to take effect. - original_api_key_env = process.env['BRIGHTDATA_API_KEY']; - delete process.env['BRIGHTDATA_API_KEY']; + // key via flag -> env -> stored credentials, so every env var that + // resolve_api_key reads (the documented BRIGHTDATA_API_KEY plus the + // BRIGHTDATA_API_TOKEN / API_TOKEN fallbacks) must be cleared for the + // mocked credential to take effect. + original_key_env = {}; + for (const name of key_env_vars) { + original_key_env[name] = process.env[name]; + delete process.env[name]; + } vi.spyOn(os, 'homedir').mockReturnValue(home_dir); Object.defineProperty(process.stdin, 'isTTY', { value: true, @@ -100,10 +107,12 @@ describe('commands/add-mcp', ()=>{ if (stdout_tty) Object.defineProperty(process.stdout, 'isTTY', stdout_tty); delete process.env['CODEX_HOME']; - if (original_api_key_env === undefined) - delete process.env['BRIGHTDATA_API_KEY']; - else - process.env['BRIGHTDATA_API_KEY'] = original_api_key_env; + for (const name of key_env_vars) { + if (original_key_env[name] === undefined) + delete process.env[name]; + else + process.env[name] = original_key_env[name]; + } vi.restoreAllMocks(); if (tmp_dir) fs.rmSync(tmp_dir, {recursive: true, force: true}); diff --git a/src/__tests__/utils/config.test.ts b/src/__tests__/utils/config.test.ts index 19fb980..8e9e3fb 100644 --- a/src/__tests__/utils/config.test.ts +++ b/src/__tests__/utils/config.test.ts @@ -26,6 +26,8 @@ describe('utils/config', ()=>{ fs.mkdirSync(tmp_home, {recursive: true}); process.env['HOME'] = tmp_home; delete process.env['BRIGHTDATA_API_KEY']; + delete process.env['BRIGHTDATA_API_TOKEN']; + delete process.env['API_TOKEN']; delete process.env['TEST_ZONE_ENV']; }); @@ -64,4 +66,31 @@ describe('utils/config', ()=>{ expect(resolve_api_key('from_cli_key')).toBe('from_cli_key'); expect(resolve_api_key(undefined)).toBe('from_env_key'); }); + + it('resolve_api_key falls back to BRIGHTDATA_API_TOKEN, then API_TOKEN', + ()=>{ + // no BRIGHTDATA_API_KEY set -> use the MCP/SDK token var + process.env['BRIGHTDATA_API_TOKEN'] = 'from_token'; + expect(resolve_api_key(undefined)).toBe('from_token'); + + // BRIGHTDATA_API_KEY still wins over the token fallback + process.env['BRIGHTDATA_API_KEY'] = 'from_key'; + expect(resolve_api_key(undefined)).toBe('from_key'); + + // API_TOKEN is the last resort + delete process.env['BRIGHTDATA_API_KEY']; + delete process.env['BRIGHTDATA_API_TOKEN']; + process.env['API_TOKEN'] = 'from_api_token'; + expect(resolve_api_key(undefined)).toBe('from_api_token'); + }); + + it('resolve_api_key ignores empty-string env vars', ()=>{ + process.env['BRIGHTDATA_API_KEY'] = ''; + process.env['BRIGHTDATA_API_TOKEN'] = 'real_token'; + expect(resolve_api_key(undefined)).toBe('real_token'); + }); + + it('resolve_api_key returns undefined when nothing is set', ()=>{ + expect(resolve_api_key(undefined)).toBeUndefined(); + }); }); diff --git a/src/utils/auth.ts b/src/utils/auth.ts index e7e915a..600c2fe 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -32,7 +32,8 @@ const ensure_authenticated = (cli_key: string|undefined): string=>{ return key; console.error( 'Error: No API key found.\n' - +' Run \'brightdata login\' or set BRIGHTDATA_API_KEY env variable.' + +' Run \'brightdata login\', or set BRIGHTDATA_API_KEY ' + +'(BRIGHTDATA_API_TOKEN / API_TOKEN are also accepted).' ); process.exit(1); }; diff --git a/src/utils/config.ts b/src/utils/config.ts index 0efa59b..0f0c07b 100644 --- a/src/utils/config.ts +++ b/src/utils/config.ts @@ -63,7 +63,14 @@ const resolve = ( const resolve_api_key = (cli_val: string|undefined): string|undefined=>{ if (cli_val) return cli_val; - const env_val = process.env['BRIGHTDATA_API_KEY']; + // BRIGHTDATA_API_KEY is the documented variable, but the Bright Data MCP + // and several reference SDKs export the same secret as BRIGHTDATA_API_TOKEN + // (and some setups use a bare API_TOKEN). Accept those as fallbacks so the + // CLI authenticates from the same environment as the rest of the toolchain + // instead of failing with "No API key found" on the first call. + const env_val = process.env['BRIGHTDATA_API_KEY'] + || process.env['BRIGHTDATA_API_TOKEN'] + || process.env['API_TOKEN']; if (env_val) return env_val; return undefined;