@mokoconsulting/mcp-mokosuite 1.0.0 → 1.1.0-dev.372

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.
@@ -7,8 +7,8 @@
7
7
  # INGROUP: MokoCLI.Release
8
8
  # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
9
  # PATH: /.mokogit/workflows/pre-release.yml
10
- # VERSION: 06.00.00
11
- # BRIEF: Batched pre-release on nightly schedule + manual dispatch (rolling dev/rc asset, build-metadata version — #404)
10
+ # VERSION: 06.01.00
11
+ # BRIEF: Batched pre-release on nightly schedule + manual dispatch (rolling dev/rc asset, build-metadata version — #404); non-Joomla MokoGIT release + cosmetic release-notes are best-effort. npm publishing moved OUT of this universal workflow into Template-NPM's dedicated npm-publish.yml so it can't leak into non-npm repos via sync.
12
12
 
13
13
  name: "Universal: Pre-Release"
14
14
 
@@ -19,8 +19,32 @@ name: "Universal: Pre-Release"
19
19
  # as BUILD METADATA (version_build_metadata.php) instead of a committed bump, and
20
20
  # the asset is published under a FIXED rolling name replaced in place.
21
21
  on:
22
+ # Dev-channel release on every merge to dev. Uses build-metadata versioning
23
+ # (version_build_metadata.php) so nothing is committed back to dev — the #404
24
+ # manifest <version> merge-conflict problem does NOT return. paths-ignore is a
25
+ # non-code denylist: a push touching only docs / CI config / repo metadata does
26
+ # not build. The concurrency guard below collapses a burst of merges into one.
27
+ push:
28
+ branches:
29
+ - dev
30
+ paths-ignore:
31
+ - '.mokogit/**'
32
+ - '.gitea/**'
33
+ - '.github/**'
34
+ - '.vscode/**'
35
+ - '.idea/**'
36
+ - 'docs/**'
37
+ - 'wiki/**'
38
+ - '**/*.md'
39
+ - '.editorconfig'
40
+ - '.gitignore'
41
+ - '.gitattributes'
42
+ - '.gitmodules'
43
+ - '.gitmessage'
44
+ - 'phpstan.neon'
45
+ - 'LICENSE'
22
46
  schedule:
23
- # Nightly at 07:00 UTC — batches the day's dev merges into one dev build.
47
+ # Nightly at 07:00 UTC — safety-net batch for any dev merge a push build missed.
24
48
  - cron: '0 7 * * *'
25
49
  workflow_dispatch:
26
50
  inputs:
@@ -40,6 +64,14 @@ on:
40
64
  default: dev
41
65
  type: string
42
66
 
67
+ concurrency:
68
+ # Per channel + ref: a burst of dev merges collapses to the latest build. A
69
+ # manual alpha/beta/rc dispatch has its own group, so it is never cancelled by
70
+ # a dev push (and vice versa); the nightly runs on the default-branch ref, a
71
+ # different group again.
72
+ group: pre-release-${{ github.ref }}-${{ inputs.stability || 'development' }}
73
+ cancel-in-progress: true
74
+
43
75
  permissions:
44
76
  contents: write
45
77
 
@@ -53,14 +85,15 @@ jobs:
53
85
  name: "Build Pre-Release (${{ inputs.stability || 'development' }})"
54
86
  runs-on: release
55
87
  # Skip on template repos (Template-*) — they scaffold other repos and do not release.
56
- # Trigger-loop guard (#404 follow-up): this workflow already runs ONLY on `schedule` +
57
- # `workflow_dispatch` (the every-push trigger that caused the ~150x pre-release storm on
58
- # chore/mokoonyx-template-sync-* branches was removed in #404) — so a template-sync push
59
- # can no longer reach it. As defence-in-depth we also refuse a manual dispatch aimed at a
60
- # bot/sync ref, so provisioning tooling cannot fan out dispatches against those branches.
88
+ # Triggers: push to dev (per-merge dev-channel build), the nightly schedule
89
+ # (safety-net batch), and manual workflow_dispatch. The push trigger is scoped
90
+ # to `dev` + a non-code paths-ignore, so a template-sync/doc-only push can't
91
+ # reach it (the ~150x storm on chore/mokoonyx-template-sync-* stays prevented).
92
+ # As defence-in-depth we also refuse a manual dispatch aimed at a bot/sync ref.
61
93
  if: >-
62
94
  !startsWith(github.event.repository.name, 'Template-') &&
63
95
  (
96
+ github.event_name == 'push' ||
64
97
  github.event_name == 'schedule' ||
65
98
  (
66
99
  github.event_name == 'workflow_dispatch' &&
@@ -73,9 +106,9 @@ jobs:
73
106
  )
74
107
 
75
108
  steps:
76
- # #404 — no push event any more. The build ref is the dispatch input (default
77
- # dev) or, for the nightly schedule, dev. On schedule github.ref_name is the
78
- # default branch (main), so we resolve the pre-release branch explicitly here.
109
+ # Build ref: the dispatch input if given, else `dev` — which is correct for
110
+ # both a push (only `dev` triggers it) and the nightly schedule (where
111
+ # github.ref_name would otherwise be the default branch, main).
79
112
  - name: Resolve build ref
80
113
  id: ref
81
114
  run: echo "ref=${{ inputs.ref || 'dev' }}" >> "$GITHUB_OUTPUT"
@@ -88,9 +121,32 @@ jobs:
88
121
  ref: ${{ steps.ref.outputs.ref }}
89
122
  submodules: recursive
90
123
 
91
- - name: Update submodules to main
124
+ - name: Update submodules to latest
125
+ # Skip cleanly on submodule-less repos. The old step was a silent no-op:
126
+ # `2>/dev/null || true` swallowed every error and the in-step `git pull`
127
+ # used an UN-credentialed remote, so private submodules never actually
128
+ # updated. Credential the private remote, then update each submodule to its
129
+ # tracked branch (default: the submodule's default branch) so the built
130
+ # package contains the latest submodule content. Per #404 this is
131
+ # working-tree only (no commit) — the package is built from the working tree.
132
+ run: |
133
+ if [ -f .gitmodules ]; then
134
+ git config --global url."https://x-access-token:${{ secrets.MOKOGIT_TOKEN }}@git.mokoconsulting.tech/".insteadOf "https://git.mokoconsulting.tech/"
135
+ git submodule sync --recursive
136
+ git submodule update --init --remote --recursive
137
+ else
138
+ echo "No .gitmodules — skipping submodule update."
139
+ fi
140
+
141
+ - name: Isolate workspace (remove foreign/stale checkouts)
92
142
  run: |
93
- git submodule foreach --quiet 'git checkout main && git pull --quiet origin main' 2>/dev/null || true
143
+ find . -name .git -not -path './.git' -prune -print | while read nested; do
144
+ dir=$(dirname "$nested")
145
+ [ "$dir" = "." ] && continue
146
+ git ls-files --error-unmatch "$dir" >/dev/null 2>&1 && continue
147
+ echo "::warning::Removing untracked nested checkout: $dir"
148
+ rm -rf "$dir"
149
+ done
94
150
 
95
151
  - name: Setup MokoCLI tools
96
152
  env:
@@ -100,19 +156,39 @@ jobs:
100
156
  # Use pre-installed /opt/mokocli if available (updated by cron every 6h)
101
157
  if [ -f /opt/mokocli/cli/version_bump.php ] && [ -f /opt/mokocli/cli/manifest_element.php ] && [ -f /opt/mokocli/vendor/autoload.php ]; then
102
158
  echo Using pre-installed /opt/mokocli
159
+ # Keep the pre-installed copy current with mokocli main (auto-refresh; soft — never fails the run).
160
+ if git -C /opt/mokocli rev-parse --git-dir >/dev/null 2>&1; then
161
+ git -C /opt/mokocli pull --ff-only --quiet 2>/dev/null || echo " (mokocli git refresh skipped)"
162
+ REFRESH_TOK="$(printf '%s' "${MOKO_CLONE_TOKEN}" | tr -d '[:space:]')"
163
+ if [ -n "${REFRESH_TOK}" ] && command -v composer >/dev/null 2>&1; then
164
+ composer config -g http-basic.git.mokoconsulting.tech x-access-token "${REFRESH_TOK}" 2>/dev/null || true
165
+ ( cd /opt/mokocli && composer install --no-dev --no-interaction --no-progress --optimize-autoloader --quiet ) 2>/dev/null || echo " (mokocli composer refresh skipped)"
166
+ fi
167
+ fi
103
168
  echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
104
169
  else
105
170
  echo Falling back to fresh clone
171
+ # Strip any stray whitespace/newline a pasted secret may carry, and mask
172
+ # it in the logs. Without the trim + quoted URL below, a trailing newline
173
+ # in MOKOGIT_TOKEN word-splits CLONE_URL so git clone sees extra
174
+ # positionals and dies with "fatal: Too many arguments".
175
+ MOKO_CLONE_TOKEN="$(printf '%s' "${MOKO_CLONE_TOKEN}" | tr -d '[:space:]')"
176
+ echo "::add-mask::${MOKO_CLONE_TOKEN}"
177
+ if [ -z "${MOKO_CLONE_TOKEN}" ]; then
178
+ echo "::error::MOKOGIT_TOKEN secret is empty — cannot clone mokocli fallback." >&2
179
+ exit 1
180
+ fi
106
181
  if ! command -v composer > /dev/null 2>&1; then
107
182
  sudo apt-get update -qq && sudo apt-get install -y -qq php-cli php-mbstring php-xml php-zip php-curl composer > /dev/null 2>&1
108
183
  fi
109
184
  rm -rf /tmp/mokocli
110
- CLONE_URL=https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git
111
- git clone --depth 1 --branch main --quiet $CLONE_URL /tmp/mokocli
185
+ CLONE_URL="https://x-access-token:${MOKO_CLONE_TOKEN}@${MOKO_CLONE_HOST}/mokocli.git"
186
+ git clone --depth 1 --branch main --quiet "${CLONE_URL}" /tmp/mokocli
112
187
  cd /tmp/mokocli
113
188
  # Authenticate to the MokoGIT private Composer registry (git.mokoconsulting.tech)
114
189
  # declared in mokocli composer.json (preferred-install=dist) before installing deps.
115
190
  composer config -g http-basic.git.mokoconsulting.tech x-access-token "${MOKO_CLONE_TOKEN}"
191
+ export COMPOSER_AUTH="{\"http-basic\":{\"git.mokoconsulting.tech\":{\"username\":\"x-access-token\",\"password\":\"${MOKO_CLONE_TOKEN}\"}}}"
116
192
  composer install --no-dev --no-interaction --no-progress --optimize-autoloader --quiet
117
193
  echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
118
194
  fi
@@ -266,6 +342,11 @@ jobs:
266
342
  --path . --version "$VERSION" --branch "${{ steps.ref.outputs.ref }}" --stability "$STABILITY" 2>/dev/null || true
267
343
  php ${MOKO_CLI}/version_check.php --path . --fix 2>/dev/null || true
268
344
 
345
+ # Emit a Joomla schema-update stub for each shipped extension's own
346
+ # version so #__schemas tracks the manifest (fixes "database out of date").
347
+ # No-op (|| true) on non-Joomla repos, which have no sql/updates dirs.
348
+ php ${MOKO_CLI}/schema_stub.php --path . || true
349
+
269
350
  # Ensure licensing tags (updateservers, dlid) if enabled in manifest.xml
270
351
  php ${MOKO_CLI}/manifest_licensing.php --path . --fix 2>/dev/null || true
271
352
 
@@ -302,20 +383,61 @@ jobs:
302
383
 
303
384
  echo "=== Pre-Release: ${EXT_ELEMENT} ${VERSION}${SUFFIX} ==="
304
385
 
386
+ # npm publishing is NOT part of this universal workflow — it lives only in
387
+ # Template-NPM (.mokogit/workflows/npm-publish.yml) so it cannot leak into
388
+ # Joomla / non-npm repos via template sync. Joomla-family repos attach a zip
389
+ # below; npm repos publish from their own dedicated workflow.
390
+ # Release API operations (create / notes / upload / cascade delete) run
391
+ # against THIS repo, so they authenticate with the built-in per-run
392
+ # github.token (contents: write) — NOT the org MOKOGIT_TOKEN. That keeps
393
+ # MOKOGIT_TOKEN least-privilege (read-only, used only for the cross-repo
394
+ # mokocli / submodule clones above) while same-repo release writes stay
395
+ # scoped to the run — matching auto-release.yml (Template-Generic #188).
396
+ # github.token carries no trailing newline, but trim + mask anyway for
397
+ # Authorization-header hygiene.
398
+ - name: Sanitize release API token
399
+ if: steps.eligibility.outputs.proceed == 'true'
400
+ run: |
401
+ T="$(printf '%s' "${{ github.token }}" | tr -d '[:space:]')"
402
+ echo "::add-mask::${T}"
403
+ echo "MOKO_TOKEN=${T}" >> "$GITHUB_ENV"
404
+
305
405
  - name: Create release
306
406
  id: release
307
407
  if: steps.eligibility.outputs.proceed == 'true'
408
+ # The MokoGIT release is the deliverable ONLY for the Joomla family. npm/mcp/
409
+ # client publish their real artifact upstream (npm) and merely mirror a
410
+ # release here, so a MokoGIT release failure must not fail those runs.
411
+ # release_create.php can fail its delete-then-recreate of the rolling dev/rc
412
+ # release (DELETE returns HTTP 200 but the release lingers), which turns an
413
+ # already-successful npm publish red. We branch on family INSIDE the run
414
+ # script (this act runner rejects `continue-on-error: ${{ ... }}` — it only
415
+ # accepts a literal bool): Joomla stays fatal; non-Joomla swallows a release
416
+ # failure so a green npm publish stays green.
308
417
  run: |
309
418
  TAG="${{ steps.meta.outputs.tag }}"
310
419
  VERSION="${{ steps.meta.outputs.version }}"
311
420
  API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
312
- php ${MOKO_CLI}/release_create.php \
313
- --path . --version "$VERSION" --tag "$TAG" \
314
- --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
315
- --repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease
421
+ if [ "${{ steps.eligibility.outputs.is_joomla_family }}" = "true" ]; then
422
+ php ${MOKO_CLI}/release_create.php \
423
+ --path . --version "$VERSION" --tag "$TAG" \
424
+ --token "${MOKO_TOKEN}" --api-base "$API_BASE" \
425
+ --repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease
426
+ else
427
+ php ${MOKO_CLI}/release_create.php \
428
+ --path . --version "$VERSION" --tag "$TAG" \
429
+ --token "${MOKO_TOKEN}" --api-base "$API_BASE" \
430
+ --repo "${GIT_REPO}" --branch "${{ steps.ref.outputs.ref }}" --prerelease \
431
+ || echo "::warning::MokoGIT release failed — non-fatal for npm/mcp/client (npm is the real artifact)"
432
+ fi
316
433
 
317
434
  - name: Update release notes from CHANGELOG.md
318
435
  if: steps.eligibility.outputs.proceed == 'true'
436
+ # Release notes are cosmetic. The release-body PATCH can 403 on this MokoGIT
437
+ # instance (the per-run github.token lacks release-write), and for npm/mcp/
438
+ # client the published package is the real artifact — never let this fail
439
+ # the run. (Matches the cascade step's best-effort contract.)
440
+ continue-on-error: true
319
441
  run: |
320
442
  TAG="${{ steps.meta.outputs.tag }}"
321
443
  VERSION="${{ steps.meta.outputs.version }}"
@@ -332,7 +454,7 @@ jobs:
332
454
  fi
333
455
 
334
456
  # Update release body via API
335
- RELEASE_ID=$(curl -sf -H "Authorization: token ${{ secrets.MOKOGIT_TOKEN }}" \
457
+ RELEASE_ID=$(curl -sf -H "Authorization: token ${MOKO_TOKEN}" \
336
458
  "${API_BASE}/releases/tags/${TAG}" | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || true)
337
459
 
338
460
  if [ -n "$RELEASE_ID" ]; then
@@ -344,7 +466,7 @@ jobs:
344
466
  '${API_BASE}/releases/${RELEASE_ID}',
345
467
  data=payload, method='PATCH',
346
468
  headers={
347
- 'Authorization': 'token ${{ secrets.MOKOGIT_TOKEN }}',
469
+ 'Authorization': 'token ${MOKO_TOKEN}',
348
470
  'Content-Type': 'application/json'
349
471
  })
350
472
  urllib.request.urlopen(req)
@@ -352,6 +474,25 @@ jobs:
352
474
  echo "Release notes updated from CHANGELOG.md"
353
475
  fi
354
476
 
477
+ # Build-time PHP dependency vendoring (universal, guarded). If a package ships a
478
+ # composer.json, fetch its runtime deps into that package's vendor dir BEFORE
479
+ # release_package.php zips the working tree — so a component can declare a PHP
480
+ # library (e.g. dompdf) via composer.json + composer.lock instead of committing
481
+ # vendor/. No-op for any repo without a package composer.json; non-fatal so a
482
+ # composer failure can never block a release (it packages whatever vendor exists).
483
+ - name: Vendor PHP dependencies (build-time)
484
+ if: steps.eligibility.outputs.proceed == 'true'
485
+ continue-on-error: true
486
+ run: |
487
+ command -v composer >/dev/null 2>&1 || { sudo apt-get update -qq && sudo apt-get install -y -qq composer >/dev/null 2>&1 || true; }
488
+ for cj in $(find source/packages -maxdepth 2 -name composer.json 2>/dev/null); do
489
+ dir="$(dirname "$cj")"
490
+ echo "::notice::composer install (no-dev) in ${dir}"
491
+ composer install --no-dev --no-interaction --no-progress --optimize-autoloader --working-dir="$dir" \
492
+ || echo "::warning::composer install failed in ${dir} — packaging existing vendor"
493
+ done
494
+ true
495
+
355
496
  # Joomla-family only: mokocli release_package.php builds the extension zip
356
497
  # (+ sha256, attach). npm/mcp/client package/publish in their platform shim
357
498
  # (trust boundary, ADR #124), so this PHP zip step is skipped for them.
@@ -365,31 +506,29 @@ jobs:
365
506
  TAG="${{ steps.meta.outputs.tag }}"
366
507
  STABILITY="${{ steps.meta.outputs.stability }}"
367
508
  API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
368
- # #404 — publish the pre-release asset under a FIXED rolling name
369
- # (pkg_<ext>-dev.zip / -rc.zip) replaced in place, instead of a new
370
- # versioned file each build. --stability drives the channel slug; the
371
- # in-package manifest <version> still advances (build metadata) so the
372
- # updater keeps detecting new builds.
509
+ # Publish the pre-release asset under a VERSIONED rolling name
510
+ # (pkg_<ext>-<version>-dev.zip / -rc.zip) replaced in place each build. The
511
+ # release version is stable within a base version, so the asset URL stays
512
+ # stable while the version is visible in the filename. The in-package
513
+ # manifest <version> still advances (build metadata) so the updater keeps
514
+ # detecting new builds.
373
515
  php ${MOKO_CLI}/release_package.php \
374
516
  --path . --version "$VERSION" --tag "$TAG" \
375
- --token "${{ secrets.MOKOGIT_TOKEN }}" --api-base "$API_BASE" \
517
+ --token "${MOKO_TOKEN}" --api-base "$API_BASE" \
376
518
  --repo "${GIT_REPO}" --output /tmp \
377
519
  --stability "$STABILITY" --rolling || true
378
520
 
379
- # updates.xml is generated dynamically by MokoGIT license server.
380
- # #404 — the dynamic feed must point <downloadurl> at the FIXED rolling asset
381
- # name for dev/rc (pkg_<ext>-dev.zip), matching what release_package.php now
382
- # publishes. mokocli's updates_xml_build.php grew a --rolling flag for this;
383
- # the server-side generator (MokoGIT license server) needs the equivalent
384
- # change so the served updates.xml references the fixed filename, not the old
385
- # versioned one. See #404 report — flagged for human/server-side follow-up.
521
+ # updates.xml <downloadurl> is generated server-side by MokoGIT (the license
522
+ # server; being moved to MokoSuiteLicensing). It must reference the VERSIONED
523
+ # rolling asset (pkg_<ext>-<version>-dev.zip) that release_package.php now
524
+ # publishes — keep the server-side generator in sync with that filename.
386
525
 
387
526
  - name: "Delete lesser pre-release channels (cascade)"
388
527
  if: steps.eligibility.outputs.proceed == 'true'
389
528
  continue-on-error: true
390
529
  run: |
391
530
  API_BASE="${GIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
392
- TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
531
+ TOKEN="${MOKO_TOKEN}"
393
532
 
394
533
  php ${MOKO_CLI}/release_cascade.php \
395
534
  --stability "${{ steps.meta.outputs.stability }}" \
@@ -34,7 +34,7 @@ jobs:
34
34
  BRANCH: ${{ github.event.pull_request.head.ref }}
35
35
  REPO: ${{ github.repository }}
36
36
  GIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
37
- TOKEN: ${{ secrets.MOKOGIT_TOKEN }}
37
+ TOKEN: ${{ secrets.MOKOGIT_TOKEN || github.token }}
38
38
  run: |
39
39
  set -euo pipefail
40
40
  # BRANCH is attacker-controlled (PR head ref). Strict allowlist before ANY use.
@@ -5,12 +5,12 @@
5
5
  # FILE INFORMATION
6
6
  # DEFGROUP: MokoGIT.Workflow.Template
7
7
  # INGROUP: MokoCLI.CI
8
- # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Joomla
8
+ # REPO: https://git.mokoconsulting.tech/MokoConsulting/Template-Generic
9
9
  # PATH: /.mokogit/workflows/version-set.yml
10
- # VERSION: 01.00.00
11
- # BRIEF: Set or reset the extension version across all version-bearing files
10
+ # VERSION: 01.01.00
11
+ # BRIEF: Set or reset the project version across all version-bearing files (README / CHANGELOG / file-header blocks universal; the Joomla manifest step is guarded and no-ops on non-Joomla repos)
12
12
 
13
- name: "Joomla: Set Version"
13
+ name: "Universal: Set Version"
14
14
 
15
15
  on:
16
16
  workflow_dispatch:
package/AGENTS.md ADDED
@@ -0,0 +1,39 @@
1
+ <!--
2
+ Copyright 2026 Moko Consulting
3
+ SPDX-License-Identifier: GPL-3.0-or-later
4
+ BRIEF: Shared AI-agent instruction source of truth for this repository.
5
+ This file is the single point of truth; CLAUDE.md / GEMINI.md / MOKOAI.md point here.
6
+ -->
7
+
8
+ # mcp-mokosuite — Agent Instructions
9
+
10
+ `mcp-mokosuite` is an MCP (Model Context Protocol) server for MokoSuite. It
11
+ exposes MokoSuite operations to AI agents over the Model Context Protocol.
12
+
13
+ > Read by every AI agent (Claude Code, Gemini CLI, MokoAI). CLAUDE.md / GEMINI.md / MOKOAI.md point here.
14
+
15
+ ## Platform & Layout
16
+
17
+ - Platform: MCP server (Node.js).
18
+ - Language: TypeScript.
19
+ - Source dir: `src/`.
20
+ - TypeScript config: `tsconfig.json`.
21
+ - Example runtime config: `config.example.json`.
22
+ - Docs: `docs/`.
23
+ - Notable files: `package.json`, `Makefile`, `CODE_OF_CONDUCT.md`.
24
+
25
+ ## Commands
26
+
27
+ - `npm install` — install dependencies.
28
+ - `npm run build` — compile TypeScript (see `package.json` scripts).
29
+ - `make` — see `Makefile` for the repo's build/lint targets.
30
+
31
+ ## Rules
32
+
33
+ - Never commit `.claude/`, `.gemini/`, `.mokoai/`, `.mcp.json`, `TODO.md`, or
34
+ minified/compiled assets.
35
+ - Workflow / automation config lives under `.mokogit/`.
36
+ - Attribute all machine-authored commits with `Authored-by: Moko Consulting`.
37
+ - Do not commit real credentials; copy `config.example.json` to a local,
38
+ ignored config.
39
+ - Standards: MokoCLI — https://git.mokoconsulting.tech/MokoConsulting/mokocli/wiki/Home
package/CHANGELOG.md CHANGED
@@ -1,18 +1,54 @@
1
+ # Changelog
1
2
 
3
+ All notable changes to this project are documented here, following [Keep a Changelog](https://keepachangelog.com/) and MokoStandards `XX.YY.ZZ` versioning.
2
4
 
3
5
  ## [Unreleased]
4
6
 
5
- ### Changed
6
- - Branding: Git -> MokoGIT in workflow DEFGROUP tags, committer identity (git-actions[bot] -> mokogit-actions[bot]), and platform prose. Functional tokens (GIT_TOKEN/GITHUB_TOKEN, GIT_ACTIONS_*, vars.GITEA_*, git.mokoconsulting.tech) preserved.
7
+ ## [01.01.00] - 2026-08-15
7
8
 
8
- ### Removed
9
- - CI: stripped GitHub-only workflows (DEFGROUP GitHub.Workflow), which do not run on MokoGIT Actions: auto-dev-issue.yml changelog-validation.yml standards-compliance.yml sync-version-on-merge.yml.
9
+ ### Added
10
+ - **Layer 1 (Extensions Store / MokoSuiteExtensions)** — 5 MCP tools against the
11
+ `/v1/mokosuiteextensions` base path:
12
+ - Reads: `store_catalog_list` (`GET /catalog`), `store_catalog_get`
13
+ (`GET /catalog/{id}`).
14
+ - Writes: `store_install` (`POST /catalog/{id}/install`), `store_update`
15
+ (`POST /catalog/{id}/update`), `store_uninstall`
16
+ (`POST /catalog/{id}/uninstall`, `confirm: true` guard — destructive).
17
+ - Bumped the MCP server advertised version to `1.1.0`.
18
+
19
+ ### Note
20
+ - The store write routes (`/install`, `/update`, `/uninstall`) are being added to
21
+ the store REST API in parallel and may not be live on a site until that ships and
22
+ deploys. The read routes exist. The tools are defined now regardless.
10
23
 
11
- ### Changed
12
- - Migrated all workflow and template paths from `.github/` to `.mokogit/`
13
- - Template source paths updated: `templates/git/` to `templates/mokogit/`
14
- - HCL definition files removed -- Template repos are now the canonical source
24
+ ## [01.00.00] - 2026-07-26
15
25
 
16
26
  ### Added
17
- - CI: gate build/CI/release/version workflows so they skip template repos; smart-gate the sync workflows to run only on Template-* repos; normalize mokocli/MokoCLI refs and drop the stale mokoconsulting-tech org slug.
18
- - `branch-cleanup.yml`: auto-delete merged feature branches after PR merge
27
+ - **Foundation** — the real MokoSuite MCP server (de-templated from Template-NPM):
28
+ - Multi-site connection model with two credential tiers per site — a Joomla Web
29
+ Services Bearer token for read/CRUD, plus an optional `health_api_token` + HQ
30
+ RSA private key for the remote-control tier (`src/types.ts`).
31
+ - RSA remote-control request signing reproducing HQ's
32
+ `X-MokoSuite-Signature` / `-Timestamp` / `-Key-Version` scheme (RSA-2048 over
33
+ `domain|timestamp|token`, SHA-256, 300 s window) (`src/signing.ts`).
34
+ - Joomla Web Services HTTP client with a signed `remoteControl()` path alongside
35
+ the token-auth methods (`src/client.ts`).
36
+ - **Layer 0 (Platform / MokoSuiteClient)** — 24 MCP tools:
37
+ - Foundation: `mokosuite_ping`, `list_connections`, `api_request`.
38
+ - Reads: `client_get_health`, `client_get_dashboard`, `client_list_extensions`,
39
+ `client_list_plugins`, `client_list_snapshots`, `client_list_users`,
40
+ `client_export_users`.
41
+ - Actions: `client_clear_cache`, `client_check_updates`, `client_install_extension`,
42
+ `client_toggle_plugin`, `client_create_snapshot`, `client_reset_snapshot`,
43
+ `client_sync_push`.
44
+ - Remote-control (RSA-signed; `confirm: true` guard on destructive fleet-wide ops):
45
+ `client_provision_reset`, `client_remote_login`, `client_users_reset_passwords`,
46
+ `client_users_reset_2fa`, `client_users_disable_all`, `client_users_enable_all`,
47
+ `client_users_force_logout`.
48
+ - Published `@mokoconsulting/mcp-mokosuite@1.0.0` to npmjs.org and the internal
49
+ git.mokoconsulting.tech registry.
50
+
51
+ ### Verified
52
+ - The read tier (`get_health`, `get_dashboard`, `list_extensions`, `list_plugins`,
53
+ `list_snapshots`) returns live data from `suite.dev.mokoconsulting.tech`
54
+ (`/api/index.php/v1/mokosuiteclient/*`).
package/dist/index.js CHANGED
@@ -21,6 +21,8 @@ import { ApiClient } from './client.js';
21
21
  import { hasRemoteControl } from './signing.js';
22
22
  /** Base path for MokoSuiteClient Web Services (appended after /api/index.php). */
23
23
  const MSC = '/v1/mokosuiteclient';
24
+ /** Base path for MokoSuiteExtensions (store) Web Services (appended after /api/index.php). */
25
+ const MSE = '/v1/mokosuiteextensions';
24
26
  let config;
25
27
  function clientFor(connection) {
26
28
  return new ApiClient(getConnection(config, connection));
@@ -51,7 +53,7 @@ function requireConfirm(confirm) {
51
53
  // ── Server ──────────────────────────────────────────────────────────────
52
54
  const server = new McpServer({
53
55
  name: 'mcp-mokosuite',
54
- version: '1.0.0',
56
+ version: '1.1.0',
55
57
  });
56
58
  // ════════════════════════════════════════════════════════════════════════
57
59
  // FOUNDATION — connectivity & generic access
@@ -168,6 +170,31 @@ server.tool('client_users_force_logout', 'Terminate ALL user sessions on a MokoS
168
170
  return guard;
169
171
  return formatResponse(await clientFor(connection).remoteControl('POST', `${MSC}/users/force-logout`));
170
172
  });
173
+ // ════════════════════════════════════════════════════════════════════════
174
+ // LAYER 1 — Extensions Store (MokoSuiteExtensions)
175
+ // ════════════════════════════════════════════════════════════════════════
176
+ server.tool('store_catalog_list', 'List the products available in the MokoSuite Extensions store catalog for a site.', { ...ConnectionParam }, async ({ connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog`)));
177
+ server.tool('store_catalog_get', 'Get a single MokoSuite Extensions store product by its catalog id.', {
178
+ id: z.number().describe('Catalog product id'),
179
+ ...ConnectionParam,
180
+ }, async ({ id, connection }) => formatResponse(await clientFor(connection).get(`${MSE}/catalog/${id}`)));
181
+ server.tool('store_install', 'Install a MokoSuite Extensions store product on a site by its catalog id.', {
182
+ id: z.number().describe('Catalog product id to install'),
183
+ ...ConnectionParam,
184
+ }, async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/install`)));
185
+ server.tool('store_update', 'Update an installed MokoSuite Extensions store product on a site by its catalog id.', {
186
+ id: z.number().describe('Catalog product id to update'),
187
+ ...ConnectionParam,
188
+ }, async ({ id, connection }) => formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/update`)));
189
+ server.tool('store_uninstall', 'Uninstall a MokoSuite Extensions store product from a site by its catalog id. Destructive — requires `confirm: true`.', {
190
+ id: z.number().describe('Catalog product id to uninstall'),
191
+ confirm: z.boolean().describe('Must be true — uninstalling removes the product from the target site'),
192
+ ...ConnectionParam,
193
+ }, async ({ id, confirm, connection }) => {
194
+ if (!confirm)
195
+ return text('Refused: set `confirm: true` to uninstall this store product.');
196
+ return formatResponse(await clientFor(connection).post(`${MSE}/catalog/${id}/uninstall`));
197
+ });
171
198
  // ── Start Server ────────────────────────────────────────────────────────
172
199
  async function main() {
173
200
  config = await loadConfig();
package/docs/API.md CHANGED
@@ -11,53 +11,62 @@ BRIEF: MCP tool reference documentation
11
11
 
12
12
  # API Reference
13
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.
14
+ Every tool accepts an optional `connection` parameter naming a MokoSuite site/connection from your config (`~/.mcp_mokosuite.json`); omit it to use the default. Tool endpoints target the MokoSuiteClient Web Services under `/api/index.php/v1/mokosuiteclient/`.
15
+
16
+ Two credential tiers apply per connection:
17
+ - **Read / CRUD** — the site's Joomla Web Services Bearer token (`apiToken`).
18
+ - **Remote-control** (RSA-signed) — the site's `healthApiToken` + HQ's `rsaPrivateKeyPath`. Tools in that tier fail with a clear message if those aren't configured.
19
+
20
+ ## Foundation
21
+
22
+ | Tool | Parameters | Description |
23
+ |------|------------|-------------|
24
+ | `mokosuite_ping` | `connection?` | Check a site's reachability (calls its dashboard endpoint, reports HTTP status). |
25
+ | `list_connections` | — | List configured sites, their role, and which credential tiers are available. |
26
+ | `api_request` | `method`, `endpoint`, `body?`, `params?`, `connection?` | Raw request to any endpoint relative to `/api/index.php` (escape hatch). |
27
+
28
+ ## Layer 0 — Platform (MokoSuiteClient)
29
+
30
+ ### Reads (Joomla API token)
31
+
32
+ | Tool | Parameters | Endpoint |
33
+ |------|------------|----------|
34
+ | `client_get_health` | `connection?` | `GET /v1/mokosuiteclient/health` — full 16-check diagnostics |
35
+ | `client_get_dashboard` | `connection?` | `GET .../dashboard` — health summary, versions, plugin states |
36
+ | `client_list_extensions` | `connection?` | `GET .../extensions` — installed + update info |
37
+ | `client_list_plugins` | `connection?` | `GET .../plugins` — Moko feature plugins + state |
38
+ | `client_list_snapshots` | `connection?` | `GET .../snapshot` |
39
+ | `client_list_users` | `connection?` | `GET .../users` |
40
+ | `client_export_users` | `connection?` | `GET .../users/export` |
41
+
42
+ ### Actions (Joomla API token, `core.manage`)
43
+
44
+ | Tool | Parameters | Endpoint |
45
+ |------|------------|----------|
46
+ | `client_clear_cache` | `connection?` | `POST .../cache` |
47
+ | `client_check_updates` | `connection?` | `POST .../update` |
48
+ | `client_install_extension` | `url`, `connection?` | `POST .../install` — install from remote ZIP (64 MB cap) |
49
+ | `client_toggle_plugin` | `extension_id`, `connection?` | `POST .../plugins` (`task=toggle`) |
50
+ | `client_create_snapshot` | `label?`, `connection?` | `POST .../snapshot` |
51
+ | `client_reset_snapshot` | `name`, `connection?` | `POST .../reset` — restore a baseline |
52
+ | `client_sync_push` | `target?`, `connection?` | `POST .../sync` |
53
+
54
+ ### Remote-control (RSA-signed; requires HQ credentials)
55
+
56
+ Destructive fleet-wide tools require `confirm: true`.
57
+
58
+ | Tool | Parameters | Endpoint |
59
+ |------|------------|----------|
60
+ | `client_provision_reset` | `confirm`, `connection?` | `POST .../provision-reset` |
61
+ | `client_remote_login` | `connection?` | `POST .../remote-login` — one-time login URL (60 s TTL) |
62
+ | `client_users_reset_passwords` | `confirm`, `connection?` | `POST .../users/reset-passwords` |
63
+ | `client_users_reset_2fa` | `confirm`, `connection?` | `POST .../users/reset-2fa` |
64
+ | `client_users_disable_all` | `confirm`, `connection?` | `POST .../users/disable-all` |
65
+ | `client_users_enable_all` | `confirm`, `connection?` | `POST .../users/enable-all` |
66
+ | `client_users_force_logout` | `confirm`, `connection?` | `POST .../users/force-logout` |
58
67
 
59
68
  ## Revision History
60
69
 
61
70
  | Date | Version | Author | Notes |
62
71
  | --- | --- | --- | --- |
63
- | 2026-05-07 | 0.0.1 | jmiller | Initial template API reference |
72
+ | 2026-07-26 | 01.00.00 | Moko Consulting | Foundation + Layer 0 tool reference (24 tools) |