import { UnauthorizedError } from '@modelcontextprotocol/sdk/client/auth.js'; import { describe, expect, it, vi } from 'vitest'; import type { ServerDefinition } from '../src/config.js'; import { isUnauthorizedError, maybeEnableOAuth } from '../src/runtime-oauth-support.js'; const logger = { info: vi.fn(), warn: vi.fn(), error: vi.fn(), }; describe('maybeEnableOAuth', () => { const baseDefinition: ServerDefinition = { name: 'adhoc-server', command: { kind: 'http', url: new URL('https://example.com/mcp') }, source: { kind: 'local', path: '' }, }; it('returns an updated definition for ad-hoc HTTP servers', () => { const updated = maybeEnableOAuth(baseDefinition, logger as never); expect(updated).toBeDefined(); expect(updated?.auth).toBe('oauth'); expect(updated?.tokenCacheDir).toBeUndefined(); expect(logger.info).toHaveBeenCalled(); }); it('enables OAuth for non-ad-hoc HTTP servers (issue #38)', () => { const def: ServerDefinition = { name: 'local-server', command: { kind: 'http', url: new URL('https://example.com') }, source: { kind: 'local', path: '/tmp/config.json' }, }; const updated = maybeEnableOAuth(def, logger as never); expect(updated).toBeDefined(); expect(updated?.auth).toBe('oauth'); }); it('does not mutate stdio servers', () => { const def: ServerDefinition = { name: 'stdio-server', command: { kind: 'stdio', command: 'echo', args: [], cwd: process.cwd() }, }; const updated = maybeEnableOAuth(def, logger as never); expect(updated).toBeUndefined(); }); it('does not re-promote servers already configured for oauth', () => { const def: ServerDefinition = { name: 'oauth-server', auth: 'oauth', command: { kind: 'http', url: new URL('https://example.com') }, }; const updated = maybeEnableOAuth(def, logger as never); expect(updated).toBeUndefined(); }); it('does not promote refreshable bearer servers to oauth', () => { const def: ServerDefinition = { name: 'refreshable-server', auth: 'refreshable_bearer', command: { kind: 'http', url: new URL('https://example.com') }, }; const updated = maybeEnableOAuth(def, logger as never); expect(updated).toBeUndefined(); }); it('promotes unsupported auth markers consistently with config normalization', () => { const def: ServerDefinition = { name: 'unsupported-auth-server', auth: 'bearer', command: { kind: 'http', url: new URL('https://example.com') }, }; const updated = maybeEnableOAuth(def, logger as never); expect(updated?.auth).toBe('oauth'); }); }); describe('isUnauthorizedError helper', () => { it('matches UnauthorizedError instances', () => { const err = new UnauthorizedError('Unauthorized'); expect(isUnauthorizedError(err)).toBe(true); }); it('matches generic errors with 401 codes', () => { expect(isUnauthorizedError(new Error('SSE error: Non-200 status code (401)'))).toBe(true); }); it('ignores unrelated errors', () => { expect(isUnauthorizedError(new Error('network timeout'))).toBe(false); }); it('matches errors with code=401 even when message lacks 401', () => { const err = Object.assign(new Error('Error POSTing to endpoint: {}'), { code: 401 }); expect(isUnauthorizedError(err)).toBe(true); }); });