Files
mcpctl/src/cli/src/api-client.ts

105 lines
2.9 KiB
TypeScript
Raw Normal View History

import http from 'node:http';
import https from 'node:https';
export interface ApiClientOptions {
baseUrl: string;
timeout?: number | undefined;
token?: string | undefined;
}
export interface ApiResponse<T = unknown> {
status: number;
data: T;
}
export class ApiError extends Error {
constructor(
public readonly status: number,
public readonly body: string,
) {
super(`API error ${status}: ${body}`);
this.name = 'ApiError';
}
}
function request<T>(method: string, url: string, timeout: number, body?: unknown, token?: string): Promise<ApiResponse<T>> {
return new Promise((resolve, reject) => {
const parsed = new URL(url);
const headers: Record<string, string> = {};
if (body !== undefined) {
headers['Content-Type'] = 'application/json';
}
if (token) {
headers['Authorization'] = `Bearer ${token}`;
}
const isHttps = parsed.protocol === 'https:';
const opts: http.RequestOptions = {
hostname: parsed.hostname,
port: parsed.port || (isHttps ? 443 : 80),
path: parsed.pathname + parsed.search,
method,
timeout,
headers,
};
const driver = isHttps ? https : http;
const req = driver.request(opts, (res) => {
const chunks: Buffer[] = [];
res.on('data', (chunk: Buffer) => chunks.push(chunk));
res.on('end', () => {
const raw = Buffer.concat(chunks).toString('utf-8');
const status = res.statusCode ?? 0;
if (status >= 400) {
reject(new ApiError(status, raw));
return;
}
try {
resolve({ status, data: JSON.parse(raw) as T });
} catch {
resolve({ status, data: raw as unknown as T });
}
});
});
req.on('error', reject);
req.on('timeout', () => {
req.destroy();
reject(new Error(`Request to ${url} timed out`));
});
if (body !== undefined) {
req.write(JSON.stringify(body));
}
req.end();
});
}
export class ApiClient {
private baseUrl: string;
private timeout: number;
private token?: string | undefined;
constructor(opts: ApiClientOptions) {
this.baseUrl = opts.baseUrl.replace(/\/$/, '');
this.timeout = opts.timeout ?? 10000;
this.token = opts.token;
}
async get<T = unknown>(path: string): Promise<T> {
const res = await request<T>('GET', `${this.baseUrl}${path}`, this.timeout, undefined, this.token);
return res.data;
}
async post<T = unknown>(path: string, body?: unknown): Promise<T> {
const res = await request<T>('POST', `${this.baseUrl}${path}`, this.timeout, body, this.token);
return res.data;
}
async put<T = unknown>(path: string, body?: unknown): Promise<T> {
const res = await request<T>('PUT', `${this.baseUrl}${path}`, this.timeout, body, this.token);
return res.data;
}
async delete(path: string): Promise<void> {
await request('DELETE', `${this.baseUrl}${path}`, this.timeout, undefined, this.token);
}
}