Template
91b78f8da1
Template repository for creating MokoStandards-compliant MCP servers.
Includes 4-file src/ structure (index, client, config, types), setup
wizard, example tools, 12 CI/CD workflows, full docs, and {{placeholder}}
tokens for search-and-replace customization.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
130 lines
4.2 KiB
TypeScript
130 lines
4.2 KiB
TypeScript
/* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
|
|
*
|
|
* This file is part of a Moko Consulting project.
|
|
*
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
*
|
|
* FILE INFORMATION
|
|
* DEFGROUP: {{PROJECT_NAME}}.Client
|
|
* INGROUP: {{PROJECT_NAME}}
|
|
* REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}}
|
|
* PATH: /src/client.ts
|
|
* VERSION: 01.00.00
|
|
* BRIEF: HTTP client for {{DISPLAY_NAME}} API
|
|
*/
|
|
|
|
import * as https from 'node:https';
|
|
import * as http from 'node:http';
|
|
import type { ApiConnection, ApiResponse } from './types.js';
|
|
|
|
// ── Customize these ─────────────────────────────────────────────────────
|
|
// API path prefix appended to baseUrl (e.g. "/api/index.php", "/api/v1")
|
|
const API_PREFIX = '/api';
|
|
const TIMEOUT_MS = 30_000;
|
|
// ────────────────────────────────────────────────────────────────────────
|
|
|
|
export class ApiClient {
|
|
private readonly base_url: string;
|
|
private readonly headers: Record<string, string>;
|
|
private readonly insecure: boolean;
|
|
|
|
constructor(conn: ApiConnection) {
|
|
this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
|
|
|
|
// ── Customize auth headers ──────────────────────────────────
|
|
// Examples:
|
|
// Bearer token: { 'Authorization': `Bearer ${conn.apiKey}` }
|
|
// API key header: { 'DOLAPIKEY': conn.apiKey }
|
|
// Basic auth: { 'Authorization': `Basic ${btoa(user + ':' + pass)}` }
|
|
this.headers = {
|
|
'Authorization': `Bearer ${conn.apiKey}`,
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
};
|
|
this.insecure = conn.insecure ?? false;
|
|
}
|
|
|
|
async get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse> {
|
|
return this.request(this.buildUrl(endpoint, params), 'GET');
|
|
}
|
|
|
|
async post(endpoint: string, body?: unknown): Promise<ApiResponse> {
|
|
return this.request(this.buildUrl(endpoint), 'POST', body);
|
|
}
|
|
|
|
async put(endpoint: string, body: unknown): Promise<ApiResponse> {
|
|
return this.request(this.buildUrl(endpoint), 'PUT', body);
|
|
}
|
|
|
|
async patch(endpoint: string, body: unknown): Promise<ApiResponse> {
|
|
return this.request(this.buildUrl(endpoint), 'PATCH', body);
|
|
}
|
|
|
|
async delete(endpoint: string): Promise<ApiResponse> {
|
|
return this.request(this.buildUrl(endpoint), 'DELETE');
|
|
}
|
|
|
|
private buildUrl(endpoint: string, params?: Record<string, string>): string {
|
|
const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
|
|
const url = new URL(`${this.base_url}${path}`);
|
|
if (params) {
|
|
for (const [key, value] of Object.entries(params)) {
|
|
url.searchParams.set(key, value);
|
|
}
|
|
}
|
|
return url.toString();
|
|
}
|
|
|
|
private request(url: string, method: string, body?: unknown): Promise<ApiResponse> {
|
|
return new Promise((resolve, reject) => {
|
|
const parsed = new URL(url);
|
|
const is_https = parsed.protocol === 'https:';
|
|
const transport = is_https ? https : http;
|
|
|
|
const options: https.RequestOptions = {
|
|
hostname: parsed.hostname,
|
|
port: parsed.port || (is_https ? 443 : 80),
|
|
path: parsed.pathname + parsed.search,
|
|
method,
|
|
headers: { ...this.headers },
|
|
timeout: TIMEOUT_MS,
|
|
};
|
|
|
|
if (this.insecure && is_https) {
|
|
options.rejectUnauthorized = false;
|
|
}
|
|
|
|
const payload = body !== undefined ? JSON.stringify(body) : undefined;
|
|
if (payload) {
|
|
(options.headers as Record<string, string>)['Content-Length'] = Buffer.byteLength(payload).toString();
|
|
}
|
|
|
|
const req = transport.request(options, (res) => {
|
|
const chunks: Buffer[] = [];
|
|
res.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
res.on('end', () => {
|
|
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
let data: unknown;
|
|
try {
|
|
data = JSON.parse(raw);
|
|
} catch {
|
|
data = raw;
|
|
}
|
|
resolve({ status: res.statusCode ?? 0, data });
|
|
});
|
|
});
|
|
|
|
req.on('error', (err) => reject(err));
|
|
req.on('timeout', () => {
|
|
req.destroy();
|
|
reject(new Error('Request timed out'));
|
|
});
|
|
|
|
if (payload) {
|
|
req.write(payload);
|
|
}
|
|
req.end();
|
|
});
|
|
}
|
|
}
|