98 lines
3.3 KiB
TypeScript
98 lines
3.3 KiB
TypeScript
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: '<adhoc>' },
|
|
};
|
|
|
|
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);
|
|
});
|
|
});
|