@mokoconsulting/mcp-mokosuite 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/.editorconfig +19 -0
  2. package/.gitattributes +94 -0
  3. package/.gitmessage +9 -0
  4. package/.mokogit/ISSUE_TEMPLATE/adr.md +110 -0
  5. package/.mokogit/ISSUE_TEMPLATE/bug_report.md +48 -0
  6. package/.mokogit/ISSUE_TEMPLATE/config.yml +18 -0
  7. package/.mokogit/ISSUE_TEMPLATE/documentation.md +52 -0
  8. package/.mokogit/ISSUE_TEMPLATE/feature_request.md +51 -0
  9. package/.mokogit/ISSUE_TEMPLATE/mcp_api_integration.md +48 -0
  10. package/.mokogit/ISSUE_TEMPLATE/mcp_connection_issue.md +69 -0
  11. package/.mokogit/ISSUE_TEMPLATE/mcp_tool_request.md +50 -0
  12. package/.mokogit/ISSUE_TEMPLATE/question.md +82 -0
  13. package/.mokogit/ISSUE_TEMPLATE/rfc.md +126 -0
  14. package/.mokogit/ISSUE_TEMPLATE/security.md +51 -0
  15. package/.mokogit/ISSUE_TEMPLATE/version.md +24 -0
  16. package/.mokogit/actions/resolve-source-dir/action.yml +108 -0
  17. package/.mokogit/workflows/auto-assign.yml +76 -0
  18. package/.mokogit/workflows/auto-bump.yml +78 -0
  19. package/.mokogit/workflows/auto-dev-issue.yml +207 -0
  20. package/.mokogit/workflows/auto-release.yml +596 -0
  21. package/.mokogit/workflows/branch-cleanup.yml +60 -0
  22. package/.mokogit/workflows/cascade-dev.yml +198 -0
  23. package/.mokogit/workflows/changelog-validation.yml +101 -0
  24. package/.mokogit/workflows/ci-generic.yml +203 -0
  25. package/.mokogit/workflows/ci-issue-reporter.yml +75 -0
  26. package/.mokogit/workflows/cleanup.yml +87 -0
  27. package/.mokogit/workflows/gitleaks.yml +94 -0
  28. package/.mokogit/workflows/issue-branch.yml +81 -0
  29. package/.mokogit/workflows/notify.yml +73 -0
  30. package/.mokogit/workflows/npm-build-test.yml +83 -0
  31. package/.mokogit/workflows/npm-publish.yml +126 -0
  32. package/.mokogit/workflows/npm-sdk-check.yml +110 -0
  33. package/.mokogit/workflows/npm-tool-inventory.yml +80 -0
  34. package/.mokogit/workflows/pr-branch-check.yml +90 -0
  35. package/.mokogit/workflows/pr-check.yml +565 -0
  36. package/.mokogit/workflows/pre-release.yml +413 -0
  37. package/.mokogit/workflows/push-notify.yml +43 -0
  38. package/.mokogit/workflows/rc-revert.yml +72 -0
  39. package/.mokogit/workflows/repo-health.yml +700 -0
  40. package/.mokogit/workflows/repository-cleanup.yml +525 -0
  41. package/.mokogit/workflows/standards-compliance.yml +2507 -0
  42. package/.mokogit/workflows/sync-version-on-merge.yml +130 -0
  43. package/.mokogit/workflows/version-set.yml +131 -0
  44. package/CHANGELOG.md +18 -0
  45. package/CLAUDE.md +52 -0
  46. package/CODE_OF_CONDUCT.md +70 -0
  47. package/CONTRIBUTING.md +161 -0
  48. package/LICENSE +696 -0
  49. package/Makefile +70 -0
  50. package/README.md +83 -0
  51. package/SECURITY.md +34 -0
  52. package/config.example.json +21 -0
  53. package/dist/client.d.ts +22 -0
  54. package/dist/client.js +124 -0
  55. package/dist/config.d.ts +4 -0
  56. package/dist/config.js +53 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +181 -0
  59. package/dist/signing.d.ts +14 -0
  60. package/dist/signing.js +62 -0
  61. package/dist/types.d.ts +44 -0
  62. package/dist/types.js +16 -0
  63. package/docs/API.md +63 -0
  64. package/docs/ARCHITECTURE.md +73 -0
  65. package/docs/INSTALLATION.md +102 -0
  66. package/docs/index.md +12 -0
  67. package/package.json +35 -0
  68. package/src/client.ts +142 -0
  69. package/src/config.ts +63 -0
  70. package/src/index.ts +336 -0
  71. package/src/signing.ts +67 -0
  72. package/src/types.ts +63 -0
  73. package/tsconfig.json +19 -0
@@ -0,0 +1,62 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ *
3
+ * This file is part of a Moko Consulting project.
4
+ *
5
+ * SPDX-License-Identifier: GPL-3.0-or-later
6
+ *
7
+ * FILE INFORMATION
8
+ * DEFGROUP: mcp-mokosuite.Signing
9
+ * INGROUP: mcp-mokosuite
10
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite
11
+ * PATH: /src/signing.ts
12
+ * VERSION: 01.00.00
13
+ * BRIEF: RSA request-signing for the MokoSuiteClient remote-control tier
14
+ */
15
+ import { readFileSync } from 'node:fs';
16
+ import { createSign } from 'node:crypto';
17
+ /**
18
+ * MokoSuiteClient's remote-control routes (provision-reset, remote-login, mass user ops)
19
+ * are `public` but require defence-in-depth headers that only MokoSuiteHQ can generate.
20
+ * HQ signs the payload `domain|timestamp|token` with its RSA-2048 private key (SHA-256);
21
+ * the client verifies against a baked-in public keyring within a 300-second freshness
22
+ * window. This reproduces that scheme so the MCP can act as HQ.
23
+ *
24
+ * @throws if the connection is missing the remote-control credentials or the key is unreadable.
25
+ */
26
+ export function buildRemoteControlHeaders(conn) {
27
+ if (!conn.healthApiToken) {
28
+ throw new Error('Remote-control requires "healthApiToken" in the connection config.');
29
+ }
30
+ if (!conn.rsaPrivateKeyPath) {
31
+ throw new Error('Remote-control requires "rsaPrivateKeyPath" (HQ RSA-2048 private key) in the connection config.');
32
+ }
33
+ const domain = new URL(conn.baseUrl).hostname;
34
+ const timestamp = Math.floor(Date.now() / 1000).toString();
35
+ const payload = `${domain}|${timestamp}|${conn.healthApiToken}`;
36
+ let privateKey;
37
+ try {
38
+ privateKey = readFileSync(conn.rsaPrivateKeyPath, 'utf-8');
39
+ }
40
+ catch (err) {
41
+ const message = err instanceof Error ? err.message : String(err);
42
+ throw new Error(`Cannot read RSA private key at ${conn.rsaPrivateKeyPath}: ${message}`);
43
+ }
44
+ let signature;
45
+ try {
46
+ signature = createSign('RSA-SHA256').update(payload).sign(privateKey, 'base64');
47
+ }
48
+ catch (err) {
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ throw new Error(`Failed to RSA-sign remote-control payload: ${message}`);
51
+ }
52
+ return {
53
+ 'X-MokoSuite-Signature': signature,
54
+ 'X-MokoSuite-Timestamp': timestamp,
55
+ 'X-MokoSuite-Key-Version': String(conn.keyVersion ?? 1),
56
+ };
57
+ }
58
+ /** True when a connection carries the credentials needed for the remote-control tier. */
59
+ export function hasRemoteControl(conn) {
60
+ return Boolean(conn.healthApiToken && conn.rsaPrivateKeyPath);
61
+ }
62
+ //# sourceMappingURL=signing.js.map
@@ -0,0 +1,44 @@
1
+ /** A connection is either a MokoSuite client site or the MokoSuite HQ control plane. */
2
+ export type ConnectionRole = 'client' | 'hq';
3
+ /**
4
+ * Connection configuration for a single MokoSuite instance.
5
+ *
6
+ * MokoSuiteClient exposes two auth tiers on the same base URL:
7
+ * - **Reads / CRUD** — the site's Joomla Web Services Bearer token (`apiToken`),
8
+ * authorised against `core.manage`. This is all most tools need.
9
+ * - **Remote-control** (provision-reset, remote-login, mass user ops) — the routes are
10
+ * `public` and instead validate the site's `healthApiToken` plus an RSA signature that
11
+ * only MokoSuiteHQ can produce. Configure `healthApiToken` + `rsaPrivateKeyPath` to let
12
+ * this MCP act as HQ for those operations.
13
+ */
14
+ export interface ApiConnection {
15
+ /** Base URL of the site root (no trailing slash); "/api/index.php" is appended. */
16
+ baseUrl: string;
17
+ /** Joomla Web Services Bearer token — used for read/CRUD tools. */
18
+ apiToken: string;
19
+ /** Whether this connection is a client site (default) or the HQ control plane. */
20
+ role?: ConnectionRole;
21
+ /** Skip TLS certificate verification (self-signed certs). */
22
+ insecure?: boolean;
23
+ /** Per-site 64-hex `health_api_token` required by the remote-control routes. */
24
+ healthApiToken?: string;
25
+ /** Path to HQ's RSA-2048 private key (PEM) used to sign remote-control requests. */
26
+ rsaPrivateKeyPath?: string;
27
+ /** Signing key version advertised to the client's public keyring (default 1). */
28
+ keyVersion?: number;
29
+ }
30
+ /**
31
+ * Top-level configuration supporting multiple named connections.
32
+ */
33
+ export interface ApiConfig {
34
+ connections: Record<string, ApiConnection>;
35
+ defaultConnection: string;
36
+ }
37
+ /**
38
+ * Normalized API response returned by the HTTP client.
39
+ */
40
+ export interface ApiResponse {
41
+ status: number;
42
+ data: unknown;
43
+ }
44
+ //# sourceMappingURL=types.d.ts.map
package/dist/types.js ADDED
@@ -0,0 +1,16 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ *
3
+ * This file is part of a Moko Consulting project.
4
+ *
5
+ * SPDX-License-Identifier: GPL-3.0-or-later
6
+ *
7
+ * FILE INFORMATION
8
+ * DEFGROUP: mcp-mokosuite.Types
9
+ * INGROUP: mcp-mokosuite
10
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite
11
+ * PATH: /src/types.ts
12
+ * VERSION: 01.00.00
13
+ * BRIEF: TypeScript type definitions for MokoSuite MCP server
14
+ */
15
+ export {};
16
+ //# sourceMappingURL=types.js.map
package/docs/API.md ADDED
@@ -0,0 +1,63 @@
1
+ <!--
2
+ Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ # FILE INFORMATION
6
+ DEFGROUP: mcp-mokosuite.Documentation
7
+ PATH: /docs/API.md
8
+ VERSION: 01.00.00
9
+ BRIEF: MCP tool reference documentation
10
+ -->
11
+
12
+ # API Reference
13
+
14
+ All tools accept an optional `connection` parameter to target a specific named connection. If omitted, the default connection is used.
15
+
16
+ ## Example Resources
17
+
18
+ > Replace these example tools with your actual API tools.
19
+
20
+ ### `example_resources_list`
21
+ List resources with optional search.
22
+
23
+ | Parameter | Type | Required | Description |
24
+ |-----------|------|----------|-------------|
25
+ | `search` | string | No | Search query |
26
+ | `limit` | number | No | Max results |
27
+ | `page` | number | No | Page number (0-based) |
28
+
29
+ ### `example_resource_get`
30
+ Get a single resource by ID.
31
+
32
+ | Parameter | Type | Required | Description |
33
+ |-----------|------|----------|-------------|
34
+ | `id` | number | Yes | Resource ID |
35
+
36
+ ### `example_resource_create`
37
+ Create a new resource.
38
+
39
+ | Parameter | Type | Required | Description |
40
+ |-----------|------|----------|-------------|
41
+ | `name` | string | Yes | Resource name |
42
+ | `description` | string | No | Resource description |
43
+
44
+ ## Generic
45
+
46
+ ### `api_request`
47
+ Make a raw API request to any endpoint.
48
+
49
+ | Parameter | Type | Required | Description |
50
+ |-----------|------|----------|-------------|
51
+ | `method` | `"GET"` / `"POST"` / `"PUT"` / `"PATCH"` / `"DELETE"` | Yes | HTTP method |
52
+ | `endpoint` | string | Yes | API path |
53
+ | `body` | object | No | Request body |
54
+ | `params` | object | No | Query parameters |
55
+
56
+ ### `list_connections`
57
+ List all configured connections. No parameters.
58
+
59
+ ## Revision History
60
+
61
+ | Date | Version | Author | Notes |
62
+ | --- | --- | --- | --- |
63
+ | 2026-05-07 | 0.0.1 | jmiller | Initial template API reference |
@@ -0,0 +1,73 @@
1
+ <!--
2
+ Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ # FILE INFORMATION
6
+ DEFGROUP: mcp-mokosuite.Documentation
7
+ PATH: /docs/ARCHITECTURE.md
8
+ VERSION: 01.00.00
9
+ BRIEF: Architecture overview and design decisions
10
+ -->
11
+
12
+ # Architecture
13
+
14
+ ## Overview
15
+
16
+ mcp-mokosuite is a Model Context Protocol (MCP) server that bridges AI assistants with a REST API.
17
+
18
+ ```
19
+ AI Assistant <--> MCP (stdio) <--> ApiClient <--> REST API
20
+ ```
21
+
22
+ ## Components
23
+
24
+ ### `src/index.ts` — Server Entry Point
25
+
26
+ Registers all MCP tools with `McpServer` from `@modelcontextprotocol/sdk`. Each tool maps to one or more API endpoints. Uses Zod schemas for input validation.
27
+
28
+ Includes shared helpers:
29
+ - `formatResponse()` — normalizes error/success responses into MCP text content
30
+ - `paginationQuery()` — builds pagination query params
31
+ - `ConnectionParam` / `PaginationParams` — reusable Zod parameter spreads
32
+
33
+ ### `src/client.ts` — HTTP Client
34
+
35
+ The `ApiClient` class handles all HTTP communication:
36
+ - Uses `node:https` / `node:http` (not `fetch`) for reliable self-signed cert support
37
+ - Supports GET, POST, PUT, PATCH, DELETE
38
+ - JSON serialization/deserialization with error handling
39
+
40
+ ### `src/config.ts` — Configuration Loader
41
+
42
+ Loads connection details from `~/.<project>.json`. Supports multiple named connections with a configurable default.
43
+
44
+ ### `src/types.ts` — Type Definitions
45
+
46
+ TypeScript interfaces for `ApiConnection`, `ApiConfig`, and `ApiResponse`.
47
+
48
+ ### `scripts/setup.mjs` — Interactive Setup
49
+
50
+ Node.js script using `readline/promises` for interactive config creation.
51
+
52
+ ## Design Decisions
53
+
54
+ ### Why `node:https` instead of `fetch`?
55
+
56
+ Node.js 24's built-in `fetch` does not honor self-signed certificate bypass. The classic `node:https` module with `rejectUnauthorized: false` works reliably across all Node.js versions.
57
+
58
+ ### Why multiple named connections?
59
+
60
+ Multi-instance support is a core use case — managing staging, production, and dev environments from a single MCP server.
61
+
62
+ ## Data Flow
63
+
64
+ 1. AI assistant sends a tool call via MCP stdio transport
65
+ 2. `index.ts` validates parameters with Zod and resolves the connection
66
+ 3. `ApiClient` constructs the API URL, attaches auth headers, and makes the HTTP request
67
+ 4. Response is parsed as JSON and returned as MCP tool output
68
+
69
+ ## Revision History
70
+
71
+ | Date | Version | Author | Notes |
72
+ | --- | --- | --- | --- |
73
+ | 2026-05-07 | 0.0.1 | jmiller | Initial architecture document |
@@ -0,0 +1,102 @@
1
+ <!--
2
+ Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ # FILE INFORMATION
6
+ DEFGROUP: mcp-mokosuite.Documentation
7
+ PATH: /docs/INSTALLATION.md
8
+ VERSION: 01.00.00
9
+ BRIEF: Installation and setup instructions
10
+ -->
11
+
12
+ # Installation
13
+
14
+ ## Prerequisites
15
+
16
+ - **Node.js** 20.0.0 or later
17
+ - **npm** (included with Node.js)
18
+ - Access to the target API with valid credentials
19
+
20
+ ## Install
21
+
22
+ ```sh
23
+ git clone https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite.git
24
+ cd mcp-mokosuite
25
+ npm install
26
+ npm run build
27
+ npm run setup
28
+ ```
29
+
30
+ The setup wizard will prompt for:
31
+
32
+ 1. **Connection name** — a label for this instance (e.g. `production`, `staging`)
33
+ 2. **API URL** — the base URL of the instance
34
+ 3. **API key/token** — authentication credentials
35
+ 4. **TLS verification** — whether to skip certificate verification (for self-signed certs)
36
+
37
+ Run `npm run setup` again to add more connections.
38
+
39
+ ## Register with Claude Code
40
+
41
+ Add to your Claude Code MCP settings (`~/.claude.json` or project `.mcp.json`):
42
+
43
+ ```json
44
+ {
45
+ "mcpServers": {
46
+ "mcp-mokosuite": {
47
+ "type": "stdio",
48
+ "command": "node",
49
+ "args": ["/path/to/mcp-mokosuite/dist/index.js"]
50
+ }
51
+ }
52
+ }
53
+ ```
54
+
55
+ Restart Claude Code after adding the server.
56
+
57
+ ## Configuration File
58
+
59
+ The config is stored at `~/.mcp-mokosuite.json`:
60
+
61
+ ```json
62
+ {
63
+ "defaultConnection": "production",
64
+ "connections": {
65
+ "production": {
66
+ "baseUrl": "https://api.example.com",
67
+ "apiKey": "your-api-key"
68
+ },
69
+ "staging": {
70
+ "baseUrl": "https://api-staging.example.com",
71
+ "apiKey": "your-staging-key",
72
+ "insecure": true
73
+ }
74
+ }
75
+ }
76
+ ```
77
+
78
+ You can also set the `MOKOSUITE_CONFIG` environment variable to use a config file at a custom path.
79
+
80
+ ## Verification
81
+
82
+ ```sh
83
+ npm start
84
+ ```
85
+
86
+ If configured correctly, the server will start listening on stdio.
87
+
88
+ ## Troubleshooting
89
+
90
+ ### "Failed to load config" error
91
+ Run `npm run setup` to create the config file.
92
+
93
+ ### Connection errors
94
+ - Verify the API is reachable from your machine
95
+ - For self-signed certs, set `"insecure": true`
96
+ - Ensure the API key/token is valid
97
+
98
+ ## Revision History
99
+
100
+ | Date | Version | Author | Notes |
101
+ | --- | --- | --- | --- |
102
+ | 2026-05-07 | 0.0.1 | jmiller | Initial installation guide |
package/docs/index.md ADDED
@@ -0,0 +1,12 @@
1
+ <!-- Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ SPDX-License-Identifier: GPL-3.0-or-later -->
3
+
4
+ # mcp-mokosuite Documentation
5
+
6
+ See the [README](../README.md) for quick start, customization guide, and tool patterns.
7
+
8
+ ## Documents
9
+
10
+ - [INSTALLATION.md](./INSTALLATION.md) — Prerequisites, install, setup, troubleshooting
11
+ - [ARCHITECTURE.md](./ARCHITECTURE.md) — Component overview, design decisions, data flow
12
+ - [API.md](./API.md) — Full MCP tool reference with parameter tables
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "@mokoconsulting/mcp-mokosuite",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for MokoSuite (Joomla) REST API v1 operations",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "mokosuite-api-mcp": "dist/index.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "tsc --watch",
13
+ "start": "node dist/index.js",
14
+ "lint": "eslint src/",
15
+ "setup": "node scripts/setup.mjs",
16
+ "clean": "rm -rf dist/"
17
+ },
18
+ "dependencies": {
19
+ "@modelcontextprotocol/sdk": "^1.12.1",
20
+ "zod": "^3.24.4"
21
+ },
22
+ "devDependencies": {
23
+ "@types/node": "^22.15.3",
24
+ "typescript": "^5.8.3"
25
+ },
26
+ "engines": {
27
+ "node": ">=20.0.0"
28
+ },
29
+ "license": "GPL-3.0-or-later",
30
+ "author": "Moko Consulting <hello@mokoconsulting.tech>",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite.git"
34
+ }
35
+ }
package/src/client.ts ADDED
@@ -0,0 +1,142 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ *
3
+ * This file is part of a Moko Consulting project.
4
+ *
5
+ * SPDX-License-Identifier: GPL-3.0-or-later
6
+ *
7
+ * FILE INFORMATION
8
+ * DEFGROUP: mcp-mokosuite.Client
9
+ * INGROUP: mcp-mokosuite
10
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite
11
+ * PATH: /src/client.ts
12
+ * VERSION: 01.00.00
13
+ * BRIEF: HTTP client for the MokoSuite (Joomla Web Services) API
14
+ */
15
+
16
+ import * as https from 'node:https';
17
+ import * as http from 'node:http';
18
+ import type { ApiConnection, ApiResponse } from './types.js';
19
+ import { buildRemoteControlHeaders } from './signing.js';
20
+
21
+ // ── Endpoint conventions ────────────────────────────────────────────────
22
+ // Joomla Web Services live under /api/index.php; MokoSuiteClient routes are
23
+ // registered at /v1/mokosuiteclient/* (see MSC_BASE in index.ts).
24
+ const API_PREFIX = '/api/index.php';
25
+ const TIMEOUT_MS = 30_000;
26
+ // ────────────────────────────────────────────────────────────────────────
27
+
28
+ export class ApiClient {
29
+ private readonly conn: ApiConnection;
30
+ private readonly base_url: string;
31
+ private readonly headers: Record<string, string>;
32
+ private readonly insecure: boolean;
33
+
34
+ constructor(conn: ApiConnection) {
35
+ this.conn = conn;
36
+ this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
37
+
38
+ // Read/CRUD tier: Joomla Web Services authenticate via a Bearer API token.
39
+ this.headers = {
40
+ 'Authorization': `Bearer ${conn.apiToken}`,
41
+ 'Content-Type': 'application/json',
42
+ 'Accept': 'application/vnd.api+json',
43
+ };
44
+ this.insecure = conn.insecure ?? false;
45
+ }
46
+
47
+ async get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse> {
48
+ return this.request(this.buildUrl(endpoint, params), 'GET');
49
+ }
50
+
51
+ async post(endpoint: string, body?: unknown): Promise<ApiResponse> {
52
+ return this.request(this.buildUrl(endpoint), 'POST', body);
53
+ }
54
+
55
+ async put(endpoint: string, body: unknown): Promise<ApiResponse> {
56
+ return this.request(this.buildUrl(endpoint), 'PUT', body);
57
+ }
58
+
59
+ async patch(endpoint: string, body: unknown): Promise<ApiResponse> {
60
+ return this.request(this.buildUrl(endpoint), 'PATCH', body);
61
+ }
62
+
63
+ async delete(endpoint: string): Promise<ApiResponse> {
64
+ return this.request(this.buildUrl(endpoint), 'DELETE');
65
+ }
66
+
67
+ /**
68
+ * Remote-control tier: swap the auth to the site's `health_api_token` and attach the
69
+ * RSA signature headers HQ uses, then issue a GET or POST. Requires the connection to
70
+ * carry `healthApiToken` + `rsaPrivateKeyPath`.
71
+ */
72
+ async remoteControl(method: 'GET' | 'POST', endpoint: string, body?: unknown, params?: Record<string, string>): Promise<ApiResponse> {
73
+ const signed: Record<string, string> = {
74
+ ...buildRemoteControlHeaders(this.conn),
75
+ 'Authorization': `Bearer ${this.conn.healthApiToken}`,
76
+ };
77
+ return this.request(this.buildUrl(endpoint, params), method, body, signed);
78
+ }
79
+
80
+ private buildUrl(endpoint: string, params?: Record<string, string>): string {
81
+ const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
82
+ const url = new URL(`${this.base_url}${path}`);
83
+ if (params) {
84
+ for (const [key, value] of Object.entries(params)) {
85
+ url.searchParams.set(key, value);
86
+ }
87
+ }
88
+ return url.toString();
89
+ }
90
+
91
+ private request(url: string, method: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<ApiResponse> {
92
+ return new Promise((resolve, reject) => {
93
+ const parsed = new URL(url);
94
+ const is_https = parsed.protocol === 'https:';
95
+ const transport = is_https ? https : http;
96
+
97
+ const options: https.RequestOptions = {
98
+ hostname: parsed.hostname,
99
+ port: parsed.port || (is_https ? 443 : 80),
100
+ path: parsed.pathname + parsed.search,
101
+ method,
102
+ headers: { ...this.headers, ...(extraHeaders ?? {}) },
103
+ timeout: TIMEOUT_MS,
104
+ };
105
+
106
+ if (this.insecure && is_https) {
107
+ options.rejectUnauthorized = false;
108
+ }
109
+
110
+ const payload = body !== undefined ? JSON.stringify(body) : undefined;
111
+ if (payload) {
112
+ (options.headers as Record<string, string>)['Content-Length'] = Buffer.byteLength(payload).toString();
113
+ }
114
+
115
+ const req = transport.request(options, (res) => {
116
+ const chunks: Buffer[] = [];
117
+ res.on('data', (chunk: Buffer) => chunks.push(chunk));
118
+ res.on('end', () => {
119
+ const raw = Buffer.concat(chunks).toString('utf-8');
120
+ let data: unknown;
121
+ try {
122
+ data = JSON.parse(raw);
123
+ } catch {
124
+ data = raw;
125
+ }
126
+ resolve({ status: res.statusCode ?? 0, data });
127
+ });
128
+ });
129
+
130
+ req.on('error', (err) => reject(err));
131
+ req.on('timeout', () => {
132
+ req.destroy();
133
+ reject(new Error('Request timed out'));
134
+ });
135
+
136
+ if (payload) {
137
+ req.write(payload);
138
+ }
139
+ req.end();
140
+ });
141
+ }
142
+ }
package/src/config.ts ADDED
@@ -0,0 +1,63 @@
1
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
2
+ *
3
+ * This file is part of a Moko Consulting project.
4
+ *
5
+ * SPDX-License-Identifier: GPL-3.0-or-later
6
+ *
7
+ * FILE INFORMATION
8
+ * DEFGROUP: mcp-mokosuite.Config
9
+ * INGROUP: mcp-mokosuite
10
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite
11
+ * PATH: /src/config.ts
12
+ * VERSION: 01.00.00
13
+ * BRIEF: Configuration loader for MokoSuite MCP connections
14
+ */
15
+
16
+ import { readFile } from 'node:fs/promises';
17
+ import { resolve } from 'node:path';
18
+ import { homedir } from 'node:os';
19
+ import type { ApiConfig, ApiConnection } from './types.js';
20
+
21
+ // ── Customize this ──────────────────────────────────────────────────────
22
+ // Change the filename to match your project (e.g. ".dolibarr-api-mcp.json")
23
+ const CONFIG_FILENAME = '.mcp_mokosuite.json';
24
+ // Change the env var name to match your project
25
+ const CONFIG_ENV_VAR = 'MOKOSUITE_CONFIG';
26
+ // ────────────────────────────────────────────────────────────────────────
27
+
28
+ export async function loadConfig(): Promise<ApiConfig> {
29
+ const config_path = process.env[CONFIG_ENV_VAR]
30
+ ? resolve(process.env[CONFIG_ENV_VAR]!)
31
+ : resolve(homedir(), CONFIG_FILENAME);
32
+
33
+ try {
34
+ const raw = await readFile(config_path, 'utf-8');
35
+ const parsed = JSON.parse(raw) as Partial<ApiConfig>;
36
+
37
+ if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
38
+ throw new Error('No connections defined in config');
39
+ }
40
+
41
+ return {
42
+ connections: parsed.connections,
43
+ defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
44
+ };
45
+ } catch (err) {
46
+ const message = err instanceof Error ? err.message : String(err);
47
+ throw new Error(
48
+ `Failed to load config from ${config_path}: ${message}\n` +
49
+ `Create ${config_path} — see config.example.json for format.`,
50
+ );
51
+ }
52
+ }
53
+
54
+ export function getConnection(config: ApiConfig, name?: string): ApiConnection {
55
+ const key = name ?? config.defaultConnection;
56
+ const conn = config.connections[key];
57
+ if (!conn) {
58
+ throw new Error(
59
+ `Connection "${key}" not found. Available: ${Object.keys(config.connections).join(', ')}`,
60
+ );
61
+ }
62
+ return conn;
63
+ }