@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
package/src/index.ts ADDED
@@ -0,0 +1,336 @@
1
+ #!/usr/bin/env node
2
+ /* Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ *
4
+ * This file is part of a Moko Consulting project.
5
+ *
6
+ * SPDX-License-Identifier: GPL-3.0-or-later
7
+ *
8
+ * FILE INFORMATION
9
+ * DEFGROUP: mcp-mokosuite.Server
10
+ * INGROUP: mcp-mokosuite
11
+ * REPO: https://git.mokoconsulting.tech/MokoConsulting/mcp-mokosuite
12
+ * PATH: /src/index.ts
13
+ * VERSION: 01.00.00
14
+ * BRIEF: MCP server entry point — registers MokoSuite (Layer 0 / Platform) tools
15
+ */
16
+
17
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
18
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
19
+ import { z } from 'zod';
20
+ import { loadConfig, getConnection } from './config.js';
21
+ import { ApiClient } from './client.js';
22
+ import { hasRemoteControl } from './signing.js';
23
+ import type { ApiConfig, ApiResponse } from './types.js';
24
+
25
+ /** Base path for MokoSuiteClient Web Services (appended after /api/index.php). */
26
+ const MSC = '/v1/mokosuiteclient';
27
+
28
+ let config: ApiConfig;
29
+
30
+ function clientFor(connection?: string): ApiClient {
31
+ return new ApiClient(getConnection(config, connection));
32
+ }
33
+
34
+ function formatResponse(res: ApiResponse): { content: Array<{ type: 'text'; text: string }> } {
35
+ if (res.status >= 400) {
36
+ return {
37
+ content: [{ type: 'text' as const, text: `Error: HTTP ${res.status}: ${JSON.stringify(res.data, null, 2)}` }],
38
+ };
39
+ }
40
+ return {
41
+ content: [{ type: 'text' as const, text: JSON.stringify(res.data, null, 2) }],
42
+ };
43
+ }
44
+
45
+ function text(message: string): { content: Array<{ type: 'text'; text: string }> } {
46
+ return { content: [{ type: 'text' as const, text: message }] };
47
+ }
48
+
49
+ // ── Shared parameter definitions ────────────────────────────────────────
50
+
51
+ const ConnectionParam = {
52
+ connection: z.string().optional().describe('Named MokoSuite site/connection from config (uses default if omitted)'),
53
+ };
54
+
55
+ const ConfirmParam = {
56
+ confirm: z.boolean().describe('Must be true — this operates on ALL users of the target site and is not reversible'),
57
+ };
58
+
59
+ function requireConfirm(confirm: boolean): { content: Array<{ type: 'text'; text: string }> } | null {
60
+ return confirm ? null : text('Refused: set `confirm: true` to run this fleet-wide destructive operation.');
61
+ }
62
+
63
+ // ── Server ──────────────────────────────────────────────────────────────
64
+
65
+ const server = new McpServer({
66
+ name: 'mcp-mokosuite',
67
+ version: '1.0.0',
68
+ });
69
+
70
+ // ════════════════════════════════════════════════════════════════════════
71
+ // FOUNDATION — connectivity & generic access
72
+ // ════════════════════════════════════════════════════════════════════════
73
+
74
+ server.tool(
75
+ 'mokosuite_ping',
76
+ 'Check reachability of a MokoSuite site (calls its dashboard endpoint and reports HTTP status).',
77
+ { ...ConnectionParam },
78
+ async ({ connection }) => {
79
+ const client = clientFor(connection);
80
+ try {
81
+ const res = await client.get(`${MSC}/dashboard`);
82
+ return text(`Reachable — HTTP ${res.status} from ${MSC}/dashboard`);
83
+ } catch (err) {
84
+ return text(`Unreachable: ${err instanceof Error ? err.message : String(err)}`);
85
+ }
86
+ },
87
+ );
88
+
89
+ server.tool(
90
+ 'list_connections',
91
+ 'List configured MokoSuite connections, their role, and which credential tiers are available.',
92
+ {},
93
+ async () => {
94
+ const lines = Object.entries(config.connections).map(([name, conn]) => {
95
+ const is_default = name === config.defaultConnection ? ' (default)' : '';
96
+ const role = conn.role ?? 'client';
97
+ const tiers = ['read/CRUD'];
98
+ if (hasRemoteControl(conn)) tiers.push('remote-control');
99
+ return ` ${name}${is_default} [${role}]: ${conn.baseUrl} — tiers: ${tiers.join(', ')}`;
100
+ });
101
+ return text(`Configured MokoSuite connections:\n${lines.join('\n')}`);
102
+ },
103
+ );
104
+
105
+ server.tool(
106
+ 'api_request',
107
+ 'Make a raw request to any MokoSuite/Joomla Web Services endpoint (escape hatch). Path is relative to /api/index.php.',
108
+ {
109
+ method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),
110
+ endpoint: z.string().describe('Path relative to /api/index.php (e.g. "/v1/mokosuiteclient/health")'),
111
+ body: z.record(z.string(), z.unknown()).optional().describe('Request body for POST/PUT/PATCH'),
112
+ params: z.record(z.string(), z.string()).optional().describe('Query parameters'),
113
+ ...ConnectionParam,
114
+ },
115
+ async ({ method, endpoint, body, params, connection }) => {
116
+ const client = clientFor(connection);
117
+ switch (method) {
118
+ case 'GET': return formatResponse(await client.get(endpoint, params));
119
+ case 'POST': return formatResponse(await client.post(endpoint, body));
120
+ case 'PUT': return formatResponse(await client.put(endpoint, body));
121
+ case 'PATCH': return formatResponse(await client.patch(endpoint, body));
122
+ case 'DELETE': return formatResponse(await client.delete(endpoint));
123
+ }
124
+ },
125
+ );
126
+
127
+ // ════════════════════════════════════════════════════════════════════════
128
+ // LAYER 0 — Platform (MokoSuiteClient) — READS (Joomla API token)
129
+ // ════════════════════════════════════════════════════════════════════════
130
+
131
+ server.tool(
132
+ 'client_get_health',
133
+ 'Get the full 16-check health diagnostics for a MokoSuite site (db, filesystem, cache, extensions, backup, security, ssl, cron, errors, content, users, mail, seo, template, config).',
134
+ { ...ConnectionParam },
135
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/health`)),
136
+ );
137
+
138
+ server.tool(
139
+ 'client_get_dashboard',
140
+ 'Get the summarized dashboard for a MokoSuite site (health summary, Joomla/PHP versions, plugin states).',
141
+ { ...ConnectionParam },
142
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/dashboard`)),
143
+ );
144
+
145
+ server.tool(
146
+ 'client_list_extensions',
147
+ 'List installed extensions on a MokoSuite site, with versions and available update info.',
148
+ { ...ConnectionParam },
149
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/extensions`)),
150
+ );
151
+
152
+ server.tool(
153
+ 'client_list_plugins',
154
+ 'List the MokoSuite feature plugins on a site and their enabled/disabled state.',
155
+ { ...ConnectionParam },
156
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/plugins`)),
157
+ );
158
+
159
+ server.tool(
160
+ 'client_list_snapshots',
161
+ 'List the baseline snapshots available on a MokoSuite site.',
162
+ { ...ConnectionParam },
163
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/snapshot`)),
164
+ );
165
+
166
+ server.tool(
167
+ 'client_list_users',
168
+ 'List Joomla users on a MokoSuite site.',
169
+ { ...ConnectionParam },
170
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/users`)),
171
+ );
172
+
173
+ server.tool(
174
+ 'client_export_users',
175
+ 'Export the users of a MokoSuite site.',
176
+ { ...ConnectionParam },
177
+ async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/users/export`)),
178
+ );
179
+
180
+ // ════════════════════════════════════════════════════════════════════════
181
+ // LAYER 0 — Platform — ACTIONS (Joomla API token, core.manage)
182
+ // ════════════════════════════════════════════════════════════════════════
183
+
184
+ server.tool(
185
+ 'client_clear_cache',
186
+ 'Clear all Joomla caches and opcache on a MokoSuite site.',
187
+ { ...ConnectionParam },
188
+ async ({ connection }) => formatResponse(await clientFor(connection).post(`${MSC}/cache`)),
189
+ );
190
+
191
+ server.tool(
192
+ 'client_check_updates',
193
+ 'Run the Joomla update finder on a MokoSuite site and report the number of available updates.',
194
+ { ...ConnectionParam },
195
+ async ({ connection }) => formatResponse(await clientFor(connection).post(`${MSC}/update`)),
196
+ );
197
+
198
+ server.tool(
199
+ 'client_install_extension',
200
+ 'Install an extension on a MokoSuite site from a remote ZIP URL (64MB cap). Privileged — use trusted URLs only.',
201
+ {
202
+ url: z.string().describe('HTTPS URL of the extension ZIP to install'),
203
+ ...ConnectionParam,
204
+ },
205
+ async ({ url, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/install`, { url })),
206
+ );
207
+
208
+ server.tool(
209
+ 'client_toggle_plugin',
210
+ 'Enable or disable a MokoSuite feature plugin on a site by its extension id.',
211
+ {
212
+ extension_id: z.number().describe('Joomla extension id of the plugin to toggle'),
213
+ ...ConnectionParam,
214
+ },
215
+ async ({ extension_id, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/plugins`, { task: 'toggle', extension_id })),
216
+ );
217
+
218
+ server.tool(
219
+ 'client_create_snapshot',
220
+ 'Create a baseline snapshot on a MokoSuite site.',
221
+ {
222
+ label: z.string().optional().describe('Optional label for the snapshot'),
223
+ ...ConnectionParam,
224
+ },
225
+ async ({ label, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/snapshot`, label ? { label } : {})),
226
+ );
227
+
228
+ server.tool(
229
+ 'client_reset_snapshot',
230
+ 'Restore a named baseline snapshot on a MokoSuite site.',
231
+ {
232
+ name: z.string().describe('Name of the baseline snapshot to restore'),
233
+ ...ConnectionParam,
234
+ },
235
+ async ({ name, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/reset`, { name })),
236
+ );
237
+
238
+ server.tool(
239
+ 'client_sync_push',
240
+ 'Push a content sync from this MokoSuite site to a target site.',
241
+ {
242
+ target: z.string().optional().describe('Target site identifier/URL for the sync (per site sync config)'),
243
+ ...ConnectionParam,
244
+ },
245
+ async ({ target, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/sync`, target ? { target } : {})),
246
+ );
247
+
248
+ // ════════════════════════════════════════════════════════════════════════
249
+ // LAYER 0 — Platform — REMOTE-CONTROL (RSA-signed; requires HQ credentials)
250
+ // ════════════════════════════════════════════════════════════════════════
251
+
252
+ server.tool(
253
+ 'client_provision_reset',
254
+ 'Reset a MokoSuite site for a new client (clears hits, purges version history, regenerates tokens, revokes user tokens). RSA-signed remote-control tier.',
255
+ { ...ConfirmParam, ...ConnectionParam },
256
+ async ({ confirm, connection }) => {
257
+ const guard = requireConfirm(confirm);
258
+ if (guard) return guard;
259
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/provision-reset`));
260
+ },
261
+ );
262
+
263
+ server.tool(
264
+ 'client_remote_login',
265
+ 'Get a one-time master login URL (60s TTL) for a MokoSuite site. RSA-signed remote-control tier.',
266
+ { ...ConnectionParam },
267
+ async ({ connection }) => formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/remote-login`)),
268
+ );
269
+
270
+ server.tool(
271
+ 'client_users_reset_passwords',
272
+ 'Reset passwords for ALL users on a MokoSuite site. RSA-signed remote-control tier.',
273
+ { ...ConfirmParam, ...ConnectionParam },
274
+ async ({ confirm, connection }) => {
275
+ const guard = requireConfirm(confirm);
276
+ if (guard) return guard;
277
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/reset-passwords`));
278
+ },
279
+ );
280
+
281
+ server.tool(
282
+ 'client_users_reset_2fa',
283
+ 'Reset two-factor authentication for ALL users on a MokoSuite site. RSA-signed remote-control tier.',
284
+ { ...ConfirmParam, ...ConnectionParam },
285
+ async ({ confirm, connection }) => {
286
+ const guard = requireConfirm(confirm);
287
+ if (guard) return guard;
288
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/reset-2fa`));
289
+ },
290
+ );
291
+
292
+ server.tool(
293
+ 'client_users_disable_all',
294
+ 'Block ALL users on a MokoSuite site. RSA-signed remote-control tier.',
295
+ { ...ConfirmParam, ...ConnectionParam },
296
+ async ({ confirm, connection }) => {
297
+ const guard = requireConfirm(confirm);
298
+ if (guard) return guard;
299
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/disable-all`));
300
+ },
301
+ );
302
+
303
+ server.tool(
304
+ 'client_users_enable_all',
305
+ 'Unblock ALL users on a MokoSuite site. RSA-signed remote-control tier.',
306
+ { ...ConfirmParam, ...ConnectionParam },
307
+ async ({ confirm, connection }) => {
308
+ const guard = requireConfirm(confirm);
309
+ if (guard) return guard;
310
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/enable-all`));
311
+ },
312
+ );
313
+
314
+ server.tool(
315
+ 'client_users_force_logout',
316
+ 'Terminate ALL user sessions on a MokoSuite site. RSA-signed remote-control tier.',
317
+ { ...ConfirmParam, ...ConnectionParam },
318
+ async ({ confirm, connection }) => {
319
+ const guard = requireConfirm(confirm);
320
+ if (guard) return guard;
321
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/force-logout`));
322
+ },
323
+ );
324
+
325
+ // ── Start Server ────────────────────────────────────────────────────────
326
+
327
+ async function main(): Promise<void> {
328
+ config = await loadConfig();
329
+ const transport = new StdioServerTransport();
330
+ await server.connect(transport);
331
+ }
332
+
333
+ main().catch((err) => {
334
+ process.stderr.write(`Fatal: ${err}\n`);
335
+ process.exit(1);
336
+ });
package/src/signing.ts ADDED
@@ -0,0 +1,67 @@
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
+
16
+ import { readFileSync } from 'node:fs';
17
+ import { createSign } from 'node:crypto';
18
+ import type { ApiConnection } from './types.js';
19
+
20
+ /**
21
+ * MokoSuiteClient's remote-control routes (provision-reset, remote-login, mass user ops)
22
+ * are `public` but require defence-in-depth headers that only MokoSuiteHQ can generate.
23
+ * HQ signs the payload `domain|timestamp|token` with its RSA-2048 private key (SHA-256);
24
+ * the client verifies against a baked-in public keyring within a 300-second freshness
25
+ * window. This reproduces that scheme so the MCP can act as HQ.
26
+ *
27
+ * @throws if the connection is missing the remote-control credentials or the key is unreadable.
28
+ */
29
+ export function buildRemoteControlHeaders(conn: ApiConnection): Record<string, string> {
30
+ if (!conn.healthApiToken) {
31
+ throw new Error('Remote-control requires "healthApiToken" in the connection config.');
32
+ }
33
+ if (!conn.rsaPrivateKeyPath) {
34
+ throw new Error('Remote-control requires "rsaPrivateKeyPath" (HQ RSA-2048 private key) in the connection config.');
35
+ }
36
+
37
+ const domain = new URL(conn.baseUrl).hostname;
38
+ const timestamp = Math.floor(Date.now() / 1000).toString();
39
+ const payload = `${domain}|${timestamp}|${conn.healthApiToken}`;
40
+
41
+ let privateKey: string;
42
+ try {
43
+ privateKey = readFileSync(conn.rsaPrivateKeyPath, 'utf-8');
44
+ } catch (err) {
45
+ const message = err instanceof Error ? err.message : String(err);
46
+ throw new Error(`Cannot read RSA private key at ${conn.rsaPrivateKeyPath}: ${message}`);
47
+ }
48
+
49
+ let signature: string;
50
+ try {
51
+ signature = createSign('RSA-SHA256').update(payload).sign(privateKey, 'base64');
52
+ } catch (err) {
53
+ const message = err instanceof Error ? err.message : String(err);
54
+ throw new Error(`Failed to RSA-sign remote-control payload: ${message}`);
55
+ }
56
+
57
+ return {
58
+ 'X-MokoSuite-Signature': signature,
59
+ 'X-MokoSuite-Timestamp': timestamp,
60
+ 'X-MokoSuite-Key-Version': String(conn.keyVersion ?? 1),
61
+ };
62
+ }
63
+
64
+ /** True when a connection carries the credentials needed for the remote-control tier. */
65
+ export function hasRemoteControl(conn: ApiConnection): boolean {
66
+ return Boolean(conn.healthApiToken && conn.rsaPrivateKeyPath);
67
+ }
package/src/types.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.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
+
16
+ /** A connection is either a MokoSuite client site or the MokoSuite HQ control plane. */
17
+ export type ConnectionRole = 'client' | 'hq';
18
+
19
+ /**
20
+ * Connection configuration for a single MokoSuite instance.
21
+ *
22
+ * MokoSuiteClient exposes two auth tiers on the same base URL:
23
+ * - **Reads / CRUD** — the site's Joomla Web Services Bearer token (`apiToken`),
24
+ * authorised against `core.manage`. This is all most tools need.
25
+ * - **Remote-control** (provision-reset, remote-login, mass user ops) — the routes are
26
+ * `public` and instead validate the site's `healthApiToken` plus an RSA signature that
27
+ * only MokoSuiteHQ can produce. Configure `healthApiToken` + `rsaPrivateKeyPath` to let
28
+ * this MCP act as HQ for those operations.
29
+ */
30
+ export interface ApiConnection {
31
+ /** Base URL of the site root (no trailing slash); "/api/index.php" is appended. */
32
+ baseUrl: string;
33
+ /** Joomla Web Services Bearer token — used for read/CRUD tools. */
34
+ apiToken: string;
35
+ /** Whether this connection is a client site (default) or the HQ control plane. */
36
+ role?: ConnectionRole;
37
+ /** Skip TLS certificate verification (self-signed certs). */
38
+ insecure?: boolean;
39
+
40
+ // ── Remote-control tier (optional — lets the MCP act as MokoSuiteHQ) ──────
41
+ /** Per-site 64-hex `health_api_token` required by the remote-control routes. */
42
+ healthApiToken?: string;
43
+ /** Path to HQ's RSA-2048 private key (PEM) used to sign remote-control requests. */
44
+ rsaPrivateKeyPath?: string;
45
+ /** Signing key version advertised to the client's public keyring (default 1). */
46
+ keyVersion?: number;
47
+ }
48
+
49
+ /**
50
+ * Top-level configuration supporting multiple named connections.
51
+ */
52
+ export interface ApiConfig {
53
+ connections: Record<string, ApiConnection>;
54
+ defaultConnection: string;
55
+ }
56
+
57
+ /**
58
+ * Normalized API response returned by the HTTP client.
59
+ */
60
+ export interface ApiResponse {
61
+ status: number;
62
+ data: unknown;
63
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "declaration": true,
14
+ "declarationMap": true,
15
+ "sourceMap": true
16
+ },
17
+ "include": ["src/**/*"],
18
+ "exclude": ["node_modules", "dist"]
19
+ }