Files
Template-MCP/src/client.ts
T
Jonathan Miller 91b78f8da1 feat: initial MCP server template with placeholder-driven scaffolding
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>
2026-05-07 14:39:19 -05:00

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();
});
}
}