export const operations = [ 'get_connection_status', 'get_profile_context', 'get_profile_template', 'get_taste_summary', 'search_taste', 'get_connections', 'get_boundaries', 'get_context_for_task', 'evaluate_candidates', 'get_recent_changes', 'search_catalog', 'get_fingerprint_update_status' ] as const export type Operation = typeof operations[number] export const requestOperations = [...operations, 'propose_fingerprint_update', 'propose_profile_update', 'suggest_map_update', 'save_fingerprint_update'] as const export type RequestOperation = typeof requestOperations[number] export type Credentials = { accessToken: string; tokenType: 'Bearer' | 'DPoP' } export type ProofRequest = { method: 'POST'; url: string; accessToken: string } export class FingerprintError extends Error { readonly status: number readonly wwwAuthenticate: string | null constructor(status: number, message: string, wwwAuthenticate: string | null = null) { super(message) this.name = 'FingerprintError' this.status = status this.wwwAuthenticate = wwwAuthenticate } } /** Node.js 22+. The caller owns OAuth, protected token storage, and DPoP signing. */ export function createFingerprintClient( baseUrl: string, getCredentials: () => Credentials | Promise, createProof?: (request: ProofRequest) => string | Promise ) { const base = new URL(baseUrl) if (base.username || base.password || base.search || base.hash || base.pathname !== '/' || (base.protocol !== 'https:' && !(base.protocol === 'http:' && ['localhost', '127.0.0.1', '[::1]'].includes(base.hostname)))) { throw new Error('Use an HTTPS origin, or an HTTP localhost origin for development.') } async function request(operation: RequestOperation, input: Record = {}): Promise> { if (!requestOperations.includes(operation)) throw new Error('Unknown operation.') if (!input || typeof input !== 'object' || Array.isArray(input)) throw new Error('Input must be an object.') const url = `${base.origin}/mcp/v1/${operation}` const credentials = await getCredentials() if (!credentials || !['Bearer', 'DPoP'].includes(credentials.tokenType) || !/^[A-Za-z0-9_-]{40,256}$/.test(credentials.accessToken)) throw new Error('Invalid credentials.') const headers: Record = { Accept: 'application/json', 'Content-Type': 'application/json', Authorization: `${credentials.tokenType} ${credentials.accessToken}` } if (credentials.tokenType === 'DPoP') { if (!createProof) throw new Error('A DPoP proof callback is required for bound credentials.') headers.DPoP = await createProof({ method: 'POST', url, accessToken: credentials.accessToken }) if (!headers.DPoP) throw new Error('The DPoP proof callback returned no proof.') } const response = await fetch(url, { method: 'POST', headers, body: JSON.stringify(input), redirect: 'error', credentials: 'omit', cache: 'no-store', signal: AbortSignal.timeout(30_000) }) const wwwAuthenticate = response.headers.get('www-authenticate') let data: unknown try { data = await response.json() } catch { throw new FingerprintError(response.status, 'The provider did not return JSON.', wwwAuthenticate) } if (!data || typeof data !== 'object' || Array.isArray(data)) { throw new FingerprintError(response.status, 'The provider did not return a JSON object.', wwwAuthenticate) } if (!response.ok) throw new FingerprintError(response.status, 'error' in data && typeof data.error === 'string' ? data.error : 'The provider rejected the request.', wwwAuthenticate) return data as Record } return { request, async read(operation: Operation, input: Record = {}): Promise> { if (!operations.includes(operation)) throw new Error('Unknown read operation.') return request(operation, input) } } }