export interface RedditBotApiClientOptions { baseUrl?: string; token?: string; fetchImplementation?: typeof fetch; } export class RedditBotApiError extends Error { constructor(message: string, readonly status: number) { super(message); this.name = 'RedditBotApiError'; } } export class RedditBotApiClient { private readonly baseUrl: string; private readonly token: string; private readonly fetchImplementation: typeof fetch; constructor(options: RedditBotApiClientOptions = {}) { this.baseUrl = (options.baseUrl ?? process.env.REDDIT_BOT_API_URL ?? 'http://127.0.0.1:3000').replace(/\/$/, ''); this.token = options.token ?? process.env.REDDIT_BOT_AGENT_TOKEN ?? process.env.REDDIT_BOT_AGENT_CONTROL_TOKEN ?? process.env.REDDIT_BOT_AGENT_READ_TOKEN ?? process.env.WEB_DASHBOARD_TOKEN ?? ''; this.fetchImplementation = options.fetchImplementation ?? fetch; if (!this.token) { throw new Error('MCP server requires REDDIT_BOT_AGENT_TOKEN or WEB_DASHBOARD_TOKEN'); } new URL(this.baseUrl); } get(path: string): Promise { return this.request(path); } post(path: string, body?: unknown): Promise { return this.request(path, { method: 'POST', ...(body === undefined ? {} : { headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }), }); } private async request(path: string, init: RequestInit = {}): Promise { const response = await this.fetchImplementation(`${this.baseUrl}${path}`, { ...init, headers: { Authorization: `Bearer ${this.token}`, Accept: 'application/json', ...init.headers, }, }); const text = await response.text(); let payload: unknown = null; if (text) { try { payload = JSON.parse(text) as unknown; } catch { payload = text; } } if (!response.ok) { const message = typeof payload === 'object' && payload !== null && 'error' in payload && typeof payload.error === 'string' ? payload.error : `RedditBot API request failed (${response.status})`; throw new RedditBotApiError(message, response.status); } return payload; } }