@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/Makefile ADDED
@@ -0,0 +1,70 @@
1
+ # MCP Server Makefile
2
+ # Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
3
+ # SPDX-License-Identifier: GPL-3.0-or-later
4
+
5
+ PROJECT_NAME := mcp-mokosuite
6
+ PROJECT_VERSION := 1.0.0
7
+
8
+ NPM := npm
9
+
10
+ COLOR_RESET := \033[0m
11
+ COLOR_GREEN := \033[32m
12
+ COLOR_BLUE := \033[34m
13
+
14
+ .PHONY: help
15
+ help: ## Show this help message
16
+ @echo "$(COLOR_BLUE)$(PROJECT_NAME) v$(PROJECT_VERSION)$(COLOR_RESET)"
17
+ @echo ""
18
+ @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf " $(COLOR_BLUE)%-20s$(COLOR_RESET) %s\n", $$1, $$2}'
19
+ @echo ""
20
+
21
+ .PHONY: install-deps
22
+ install-deps: ## Install dependencies
23
+ @$(NPM) install
24
+ @echo "$(COLOR_GREEN)✓ Dependencies installed$(COLOR_RESET)"
25
+
26
+ MOKO_PLATFORM ?= $(or $(wildcard ../mokocli),$(wildcard $(HOME)/mokocli),$(wildcard /opt/mokocli))
27
+ MINIFY_SCRIPT := $(MOKO_PLATFORM)/build/minify.js
28
+
29
+ .PHONY: minify
30
+ minify: ## Minify CSS/JS assets
31
+ @echo "Minifying assets..."
32
+ @if [ -f "$(MINIFY_SCRIPT)" ]; then \
33
+ node "$(MINIFY_SCRIPT)" $(SRC_DIR); \
34
+ elif [ -f "scripts/minify.js" ]; then \
35
+ node scripts/minify.js; \
36
+ else \
37
+ echo "No minify script found"; \
38
+ fi
39
+
40
+ .PHONY: build
41
+ build: minify ## Build TypeScript
42
+ @$(NPM) run build
43
+ @echo "$(COLOR_GREEN)✓ Build complete$(COLOR_RESET)"
44
+
45
+ .PHONY: dev
46
+ dev: ## Watch and rebuild on changes
47
+ @$(NPM) run dev
48
+
49
+ .PHONY: clean
50
+ clean: ## Clean build artifacts
51
+ @rm -rf dist
52
+ @echo "$(COLOR_GREEN)✓ Cleaned$(COLOR_RESET)"
53
+
54
+ .PHONY: setup
55
+ setup: ## Run interactive setup wizard
56
+ @$(NPM) run setup
57
+
58
+ .PHONY: start
59
+ start: ## Start the MCP server
60
+ @$(NPM) run start
61
+
62
+ .PHONY: lint
63
+ lint: ## Run linter
64
+ @$(NPM) run lint
65
+
66
+ .PHONY: ci
67
+ ci: install-deps build ## Run CI pipeline
68
+ @echo "$(COLOR_GREEN)✓ CI pipeline complete$(COLOR_RESET)"
69
+
70
+ .DEFAULT_GOAL := help
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # mcp-mokosuite
2
+
3
+ ![Language](https://img.shields.io/badge/language-TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white) ![License](https://img.shields.io/badge/license-GPL--3.0--or--later-green?style=flat-square) ![Platform](https://img.shields.io/badge/platform-Node.js%20%7C%20MCP-339933?style=flat-square&logo=node.js&logoColor=white)
4
+
5
+ Model Context Protocol (MCP) server for the **MokoSuite** platform. It lets AI assistants like Claude operate MokoSuite client sites through the MokoSuiteClient Joomla Web Services API — health, dashboards, extensions, plugins, snapshots, cache/updates, content sync, and (via HQ credentials) provisioning, remote login, and fleet-wide user operations.
6
+
7
+ ## Features
8
+
9
+ - **24 tools** across the platform surface — reads, actions, and RSA-signed remote-control.
10
+ - **Multi-site** — every named connection is a MokoSuite site (or the HQ control plane); all tools accept an optional `connection` parameter.
11
+ - **Two credential tiers** — a Joomla Web Services Bearer token for read/CRUD, plus an optional `health_api_token` + HQ RSA private key for the remote-control tier.
12
+ - **Safety guards** — destructive fleet-wide operations require an explicit `confirm: true`.
13
+ - **TypeScript-first**, ESM, `node:https` transport (reliable self-signed cert support).
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install -g @mokoconsulting/mcp-mokosuite
19
+ ```
20
+
21
+ Or run on demand with `npx`:
22
+
23
+ ```bash
24
+ npx @mokoconsulting/mcp-mokosuite
25
+ ```
26
+
27
+ Create `~/.mcp_mokosuite.json` (or point `$MOKOSUITE_CONFIG` at a file) — see [`config.example.json`](config.example.json):
28
+
29
+ ```json
30
+ {
31
+ "defaultConnection": "moko-live",
32
+ "connections": {
33
+ "moko-live": { "baseUrl": "https://mokoconsulting.tech", "apiToken": "your-joomla-api-token" }
34
+ }
35
+ }
36
+ ```
37
+
38
+ - `apiToken` — a Joomla **Web Services** API token (Users → Manage → API Tokens) for the read/CRUD tools.
39
+ - `healthApiToken` + `rsaPrivateKeyPath` + `keyVersion` *(optional)* — the site's `health_api_token` and HQ's RSA-2048 signing key, required only for the remote-control tools (provision-reset, remote-login, mass user ops).
40
+
41
+ ## Usage
42
+
43
+ Register the server with your MCP client (e.g. Claude Code / Claude Desktop):
44
+
45
+ ```json
46
+ {
47
+ "mcpServers": {
48
+ "mokosuite": { "command": "npx", "args": ["-y", "@mokoconsulting/mcp-mokosuite"] }
49
+ }
50
+ }
51
+ ```
52
+
53
+ Then ask the assistant to use the tools, e.g. *"check the health of the moko-dev site"* → `client_get_health`. Representative tools:
54
+
55
+ | Tier | Tools |
56
+ |---|---|
57
+ | Foundation | `mokosuite_ping`, `list_connections`, `api_request` |
58
+ | Reads | `client_get_health`, `client_get_dashboard`, `client_list_extensions`, `client_list_plugins`, `client_list_snapshots`, `client_list_users`, `client_export_users` |
59
+ | Actions | `client_clear_cache`, `client_check_updates`, `client_install_extension`, `client_toggle_plugin`, `client_create_snapshot`, `client_reset_snapshot`, `client_sync_push` |
60
+ | Remote-control (RSA-signed) | `client_provision_reset`, `client_remote_login`, `client_users_reset_passwords`, `client_users_reset_2fa`, `client_users_disable_all`, `client_users_enable_all`, `client_users_force_logout` |
61
+
62
+ All tools target the default connection unless a `connection` argument names another site. Remote-control tools require the second credential tier and destructive ones require `confirm: true`.
63
+
64
+ ## Development
65
+
66
+ ```bash
67
+ npm install # install dependencies
68
+ npm run build # compile TypeScript to dist/
69
+ npm run dev # watch mode
70
+ npm start # run the compiled server
71
+ ```
72
+
73
+ ## Contributing
74
+
75
+ Contributions follow [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki/Home). See [CONTRIBUTING.md](CONTRIBUTING.md). Develop on feature branches, use `Authored-by: Moko Consulting` in commits, and open a PR to `main`.
76
+
77
+ ## License
78
+
79
+ Licensed under the GNU General Public License v3.0 or later — see [LICENSE](LICENSE).
80
+
81
+ ---
82
+
83
+ *[Moko Consulting](https://mokoconsulting.tech) — [MokoStandards](https://git.mokoconsulting.tech/MokoConsulting/MokoCLI/wiki/Home)*
package/SECURITY.md ADDED
@@ -0,0 +1,34 @@
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: /SECURITY.md
8
+ VERSION: 01.00.00
9
+ BRIEF: Security policy and vulnerability disclosure
10
+ -->
11
+
12
+ # Security Policy
13
+
14
+ ## Supported Versions
15
+
16
+ The latest published `@mokoconsulting/mcp-mokosuite` release receives security updates. Please upgrade before reporting issues against older versions.
17
+
18
+ ## Reporting a Vulnerability
19
+
20
+ Do **not** open a public issue for security vulnerabilities. Instead, email **[hello@mokoconsulting.tech](mailto:hello@mokoconsulting.tech)** with:
21
+
22
+ - a description of the vulnerability and its impact,
23
+ - steps to reproduce (proof of concept if possible),
24
+ - affected version(s).
25
+
26
+ You can expect an acknowledgement within 5 business days and a remediation plan once the report is triaged. Please allow a reasonable disclosure window before making details public.
27
+
28
+ ## Handling Credentials
29
+
30
+ This server connects to MokoSuite sites using Joomla Web Services API tokens and, for the remote-control tier, a `health_api_token` plus an RSA private key. Treat the config file (`~/.mcp_mokosuite.json`) and any key material as secrets:
31
+
32
+ - never commit tokens, `health_api_token`s, or private keys to source control;
33
+ - scope Joomla API tokens to the minimum required (`core.manage`);
34
+ - the RSA private key is HQ-only — keep it off client machines that only need read access.
@@ -0,0 +1,21 @@
1
+ {
2
+ "defaultConnection": "moko-live",
3
+ "connections": {
4
+ "moko-live": {
5
+ "baseUrl": "https://mokoconsulting.tech",
6
+ "apiToken": "your-joomla-api-token"
7
+ },
8
+ "cfurs": {
9
+ "baseUrl": "https://clarksvillefurs.com",
10
+ "apiToken": "your-joomla-api-token",
11
+ "healthApiToken": "site-health-api-token-64hex",
12
+ "rsaPrivateKeyPath": "/path/to/hq-signing-key.pem",
13
+ "keyVersion": 1
14
+ },
15
+ "local-dev": {
16
+ "baseUrl": "https://localhost",
17
+ "apiToken": "your-joomla-api-token",
18
+ "insecure": true
19
+ }
20
+ }
21
+ }
@@ -0,0 +1,22 @@
1
+ import type { ApiConnection, ApiResponse } from './types.js';
2
+ export declare class ApiClient {
3
+ private readonly conn;
4
+ private readonly base_url;
5
+ private readonly headers;
6
+ private readonly insecure;
7
+ constructor(conn: ApiConnection);
8
+ get(endpoint: string, params?: Record<string, string>): Promise<ApiResponse>;
9
+ post(endpoint: string, body?: unknown): Promise<ApiResponse>;
10
+ put(endpoint: string, body: unknown): Promise<ApiResponse>;
11
+ patch(endpoint: string, body: unknown): Promise<ApiResponse>;
12
+ delete(endpoint: string): Promise<ApiResponse>;
13
+ /**
14
+ * Remote-control tier: swap the auth to the site's `health_api_token` and attach the
15
+ * RSA signature headers HQ uses, then issue a GET or POST. Requires the connection to
16
+ * carry `healthApiToken` + `rsaPrivateKeyPath`.
17
+ */
18
+ remoteControl(method: 'GET' | 'POST', endpoint: string, body?: unknown, params?: Record<string, string>): Promise<ApiResponse>;
19
+ private buildUrl;
20
+ private request;
21
+ }
22
+ //# sourceMappingURL=client.d.ts.map
package/dist/client.js ADDED
@@ -0,0 +1,124 @@
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
+ import * as https from 'node:https';
16
+ import * as http from 'node:http';
17
+ import { buildRemoteControlHeaders } from './signing.js';
18
+ // ── Endpoint conventions ────────────────────────────────────────────────
19
+ // Joomla Web Services live under /api/index.php; MokoSuiteClient routes are
20
+ // registered at /v1/mokosuiteclient/* (see MSC_BASE in index.ts).
21
+ const API_PREFIX = '/api/index.php';
22
+ const TIMEOUT_MS = 30_000;
23
+ // ────────────────────────────────────────────────────────────────────────
24
+ export class ApiClient {
25
+ conn;
26
+ base_url;
27
+ headers;
28
+ insecure;
29
+ constructor(conn) {
30
+ this.conn = conn;
31
+ this.base_url = conn.baseUrl.replace(/\/+$/, '') + API_PREFIX;
32
+ // Read/CRUD tier: Joomla Web Services authenticate via a Bearer API token.
33
+ this.headers = {
34
+ 'Authorization': `Bearer ${conn.apiToken}`,
35
+ 'Content-Type': 'application/json',
36
+ 'Accept': 'application/vnd.api+json',
37
+ };
38
+ this.insecure = conn.insecure ?? false;
39
+ }
40
+ async get(endpoint, params) {
41
+ return this.request(this.buildUrl(endpoint, params), 'GET');
42
+ }
43
+ async post(endpoint, body) {
44
+ return this.request(this.buildUrl(endpoint), 'POST', body);
45
+ }
46
+ async put(endpoint, body) {
47
+ return this.request(this.buildUrl(endpoint), 'PUT', body);
48
+ }
49
+ async patch(endpoint, body) {
50
+ return this.request(this.buildUrl(endpoint), 'PATCH', body);
51
+ }
52
+ async delete(endpoint) {
53
+ return this.request(this.buildUrl(endpoint), 'DELETE');
54
+ }
55
+ /**
56
+ * Remote-control tier: swap the auth to the site's `health_api_token` and attach the
57
+ * RSA signature headers HQ uses, then issue a GET or POST. Requires the connection to
58
+ * carry `healthApiToken` + `rsaPrivateKeyPath`.
59
+ */
60
+ async remoteControl(method, endpoint, body, params) {
61
+ const signed = {
62
+ ...buildRemoteControlHeaders(this.conn),
63
+ 'Authorization': `Bearer ${this.conn.healthApiToken}`,
64
+ };
65
+ return this.request(this.buildUrl(endpoint, params), method, body, signed);
66
+ }
67
+ buildUrl(endpoint, params) {
68
+ const path = endpoint.startsWith('/') ? endpoint : `/${endpoint}`;
69
+ const url = new URL(`${this.base_url}${path}`);
70
+ if (params) {
71
+ for (const [key, value] of Object.entries(params)) {
72
+ url.searchParams.set(key, value);
73
+ }
74
+ }
75
+ return url.toString();
76
+ }
77
+ request(url, method, body, extraHeaders) {
78
+ return new Promise((resolve, reject) => {
79
+ const parsed = new URL(url);
80
+ const is_https = parsed.protocol === 'https:';
81
+ const transport = is_https ? https : http;
82
+ const options = {
83
+ hostname: parsed.hostname,
84
+ port: parsed.port || (is_https ? 443 : 80),
85
+ path: parsed.pathname + parsed.search,
86
+ method,
87
+ headers: { ...this.headers, ...(extraHeaders ?? {}) },
88
+ timeout: TIMEOUT_MS,
89
+ };
90
+ if (this.insecure && is_https) {
91
+ options.rejectUnauthorized = false;
92
+ }
93
+ const payload = body !== undefined ? JSON.stringify(body) : undefined;
94
+ if (payload) {
95
+ options.headers['Content-Length'] = Buffer.byteLength(payload).toString();
96
+ }
97
+ const req = transport.request(options, (res) => {
98
+ const chunks = [];
99
+ res.on('data', (chunk) => chunks.push(chunk));
100
+ res.on('end', () => {
101
+ const raw = Buffer.concat(chunks).toString('utf-8');
102
+ let data;
103
+ try {
104
+ data = JSON.parse(raw);
105
+ }
106
+ catch {
107
+ data = raw;
108
+ }
109
+ resolve({ status: res.statusCode ?? 0, data });
110
+ });
111
+ });
112
+ req.on('error', (err) => reject(err));
113
+ req.on('timeout', () => {
114
+ req.destroy();
115
+ reject(new Error('Request timed out'));
116
+ });
117
+ if (payload) {
118
+ req.write(payload);
119
+ }
120
+ req.end();
121
+ });
122
+ }
123
+ }
124
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1,4 @@
1
+ import type { ApiConfig, ApiConnection } from './types.js';
2
+ export declare function loadConfig(): Promise<ApiConfig>;
3
+ export declare function getConnection(config: ApiConfig, name?: string): ApiConnection;
4
+ //# sourceMappingURL=config.d.ts.map
package/dist/config.js ADDED
@@ -0,0 +1,53 @@
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
+ import { readFile } from 'node:fs/promises';
16
+ import { resolve } from 'node:path';
17
+ import { homedir } from 'node:os';
18
+ // ── Customize this ──────────────────────────────────────────────────────
19
+ // Change the filename to match your project (e.g. ".dolibarr-api-mcp.json")
20
+ const CONFIG_FILENAME = '.mcp_mokosuite.json';
21
+ // Change the env var name to match your project
22
+ const CONFIG_ENV_VAR = 'MOKOSUITE_CONFIG';
23
+ // ────────────────────────────────────────────────────────────────────────
24
+ export async function loadConfig() {
25
+ const config_path = process.env[CONFIG_ENV_VAR]
26
+ ? resolve(process.env[CONFIG_ENV_VAR])
27
+ : resolve(homedir(), CONFIG_FILENAME);
28
+ try {
29
+ const raw = await readFile(config_path, 'utf-8');
30
+ const parsed = JSON.parse(raw);
31
+ if (!parsed.connections || Object.keys(parsed.connections).length === 0) {
32
+ throw new Error('No connections defined in config');
33
+ }
34
+ return {
35
+ connections: parsed.connections,
36
+ defaultConnection: parsed.defaultConnection ?? Object.keys(parsed.connections)[0],
37
+ };
38
+ }
39
+ catch (err) {
40
+ const message = err instanceof Error ? err.message : String(err);
41
+ throw new Error(`Failed to load config from ${config_path}: ${message}\n` +
42
+ `Create ${config_path} — see config.example.json for format.`);
43
+ }
44
+ }
45
+ export function getConnection(config, name) {
46
+ const key = name ?? config.defaultConnection;
47
+ const conn = config.connections[key];
48
+ if (!conn) {
49
+ throw new Error(`Connection "${key}" not found. Available: ${Object.keys(config.connections).join(', ')}`);
50
+ }
51
+ return conn;
52
+ }
53
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,181 @@
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
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
17
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
18
+ import { z } from 'zod';
19
+ import { loadConfig, getConnection } from './config.js';
20
+ import { ApiClient } from './client.js';
21
+ import { hasRemoteControl } from './signing.js';
22
+ /** Base path for MokoSuiteClient Web Services (appended after /api/index.php). */
23
+ const MSC = '/v1/mokosuiteclient';
24
+ let config;
25
+ function clientFor(connection) {
26
+ return new ApiClient(getConnection(config, connection));
27
+ }
28
+ function formatResponse(res) {
29
+ if (res.status >= 400) {
30
+ return {
31
+ content: [{ type: 'text', text: `Error: HTTP ${res.status}: ${JSON.stringify(res.data, null, 2)}` }],
32
+ };
33
+ }
34
+ return {
35
+ content: [{ type: 'text', text: JSON.stringify(res.data, null, 2) }],
36
+ };
37
+ }
38
+ function text(message) {
39
+ return { content: [{ type: 'text', text: message }] };
40
+ }
41
+ // ── Shared parameter definitions ────────────────────────────────────────
42
+ const ConnectionParam = {
43
+ connection: z.string().optional().describe('Named MokoSuite site/connection from config (uses default if omitted)'),
44
+ };
45
+ const ConfirmParam = {
46
+ confirm: z.boolean().describe('Must be true — this operates on ALL users of the target site and is not reversible'),
47
+ };
48
+ function requireConfirm(confirm) {
49
+ return confirm ? null : text('Refused: set `confirm: true` to run this fleet-wide destructive operation.');
50
+ }
51
+ // ── Server ──────────────────────────────────────────────────────────────
52
+ const server = new McpServer({
53
+ name: 'mcp-mokosuite',
54
+ version: '1.0.0',
55
+ });
56
+ // ════════════════════════════════════════════════════════════════════════
57
+ // FOUNDATION — connectivity & generic access
58
+ // ════════════════════════════════════════════════════════════════════════
59
+ server.tool('mokosuite_ping', 'Check reachability of a MokoSuite site (calls its dashboard endpoint and reports HTTP status).', { ...ConnectionParam }, async ({ connection }) => {
60
+ const client = clientFor(connection);
61
+ try {
62
+ const res = await client.get(`${MSC}/dashboard`);
63
+ return text(`Reachable — HTTP ${res.status} from ${MSC}/dashboard`);
64
+ }
65
+ catch (err) {
66
+ return text(`Unreachable: ${err instanceof Error ? err.message : String(err)}`);
67
+ }
68
+ });
69
+ server.tool('list_connections', 'List configured MokoSuite connections, their role, and which credential tiers are available.', {}, async () => {
70
+ const lines = Object.entries(config.connections).map(([name, conn]) => {
71
+ const is_default = name === config.defaultConnection ? ' (default)' : '';
72
+ const role = conn.role ?? 'client';
73
+ const tiers = ['read/CRUD'];
74
+ if (hasRemoteControl(conn))
75
+ tiers.push('remote-control');
76
+ return ` ${name}${is_default} [${role}]: ${conn.baseUrl} — tiers: ${tiers.join(', ')}`;
77
+ });
78
+ return text(`Configured MokoSuite connections:\n${lines.join('\n')}`);
79
+ });
80
+ server.tool('api_request', 'Make a raw request to any MokoSuite/Joomla Web Services endpoint (escape hatch). Path is relative to /api/index.php.', {
81
+ method: z.enum(['GET', 'POST', 'PUT', 'PATCH', 'DELETE']).describe('HTTP method'),
82
+ endpoint: z.string().describe('Path relative to /api/index.php (e.g. "/v1/mokosuiteclient/health")'),
83
+ body: z.record(z.string(), z.unknown()).optional().describe('Request body for POST/PUT/PATCH'),
84
+ params: z.record(z.string(), z.string()).optional().describe('Query parameters'),
85
+ ...ConnectionParam,
86
+ }, async ({ method, endpoint, body, params, connection }) => {
87
+ const client = clientFor(connection);
88
+ switch (method) {
89
+ case 'GET': return formatResponse(await client.get(endpoint, params));
90
+ case 'POST': return formatResponse(await client.post(endpoint, body));
91
+ case 'PUT': return formatResponse(await client.put(endpoint, body));
92
+ case 'PATCH': return formatResponse(await client.patch(endpoint, body));
93
+ case 'DELETE': return formatResponse(await client.delete(endpoint));
94
+ }
95
+ });
96
+ // ════════════════════════════════════════════════════════════════════════
97
+ // LAYER 0 — Platform (MokoSuiteClient) — READS (Joomla API token)
98
+ // ════════════════════════════════════════════════════════════════════════
99
+ server.tool('client_get_health', '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).', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/health`)));
100
+ server.tool('client_get_dashboard', 'Get the summarized dashboard for a MokoSuite site (health summary, Joomla/PHP versions, plugin states).', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/dashboard`)));
101
+ server.tool('client_list_extensions', 'List installed extensions on a MokoSuite site, with versions and available update info.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/extensions`)));
102
+ server.tool('client_list_plugins', 'List the MokoSuite feature plugins on a site and their enabled/disabled state.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/plugins`)));
103
+ server.tool('client_list_snapshots', 'List the baseline snapshots available on a MokoSuite site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/snapshot`)));
104
+ server.tool('client_list_users', 'List Joomla users on a MokoSuite site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/users`)));
105
+ server.tool('client_export_users', 'Export the users of a MokoSuite site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSC}/users/export`)));
106
+ // ════════════════════════════════════════════════════════════════════════
107
+ // LAYER 0 — Platform — ACTIONS (Joomla API token, core.manage)
108
+ // ════════════════════════════════════════════════════════════════════════
109
+ server.tool('client_clear_cache', 'Clear all Joomla caches and opcache on a MokoSuite site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).post(`${MSC}/cache`)));
110
+ server.tool('client_check_updates', 'Run the Joomla update finder on a MokoSuite site and report the number of available updates.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).post(`${MSC}/update`)));
111
+ server.tool('client_install_extension', 'Install an extension on a MokoSuite site from a remote ZIP URL (64MB cap). Privileged — use trusted URLs only.', {
112
+ url: z.string().describe('HTTPS URL of the extension ZIP to install'),
113
+ ...ConnectionParam,
114
+ }, async ({ url, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/install`, { url })));
115
+ server.tool('client_toggle_plugin', 'Enable or disable a MokoSuite feature plugin on a site by its extension id.', {
116
+ extension_id: z.number().describe('Joomla extension id of the plugin to toggle'),
117
+ ...ConnectionParam,
118
+ }, async ({ extension_id, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/plugins`, { task: 'toggle', extension_id })));
119
+ server.tool('client_create_snapshot', 'Create a baseline snapshot on a MokoSuite site.', {
120
+ label: z.string().optional().describe('Optional label for the snapshot'),
121
+ ...ConnectionParam,
122
+ }, async ({ label, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/snapshot`, label ? { label } : {})));
123
+ server.tool('client_reset_snapshot', 'Restore a named baseline snapshot on a MokoSuite site.', {
124
+ name: z.string().describe('Name of the baseline snapshot to restore'),
125
+ ...ConnectionParam,
126
+ }, async ({ name, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/reset`, { name })));
127
+ server.tool('client_sync_push', 'Push a content sync from this MokoSuite site to a target site.', {
128
+ target: z.string().optional().describe('Target site identifier/URL for the sync (per site sync config)'),
129
+ ...ConnectionParam,
130
+ }, async ({ target, connection }) => formatResponse(await clientFor(connection).post(`${MSC}/sync`, target ? { target } : {})));
131
+ // ════════════════════════════════════════════════════════════════════════
132
+ // LAYER 0 — Platform — REMOTE-CONTROL (RSA-signed; requires HQ credentials)
133
+ // ════════════════════════════════════════════════════════════════════════
134
+ server.tool('client_provision_reset', 'Reset a MokoSuite site for a new client (clears hits, purges version history, regenerates tokens, revokes user tokens). RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
135
+ const guard = requireConfirm(confirm);
136
+ if (guard)
137
+ return guard;
138
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/provision-reset`));
139
+ });
140
+ server.tool('client_remote_login', 'Get a one-time master login URL (60s TTL) for a MokoSuite site. RSA-signed remote-control tier.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/remote-login`)));
141
+ server.tool('client_users_reset_passwords', 'Reset passwords for ALL users on a MokoSuite site. RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
142
+ const guard = requireConfirm(confirm);
143
+ if (guard)
144
+ return guard;
145
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/reset-passwords`));
146
+ });
147
+ server.tool('client_users_reset_2fa', 'Reset two-factor authentication for ALL users on a MokoSuite site. RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
148
+ const guard = requireConfirm(confirm);
149
+ if (guard)
150
+ return guard;
151
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/reset-2fa`));
152
+ });
153
+ server.tool('client_users_disable_all', 'Block ALL users on a MokoSuite site. RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
154
+ const guard = requireConfirm(confirm);
155
+ if (guard)
156
+ return guard;
157
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/disable-all`));
158
+ });
159
+ server.tool('client_users_enable_all', 'Unblock ALL users on a MokoSuite site. RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
160
+ const guard = requireConfirm(confirm);
161
+ if (guard)
162
+ return guard;
163
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/enable-all`));
164
+ });
165
+ server.tool('client_users_force_logout', 'Terminate ALL user sessions on a MokoSuite site. RSA-signed remote-control tier.', { ...ConfirmParam, ...ConnectionParam }, async ({ confirm, connection }) => {
166
+ const guard = requireConfirm(confirm);
167
+ if (guard)
168
+ return guard;
169
+ return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/force-logout`));
170
+ });
171
+ // ── Start Server ────────────────────────────────────────────────────────
172
+ async function main() {
173
+ config = await loadConfig();
174
+ const transport = new StdioServerTransport();
175
+ await server.connect(transport);
176
+ }
177
+ main().catch((err) => {
178
+ process.stderr.write(`Fatal: ${err}\n`);
179
+ process.exit(1);
180
+ });
181
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ import type { ApiConnection } from './types.js';
2
+ /**
3
+ * MokoSuiteClient's remote-control routes (provision-reset, remote-login, mass user ops)
4
+ * are `public` but require defence-in-depth headers that only MokoSuiteHQ can generate.
5
+ * HQ signs the payload `domain|timestamp|token` with its RSA-2048 private key (SHA-256);
6
+ * the client verifies against a baked-in public keyring within a 300-second freshness
7
+ * window. This reproduces that scheme so the MCP can act as HQ.
8
+ *
9
+ * @throws if the connection is missing the remote-control credentials or the key is unreadable.
10
+ */
11
+ export declare function buildRemoteControlHeaders(conn: ApiConnection): Record<string, string>;
12
+ /** True when a connection carries the credentials needed for the remote-control tier. */
13
+ export declare function hasRemoteControl(conn: ApiConnection): boolean;
14
+ //# sourceMappingURL=signing.d.ts.map