chore: cascade main → dev (3f89873) [skip ci] #3

Merged
jmiller merged 5 commits from main into dev 2026-05-07 20:25:19 +00:00
9 changed files with 1169 additions and 136 deletions
+142 -113
View File
@@ -7,18 +7,18 @@
# INGROUP: MokoStandards.Maintenance
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/cascade-dev.yml.template
# VERSION: 01.00.00
# BRIEF: Forward-merge main → dev after every push to main
# VERSION: 02.00.00
# BRIEF: Forward-merge main → all open branches after every push to main
#
# +========================================================================+
# | CASCADE MAIN → DEV |
# | CASCADE MAIN → ALL BRANCHES |
# +========================================================================+
# | |
# | Triggers on every push to main (PR merges, bot commits, etc.) |
# | |
# | 1. Check if a 'dev' branch exists |
# | 2. Create a PR (main → dev) via Gitea API |
# | 3. Auto-merge if clean; leave open for manual resolution on conflict |
# | 1. List all branches matching: dev, rc/*, beta/*, alpha/* |
# | 2. For each: create PR (main → branch), auto-merge if clean |
# | 3. On conflict: leave PR open for manual resolution |
# | |
# +========================================================================+
@@ -42,143 +42,172 @@ permissions:
jobs:
cascade:
name: Merge main → dev
name: Cascade main → branches
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
!contains(github.event.head_commit.message, '[skip cascade]')
steps:
- name: Check dev branch exists
id: check
- name: Discover target branches
id: branches
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
STATUS=$(curl -sS -o /dev/null -w "%{http_code}" \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/branches/dev")
# Fetch all branches (paginated)
PAGE=1
ALL_BRANCHES=""
while true; do
BATCH=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/branches?page=${PAGE}&limit=50" \
| jq -r '.[].name // empty')
[ -z "$BATCH" ] && break
ALL_BRANCHES="$ALL_BRANCHES $BATCH"
PAGE=$((PAGE + 1))
done
if [ "$STATUS" = "200" ]; then
echo "exists=true" >> "$GITHUB_OUTPUT"
echo "✅ dev branch exists"
# Filter to cascade targets: dev, dev/*, rc/*, beta/*, alpha/*
TARGETS=""
for BRANCH in $ALL_BRANCHES; do
case "$BRANCH" in
dev|dev/*|rc/*|beta/*|alpha/*)
TARGETS="$TARGETS $BRANCH"
;;
esac
done
TARGETS=$(echo "$TARGETS" | xargs) # trim whitespace
if [ -z "$TARGETS" ]; then
echo "targets=" >> "$GITHUB_OUTPUT"
echo "️ No cascade target branches found"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
echo "️ No dev branch found (HTTP ${STATUS}) — skipping cascade"
echo "targets=$TARGETS" >> "$GITHUB_OUTPUT"
COUNT=$(echo "$TARGETS" | wc -w)
echo "📋 Found ${COUNT} target branch(es): ${TARGETS}"
fi
- name: Check if dev is already up to date
if: steps.check.outputs.exists == 'true'
id: diff
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
# Compare main..dev — if ahead_by is 0 there's nothing to cascade
RESPONSE=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/compare/dev...main")
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
if [ "$AHEAD" -eq 0 ]; then
echo "needs_merge=false" >> "$GITHUB_OUTPUT"
echo "✅ dev is already up to date with main"
else
echo "needs_merge=true" >> "$GITHUB_OUTPUT"
echo "️ main is ${AHEAD} commit(s) ahead of dev"
fi
- name: Create cascade PR
if: steps.diff.outputs.needs_merge == 'true'
id: pr
- name: Cascade to all target branches
if: steps.branches.outputs.targets != ''
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
SHORT_SHA="${GITHUB_SHA:0:7}"
TARGETS="${{ steps.branches.outputs.targets }}"
# Check if a cascade PR already exists (main → dev)
EXISTING=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=dev&limit=1")
SUCCESS=0
CONFLICTS=0
SKIPPED=0
FAILED=0
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
for BRANCH in $TARGETS; do
echo ""
echo "═══ main → ${BRANCH} ═══"
if [ "$EXISTING_COUNT" -gt 0 ]; then
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
PR_URL=$(echo "$EXISTING" | jq -r '.[0].html_url')
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
echo "pr_exists=true" >> "$GITHUB_OUTPUT"
echo "️ Cascade PR already exists: ${PR_URL}"
else
RESPONSE=$(curl -sS -w "\n%{http_code}" \
# Check if branch is already up to date
ENCODED_BRANCH=$(echo "$BRANCH" | sed 's|/|%2F|g')
RESPONSE=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/compare/${ENCODED_BRANCH}...main")
AHEAD=$(echo "$RESPONSE" | jq '.total_commits // 0')
if [ "$AHEAD" -eq 0 ]; then
echo " ✅ Already up to date"
SKIPPED=$((SKIPPED + 1))
continue
fi
echo " ️ main is ${AHEAD} commit(s) ahead"
# Check for existing cascade PR
EXISTING=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls?state=open&head=${GITEA_ORG}:main&base=${ENCODED_BRANCH}&limit=1")
EXISTING_COUNT=$(echo "$EXISTING" | jq 'length')
PR_NUMBER=""
if [ "$EXISTING_COUNT" -gt 0 ]; then
PR_NUMBER=$(echo "$EXISTING" | jq -r '.[0].number')
echo " ️ Reusing existing PR #${PR_NUMBER}"
else
# Create cascade PR
PR_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"chore: cascade main → ${BRANCH} (${SHORT_SHA}) [skip ci]\",
\"body\": \"## Automatic cascade\\n\\nForward-merging \`main\` (${SHORT_SHA}) into \`${BRANCH}\`.\\n\\nIf conflicts exist, resolve manually and merge.\\n\\n> Auto-created by **Cascade Main → Dev**.\",
\"head\": \"main\",
\"base\": \"${BRANCH}\"
}" \
"${API}/pulls")
HTTP_CODE=$(echo "$PR_RESPONSE" | tail -1)
BODY=$(echo "$PR_RESPONSE" | sed '$d')
PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty')
if [ "$HTTP_CODE" != "201" ] || [ -z "$PR_NUMBER" ]; then
MSG=$(echo "$BODY" | jq -r '.message // .' 2>/dev/null | head -1)
echo " ❌ Failed to create PR (HTTP ${HTTP_CODE}): ${MSG}"
FAILED=$((FAILED + 1))
continue
fi
echo " ✅ Created PR #${PR_NUMBER}"
fi
# Try auto-merge
PR_DATA=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls/${PR_NUMBER}")
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
if [ "$MERGEABLE" != "true" ]; then
echo " ⚠️ Conflicts — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
continue
fi
MERGE_RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"title\": \"chore: cascade main → dev (${SHORT_SHA}) [skip ci]\",
\"body\": \"## Automatic cascade\n\nForward-merging \`main\` (${SHORT_SHA}) into \`dev\` to keep branches in sync.\n\nIf this PR has conflicts, please resolve them manually and merge.\n\n> Auto-created by the **Cascade Main → Dev** workflow.\",
\"head\": \"main\",
\"base\": \"dev\"
\"Do\": \"merge\",
\"merge_message_field\": \"chore: cascade main → ${BRANCH} [skip ci]\",
\"delete_branch_after_merge\": false
}" \
"${API}/pulls")
"${API}/pulls/${PR_NUMBER}/merge")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
BODY=$(echo "$RESPONSE" | sed '$d')
PR_NUMBER=$(echo "$BODY" | jq -r '.number // empty')
PR_URL=$(echo "$BODY" | jq -r '.html_url // empty')
MERGE_HTTP=$(echo "$MERGE_RESPONSE" | tail -1)
if [ "$HTTP_CODE" = "201" ] && [ -n "$PR_NUMBER" ]; then
echo "pr_number=${PR_NUMBER}" >> "$GITHUB_OUTPUT"
echo "pr_exists=false" >> "$GITHUB_OUTPUT"
echo "✅ Created cascade PR #${PR_NUMBER}: ${PR_URL}"
if [ "$MERGE_HTTP" = "200" ] || [ "$MERGE_HTTP" = "204" ]; then
echo " ✅ Merged — ${BRANCH} is in sync"
SUCCESS=$((SUCCESS + 1))
else
echo "❌ Failed to create PR (HTTP ${HTTP_CODE}): ${BODY}"
exit 1
MERGE_BODY=$(echo "$MERGE_RESPONSE" | sed '$d')
echo " ⚠️ Merge failed (HTTP ${MERGE_HTTP}) — PR #${PR_NUMBER} left open"
CONFLICTS=$((CONFLICTS + 1))
fi
fi
- name: Auto-merge cascade PR
if: steps.pr.outputs.pr_number != ''
env:
GA_TOKEN: ${{ secrets.GA_TOKEN }}
run: |
API="${GITEA_URL}/api/v1/repos/${GITEA_ORG}/${GITEA_REPO}"
PR_NUMBER="${{ steps.pr.outputs.pr_number }}"
# Check if PR is mergeable
PR_DATA=$(curl -sS \
-H "Authorization: token ${GA_TOKEN}" \
"${API}/pulls/${PR_NUMBER}")
MERGEABLE=$(echo "$PR_DATA" | jq -r '.mergeable // false')
if [ "$MERGEABLE" != "true" ]; then
echo "⚠️ PR #${PR_NUMBER} has conflicts — leaving open for manual resolution"
exit 0
fi
# Merge the PR
RESPONSE=$(curl -sS -w "\n%{http_code}" \
-X POST \
-H "Authorization: token ${GA_TOKEN}" \
-H "Content-Type: application/json" \
-d "{
\"Do\": \"merge\",
\"merge_message_field\": \"chore: cascade main → dev [skip ci]\",
\"delete_branch_after_merge\": false
}" \
"${API}/pulls/${PR_NUMBER}/merge")
HTTP_CODE=$(echo "$RESPONSE" | tail -1)
if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "204" ]; then
echo "✅ Cascade PR #${PR_NUMBER} merged — dev is now in sync with main"
else
BODY=$(echo "$RESPONSE" | sed '$d')
echo "⚠️ Merge failed (HTTP ${HTTP_CODE}): ${BODY}"
echo "PR #${PR_NUMBER} left open for manual resolution"
done
# Summary
echo ""
echo "════════════════════════════════════════"
echo " ✅ Merged: ${SUCCESS}"
echo " ⚠️ Conflicts: ${CONFLICTS}"
echo " ⏭️ Up to date: ${SKIPPED}"
echo " ❌ Failed: ${FAILED}"
echo "════════════════════════════════════════"
if [ "$FAILED" -gt 0 ]; then
exit 1
fi
+96
View File
@@ -0,0 +1,96 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
#
# SPDX-License-Identifier: GPL-3.0-or-later
#
# FILE INFORMATION
# DEFGROUP: Gitea.Workflow
# INGROUP: MokoStandards.Security
# REPO: https://git.mokoconsulting.tech/mokoconsulting-tech/MokoStandards-API
# PATH: /templates/workflows/gitleaks.yml.template
# VERSION: 01.00.00
# BRIEF: Secret scanning — detect leaked credentials, API keys, and tokens
#
# +========================================================================+
# | SECRET SCANNING |
# +========================================================================+
# | |
# | Scans commits for leaked secrets using Gitleaks. |
# | |
# | - PR scan: only new commits in the PR |
# | - Scheduled: full repo scan weekly |
# | - Alerts via ntfy on findings |
# | |
# +========================================================================+
name: Secret Scanning
on:
pull_request:
branches:
- main
- 'dev/**'
schedule:
- cron: '0 5 * * 1' # Weekly Monday 05:00 UTC
workflow_dispatch:
permissions:
contents: read
env:
NTFY_URL: ${{ vars.NTFY_URL || 'https://ntfy.mokoconsulting.tech' }}
NTFY_TOPIC: ${{ vars.NTFY_TOPIC || 'gitea-security' }}
jobs:
gitleaks:
name: Gitleaks Secret Scan
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Install Gitleaks
run: |
GITLEAKS_VERSION="8.21.2"
curl -sSL "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" \
| tar -xz -C /usr/local/bin gitleaks
gitleaks version
- name: Scan for secrets
id: scan
run: |
echo "### Secret Scanning" >> $GITHUB_STEP_SUMMARY
ARGS="--source . --verbose --report-format json --report-path /tmp/gitleaks-report.json"
if [ "${{ github.event_name }}" = "pull_request" ]; then
# Scan only PR commits
ARGS="$ARGS --log-opts=${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
echo "Scanning PR commits only" >> $GITHUB_STEP_SUMMARY
else
echo "Full repository scan" >> $GITHUB_STEP_SUMMARY
fi
if gitleaks detect $ARGS 2>&1; then
echo "result=clean" >> "$GITHUB_OUTPUT"
echo "**No secrets detected.**" >> $GITHUB_STEP_SUMMARY
else
echo "result=found" >> "$GITHUB_OUTPUT"
FINDINGS=$(jq length /tmp/gitleaks-report.json 2>/dev/null || echo "unknown")
echo "**${FINDINGS} potential secret(s) detected.**" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "Review the findings and rotate any exposed credentials immediately." >> $GITHUB_STEP_SUMMARY
exit 1
fi
- name: Notify on findings
if: failure() && steps.scan.outputs.result == 'found'
run: |
REPO="${{ github.event.repository.name }}"
curl -sS \
-H "Title: ${REPO} — secrets detected in code" \
-H "Tags: rotating_light,key" \
-H "Priority: urgent" \
-d "Gitleaks found potential secrets. Review and rotate credentials immediately." \
"${NTFY_URL}/${NTFY_TOPIC}" || true
+278
View File
@@ -0,0 +1,278 @@
# MCP Server Auto-Release
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# MCP-specific release pipeline that builds TypeScript, runs validation,
# attaches the compiled dist/ as a release artifact, and creates a GitHub
# Release with tool inventory in the release notes.
#
# This replaces the generic auto-release.yml for MCP server repos.
name: MCP Release
on:
push:
branches:
- main
paths:
- 'src/**'
- 'package.json'
- 'tsconfig.json'
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
permissions:
contents: write
issues: write
jobs:
build-and-release:
name: Build, Validate & Release
runs-on: ubuntu-latest
if: >-
!contains(github.event.head_commit.message, '[skip ci]') &&
github.actor != 'github-actions[bot]'
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
token: ${{ secrets.GH_TOKEN || github.token }}
fetch-depth: 0
# ── Build ────────────────────────────────────────────────────────
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: TypeScript compile check
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Verify dist output
run: |
for f in index.js client.js config.js types.js; do
test -f "dist/${f}" || (echo "ERROR: dist/${f} not found" && exit 1)
done
echo "✓ All dist files present"
# ── Tool Inventory ───────────────────────────────────────────────
- name: Generate tool inventory
id: tools
run: |
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || echo "0")
echo "count=${TOOL_COUNT}" >> "$GITHUB_OUTPUT"
# Extract tool names
TOOL_LIST=$(grep -oE "'[a-z_]+'" src/index.ts | head -100 | tr -d "'" | sort -u)
echo "Tools registered: ${TOOL_COUNT}"
# Generate inventory for release notes
echo "## Tool Inventory (${TOOL_COUNT} tools)" > /tmp/tool-inventory.md
echo "" >> /tmp/tool-inventory.md
grep -B0 -A1 "server\.tool(" src/index.ts | grep -oE "'[^']+'" | while IFS= read -r name; do
read -r desc 2>/dev/null || true
CLEAN_NAME=$(echo "$name" | tr -d "'")
CLEAN_DESC=$(echo "$desc" | tr -d "'" | sed 's/,$//')
if [ -n "$CLEAN_NAME" ] && [ -n "$CLEAN_DESC" ]; then
echo "- \`${CLEAN_NAME}\` — ${CLEAN_DESC}" >> /tmp/tool-inventory.md
fi
done
# ── Version ──────────────────────────────────────────────────────
- name: Setup MokoStandards tools
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
COMPOSER_AUTH: '{"github-oauth":{"github.com":"${{ secrets.GH_TOKEN || github.token }}"}}'
run: |
git clone --depth 1 --branch version/04 --quiet \
"https://x-access-token:${GH_TOKEN}@github.com/mokoconsulting-tech/MokoStandards.git" \
/tmp/mokostandards
cd /tmp/mokostandards
composer install --no-dev --no-interaction --quiet
- name: Read version from README.md
id: version
run: |
VERSION=$(php /tmp/mokostandards/api/cli/version_read.php --path . 2>/dev/null)
if [ -z "$VERSION" ]; then
echo "No VERSION in README.md — skipping release"
echo "skip=true" >> "$GITHUB_OUTPUT"
exit 0
fi
MAJOR=$(echo "$VERSION" | awk -F. '{print $1}')
MINOR=$(echo "$VERSION" | awk -F. '{printf "%s.%s", $1, $2}')
PATCH=$(echo "$VERSION" | awk -F. '{print $3}')
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
echo "branch=version/${MAJOR}" >> "$GITHUB_OUTPUT"
echo "major=$MAJOR" >> "$GITHUB_OUTPUT"
echo "minor=$MINOR" >> "$GITHUB_OUTPUT"
echo "release_tag=v${MAJOR}" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "00" ]; then
echo "skip=true" >> "$GITHUB_OUTPUT"
else
echo "skip=false" >> "$GITHUB_OUTPUT"
if [ "$PATCH" = "01" ]; then
echo "is_first=true" >> "$GITHUB_OUTPUT"
else
echo "is_first=false" >> "$GITHUB_OUTPUT"
fi
fi
- name: Check if already released
if: steps.version.outputs.skip != 'true'
id: check
run: |
TAG="${{ steps.version.outputs.release_tag }}"
TAG_EXISTS=false
git rev-parse "$TAG" >/dev/null 2>&1 && TAG_EXISTS=true
echo "tag_exists=$TAG_EXISTS" >> "$GITHUB_OUTPUT"
# ── Release Artifact ─────────────────────────────────────────────
- name: Package dist
if: steps.version.outputs.skip != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
REPO_NAME="${{ github.event.repository.name }}"
tar -czf "/tmp/${REPO_NAME}-${VERSION}.tar.gz" -C dist .
echo "artifact=/tmp/${REPO_NAME}-${VERSION}.tar.gz" >> "$GITHUB_OUTPUT"
# ── Version Updates ──────────────────────────────────────────────
- name: Set platform version
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
php /tmp/mokostandards/api/cli/version_set_platform.php \
--path . --version "$VERSION" --branch main
- name: Update version badges
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
run: |
VERSION="${{ steps.version.outputs.version }}"
find . -name "*.md" ! -path "./.git/*" ! -path "./vendor/*" | while read -r f; do
if grep -q '\[VERSION:' "$f" 2>/dev/null; then
sed -i "s/\[VERSION:[[:space:]]*[0-9]\{2\}\.[0-9]\{2\}\.[0-9]\{2\}\]/[VERSION: ${VERSION}]/" "$f"
fi
done
- name: Commit release changes
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
run: |
if git diff --quiet && git diff --cached --quiet; then
echo "No changes to commit"
exit 0
fi
VERSION="${{ steps.version.outputs.version }}"
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add -A
git commit -m "chore(release): build ${VERSION} [skip ci]" \
--author="github-actions[bot] <github-actions[bot]@users.noreply.github.com>"
git push
# ── Version Branch ───────────────────────────────────────────────
- name: Archive version branch
if: steps.check.outputs.tag_exists != 'true'
run: |
BRANCH="${{ steps.version.outputs.branch }}"
git push origin HEAD:"$BRANCH" --force
echo "Updated archive branch: ${BRANCH}" >> $GITHUB_STEP_SUMMARY
# ── Tag & Release ────────────────────────────────────────────────
- name: Create git tag
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true' &&
steps.version.outputs.is_first == 'true'
run: |
TAG="${{ steps.version.outputs.release_tag }}"
if ! git rev-parse "$TAG" >/dev/null 2>&1; then
git tag "$TAG"
git push origin "$TAG"
fi
- name: GitHub Release
if: >-
steps.version.outputs.skip != 'true' &&
steps.check.outputs.tag_exists != 'true'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
VERSION="${{ steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MAJOR="${{ steps.version.outputs.major }}"
BRANCH="${{ steps.version.outputs.branch }}"
TOOL_COUNT="${{ steps.tools.outputs.count }}"
REPO_NAME="${{ github.event.repository.name }}"
# Build release notes
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null)
[ -z "$NOTES" ] && NOTES="Release ${VERSION}"
{
echo "$NOTES"
echo ""
echo "---"
echo ""
echo "### MCP Server Info"
echo "- **Tools registered**: ${TOOL_COUNT}"
echo "- **Node.js**: 20+"
echo "- **MCP SDK**: $(node -p \"require('./package.json').dependencies['@modelcontextprotocol/sdk']\" 2>/dev/null || echo 'unknown')"
echo ""
cat /tmp/tool-inventory.md 2>/dev/null || true
} > /tmp/release_notes.md
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
ARTIFACT="/tmp/${REPO_NAME}-${VERSION}.tar.gz"
if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH" \
"$ARTIFACT"
echo "Release created: ${RELEASE_TAG} (${VERSION})" >> $GITHUB_STEP_SUMMARY
else
gh release edit "$RELEASE_TAG" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md
gh release upload "$RELEASE_TAG" "$ARTIFACT" --clobber 2>/dev/null || true
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
fi
# ── Summary ──────────────────────────────────────────────────────
- name: Pipeline Summary
if: always()
run: |
VERSION="${{ steps.version.outputs.version }}"
TOOL_COUNT="${{ steps.tools.outputs.count }}"
if [ "${{ steps.version.outputs.skip }}" = "true" ]; then
echo "## Release Skipped" >> $GITHUB_STEP_SUMMARY
else
echo "" >> $GITHUB_STEP_SUMMARY
echo "## MCP Release Complete" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "| Detail | Value |" >> $GITHUB_STEP_SUMMARY
echo "|--------|-------|" >> $GITHUB_STEP_SUMMARY
echo "| Version | \`${VERSION}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tools | ${TOOL_COUNT} |" >> $GITHUB_STEP_SUMMARY
echo "| Branch | \`${{ steps.version.outputs.branch }}\` |" >> $GITHUB_STEP_SUMMARY
echo "| Tag | \`${{ steps.version.outputs.release_tag }}\` |" >> $GITHUB_STEP_SUMMARY
fi
+61
View File
@@ -0,0 +1,61 @@
# MCP Server Build & Validation
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Builds the MCP server, validates TypeScript compilation, and checks
# that tools are properly registered with valid Zod schemas.
name: MCP Build & Validate
on:
push:
branches: [main, dev/**]
paths: ['src/**', 'package.json', 'tsconfig.json']
pull_request:
branches: [main]
paths: ['src/**', 'package.json', 'tsconfig.json']
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22]
steps:
- uses: actions/checkout@v4
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm ci
- name: TypeScript compile
run: npx tsc --noEmit
- name: Build
run: npm run build
- name: Verify dist output exists
run: |
test -f dist/index.js || (echo "ERROR: dist/index.js not found" && exit 1)
test -f dist/client.js || (echo "ERROR: dist/client.js not found" && exit 1)
test -f dist/config.js || (echo "ERROR: dist/config.js not found" && exit 1)
test -f dist/types.js || (echo "ERROR: dist/types.js not found" && exit 1)
echo "✓ All required dist files present"
- name: Verify shebang in index.js
run: |
head -1 dist/index.js | grep -q "#!/usr/bin/env node" || echo "WARNING: Missing shebang in dist/index.js"
- name: Count registered tools
run: |
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || true)
echo "Registered tools: ${TOOL_COUNT}"
if [ "${TOOL_COUNT}" -eq 0 ]; then
echo "ERROR: No tools registered in src/index.ts"
exit 1
fi
+105
View File
@@ -0,0 +1,105 @@
# MCP SDK Version Check
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Weekly check for MCP SDK updates. Creates an issue when a new version
# of @modelcontextprotocol/sdk is available.
name: MCP SDK Version Check
on:
schedule:
- cron: '0 9 * * 1' # Every Monday at 9am UTC
workflow_dispatch:
jobs:
check-sdk:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
- name: Check for SDK updates
id: sdk-check
run: |
CURRENT=$(node -p "require('./package.json').dependencies['@modelcontextprotocol/sdk']" | sed 's/[\^~]//')
LATEST=$(npm view @modelcontextprotocol/sdk version 2>/dev/null || echo "unknown")
echo "current=${CURRENT}" >> $GITHUB_OUTPUT
echo "latest=${LATEST}" >> $GITHUB_OUTPUT
if [ "${CURRENT}" != "${LATEST}" ] && [ "${LATEST}" != "unknown" ]; then
echo "update_available=true" >> $GITHUB_OUTPUT
echo "MCP SDK update available: ${CURRENT} → ${LATEST}"
else
echo "update_available=false" >> $GITHUB_OUTPUT
echo "MCP SDK is up to date: ${CURRENT}"
fi
- name: Check for Zod updates
id: zod-check
run: |
CURRENT=$(node -p "require('./package.json').dependencies['zod']" | sed 's/[\^~]//')
LATEST=$(npm view zod version 2>/dev/null || echo "unknown")
echo "current=${CURRENT}" >> $GITHUB_OUTPUT
echo "latest=${LATEST}" >> $GITHUB_OUTPUT
if [ "${CURRENT}" != "${LATEST}" ] && [ "${LATEST}" != "unknown" ]; then
echo "update_available=true" >> $GITHUB_OUTPUT
else
echo "update_available=false" >> $GITHUB_OUTPUT
fi
- name: Create update issue
if: steps.sdk-check.outputs.update_available == 'true'
uses: actions/github-script@v7
with:
script: |
const title = `chore(deps): update @modelcontextprotocol/sdk ${process.env.CURRENT} → ${process.env.LATEST}`;
const body = [
'## MCP SDK Update Available',
'',
`| Package | Current | Latest |`,
`|---------|---------|--------|`,
`| @modelcontextprotocol/sdk | ${process.env.CURRENT} | ${process.env.LATEST} |`,
`| zod | ${process.env.ZOD_CURRENT} | ${process.env.ZOD_LATEST} |`,
'',
'### Steps',
'1. Update package.json',
'2. Run `npm install`',
'3. Run `npm run build` to verify compilation',
'4. Test all tools against target API',
'',
'### Changelog',
`https://github.com/modelcontextprotocol/typescript-sdk/releases`,
].join('\n');
// Check for existing open issue
const existing = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'api-change',
});
const alreadyExists = existing.data.some(i => i.title.includes('@modelcontextprotocol/sdk'));
if (!alreadyExists) {
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['api-change', 'chore'],
});
}
env:
CURRENT: ${{ steps.sdk-check.outputs.current }}
LATEST: ${{ steps.sdk-check.outputs.latest }}
ZOD_CURRENT: ${{ steps.zod-check.outputs.current }}
ZOD_LATEST: ${{ steps.zod-check.outputs.latest }}
+57
View File
@@ -0,0 +1,57 @@
# MCP Tool Inventory
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
#
# Generates a tool inventory report on each push to main.
# Extracts tool names, descriptions, and parameter counts from src/index.ts.
name: MCP Tool Inventory
on:
push:
branches: [main]
paths: ['src/index.ts']
workflow_dispatch:
jobs:
inventory:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Generate tool inventory
run: |
echo "# MCP Tool Inventory" > TOOLS.md
echo "" >> TOOLS.md
echo "Auto-generated from \`src/index.ts\` on $(date -u +%Y-%m-%dT%H:%M:%SZ)" >> TOOLS.md
echo "" >> TOOLS.md
# Count tools
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || true)
echo "**Total tools: ${TOOL_COUNT}**" >> TOOLS.md
echo "" >> TOOLS.md
# Extract tool names and descriptions
echo "| Tool | Description |" >> TOOLS.md
echo "|------|-------------|" >> TOOLS.md
grep -A1 "server\.tool(" src/index.ts | grep -E "^\s*'" | while read -r line; do
TOOL_NAME=$(echo "$line" | sed "s/.*'\([^']*\)'.*/\1/")
# Get next line for description
DESC=$(grep -A2 "'${TOOL_NAME}'" src/index.ts | grep -E "^\s*'" | tail -1 | sed "s/.*'\([^']*\)'.*/\1/" || echo "")
echo "| \`${TOOL_NAME}\` | ${DESC} |" >> TOOLS.md
done
echo "" >> TOOLS.md
echo "---" >> TOOLS.md
echo "*Generated by MCP Tool Inventory workflow*" >> TOOLS.md
cat TOOLS.md
- name: Upload inventory artifact
uses: actions/upload-artifact@v4
with:
name: tool-inventory
path: TOOLS.md
retention-days: 90
+81 -10
View File
@@ -130,7 +130,7 @@ Each connection needs the Joomla site's base URL and an API token (generated in
| `apiToken` | Yes | Joomla API token (Bearer auth) |
| `insecure` | No | Skip TLS verification for self-signed certs |
## Tools
## Tools (67)
### Articles
@@ -162,6 +162,16 @@ Each connection needs the Joomla site's base URL and an API token (generated in
| `joomla_user_delete` | Delete a user |
| `joomla_user_groups_list` | List user groups |
### Contacts
| Tool | Description |
|------|-------------|
| `joomla_contacts_list` | List contacts with optional search |
| `joomla_contact_get` | Get a single contact by ID |
| `joomla_contact_create` | Create a new contact |
| `joomla_contact_update` | Update an existing contact |
| `joomla_contact_delete` | Delete a contact |
### Menus
| Tool | Description |
@@ -169,6 +179,73 @@ Each connection needs the Joomla site's base URL and an API token (generated in
| `joomla_menus_list` | List menu types |
| `joomla_menu_items_list` | List menu items for a menu type |
| `joomla_menu_item_get` | Get a single menu item by ID |
| `joomla_menu_item_create` | Create a new menu item |
| `joomla_menu_item_update` | Update a menu item |
| `joomla_menu_item_delete` | Delete a menu item |
### Tags
| Tool | Description |
|------|-------------|
| `joomla_tags_list` | List tags with optional search |
| `joomla_tag_get` | Get a single tag by ID |
| `joomla_tag_create` | Create a tag |
| `joomla_tag_update` | Update a tag |
| `joomla_tag_delete` | Delete a tag |
### Custom Fields
| Tool | Description |
|------|-------------|
| `joomla_fields_list` | List custom fields for a context |
| `joomla_field_get` | Get a single custom field by ID |
| `joomla_field_create` | Create a custom field |
| `joomla_field_delete` | Delete a custom field |
### Banners
| Tool | Description |
|------|-------------|
| `joomla_banners_list` | List banners |
| `joomla_banner_get` | Get a single banner by ID |
| `joomla_banner_create` | Create a new banner |
| `joomla_banner_delete` | Delete a banner |
| `joomla_banner_clients_list` | List banner clients |
### Newsfeeds
| Tool | Description |
|------|-------------|
| `joomla_newsfeeds_list` | List newsfeeds |
| `joomla_newsfeed_get` | Get a single newsfeed by ID |
| `joomla_newsfeed_create` | Create a new newsfeed |
| `joomla_newsfeed_delete` | Delete a newsfeed |
### Messages
| Tool | Description |
|------|-------------|
| `joomla_messages_list` | List private messages |
| `joomla_message_get` | Get a single private message |
| `joomla_message_send` | Send a private message to a user |
| `joomla_message_delete` | Delete a private message |
### Media
| Tool | Description |
|------|-------------|
| `joomla_media_list` | List media files in a folder |
| `joomla_media_file_get` | Get metadata for a specific media file |
| `joomla_media_file_delete` | Delete a media file |
| `joomla_media_folder_create` | Create a new media folder |
### Redirects
| Tool | Description |
|------|-------------|
| `joomla_redirects_list` | List URL redirects |
| `joomla_redirect_create` | Create a URL redirect |
| `joomla_redirect_delete` | Delete a URL redirect |
### Plugins
@@ -184,15 +261,9 @@ Each connection needs the Joomla site's base URL and an API token (generated in
| `joomla_modules_list` | List site or admin modules |
| `joomla_templates_list` | List site or admin templates |
| `joomla_languages_list` | List installed content languages |
| `joomla_tags_list` | List tags |
| `joomla_tag_create` | Create a tag |
| `joomla_fields_list` | List custom fields for a context |
| `joomla_contacts_list` | List contacts |
| `joomla_banners_list` | List banners |
| `joomla_newsfeeds_list` | List newsfeeds |
| `joomla_messages_list` | List private messages |
| `joomla_message_get` | Get a single private message |
| `joomla_media_list` | List media files in a folder |
| `joomla_content_history_list` | List version history for a content item |
| `joomla_checkin` | Check in (unlock) a checked-out content item |
| `joomla_associations_list` | List multilingual associations for a content item |
| `joomla_config_get` | Get application configuration |
| `joomla_config_update` | Update application configuration values |
| `joomla_api_request` | Raw API request to any Joomla endpoint |
+323 -13
View File
@@ -10,8 +10,8 @@ DEFGROUP: joomla-api-mcp.Documentation
INGROUP: joomla-api-mcp
REPO: https://git.mokoconsulting.tech/MokoConsulting/joomla-api-mcp
PATH: /docs/API.md
VERSION: 00.00.01
BRIEF: MCP tool reference documentation
VERSION: 01.01.00
BRIEF: MCP tool reference documentation — 67 tools
-->
# API Reference
@@ -156,6 +156,59 @@ Delete a user.
### `joomla_user_groups_list`
List all user groups. No parameters.
## Contacts
### `joomla_contacts_list`
List contacts with optional search.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search` | string | No | Search in name |
### `joomla_contact_get`
Get a single contact by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Contact ID |
### `joomla_contact_create`
Create a new contact.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Contact name |
| `alias` | string | No | URL alias |
| `catid` | number | No | Category ID |
| `email_to` | string | No | Email address |
| `telephone` | string | No | Phone number |
| `address` | string | No | Street address |
| `suburb` | string | No | City/suburb |
| `state` | string | No | State/province |
| `postcode` | string | No | Postal code |
| `country_id` | number | No | Country ID |
| `published` | number | No | 1=published, 0=unpublished |
| `language` | string | No | Language code (default `"*"`) |
### `joomla_contact_update`
Update an existing contact.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Contact ID |
| `name` | string | No | Contact name |
| `email_to` | string | No | Email address |
| `telephone` | string | No | Phone number |
| `address` | string | No | Street address |
| `published` | number | No | 1=published, 0=unpublished |
### `joomla_contact_delete`
Delete a contact.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Contact ID |
## Menus
### `joomla_menus_list`
@@ -171,6 +224,38 @@ List menu items for a menu type.
### `joomla_menu_item_get`
Get a single menu item by ID.
### `joomla_menu_item_create`
Create a new menu item.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | Menu item title |
| `menutype` | string | Yes | Menu type alias (e.g. `"mainmenu"`) |
| `type` | string | Yes | Item type (`"component"`, `"url"`, `"alias"`, `"separator"`, `"heading"`) |
| `link` | string | No | URL or component link |
| `parent_id` | number | No | Parent menu item ID (default 1 = root) |
| `published` | number | No | 1=published, 0=unpublished |
| `access` | number | No | Access level ID |
| `language` | string | No | Language code (default `"*"`) |
### `joomla_menu_item_update`
Update a menu item.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Menu item ID |
| `title` | string | No | New title |
| `link` | string | No | New link URL |
| `published` | number | No | 1=published, 0=unpublished |
| `parent_id` | number | No | New parent ID |
### `joomla_menu_item_delete`
Delete a menu item.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Menu item ID |
## Plugins
### `joomla_plugins_list`
@@ -208,14 +293,22 @@ List site or admin templates.
|-----------|------|----------|-------------|
| `client_id` | `"0"` / `"1"` | No | 0=site, 1=admin |
## Other Tools
### `joomla_languages_list`
List installed content languages.
## Tags
### `joomla_tags_list`
List tags with optional search.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search` | string | No | Search in title |
### `joomla_tag_get`
Get a single tag by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Tag ID |
### `joomla_tag_create`
Create a tag.
@@ -224,24 +317,153 @@ Create a tag.
| `title` | string | Yes | Tag title |
| `parent_id` | number | No | Parent tag ID |
### `joomla_tag_update`
Update a tag.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Tag ID |
| `title` | string | No | New tag title |
| `published` | number | No | 1=published, 0=unpublished |
### `joomla_tag_delete`
Delete a tag.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Tag ID |
## Custom Fields
### `joomla_fields_list`
List custom fields for a context (default `"com_content.article"`).
### `joomla_contacts_list`
List contacts with optional search.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `context` | string | No | Context (default `"com_content.article"`) |
### `joomla_field_get`
Get a single custom field by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Field ID |
### `joomla_field_create`
Create a custom field.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `title` | string | Yes | Field title |
| `name` | string | Yes | Field name (system identifier) |
| `type` | string | Yes | Field type (text, textarea, list, radio, checkboxes, etc.) |
| `context` | string | No | Context (default `"com_content.article"`) |
| `label` | string | No | Display label |
| `description` | string | No | Field description |
| `required` | number | No | 1=required, 0=optional |
| `state` | number | No | 1=published, 0=unpublished |
### `joomla_field_delete`
Delete a custom field.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Field ID |
## Banners
### `joomla_banners_list`
List banners.
List banners. No parameters.
### `joomla_banner_get`
Get a single banner by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Banner ID |
### `joomla_banner_create`
Create a new banner.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Banner name |
| `catid` | number | No | Category ID |
| `clickurl` | string | No | Click URL |
| `custombannercode` | string | No | Custom HTML/code |
| `state` | number | No | 1=published, 0=unpublished |
### `joomla_banner_delete`
Delete a banner.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Banner ID |
### `joomla_banner_clients_list`
List banner clients. No parameters.
## Newsfeeds
### `joomla_newsfeeds_list`
List newsfeeds.
List newsfeeds. No parameters.
### `joomla_newsfeed_get`
Get a single newsfeed by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Newsfeed ID |
### `joomla_newsfeed_create`
Create a new newsfeed.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `name` | string | Yes | Feed name |
| `link` | string | Yes | Feed URL |
| `catid` | number | Yes | Category ID |
| `numarticles` | number | No | Number of articles to display |
| `published` | number | No | 1=published, 0=unpublished |
| `language` | string | No | Language code (default `"*"`) |
### `joomla_newsfeed_delete`
Delete a newsfeed.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Newsfeed ID |
## Messages
### `joomla_messages_list`
List private messages.
List private messages. No parameters.
### `joomla_message_get`
Get a single private message by ID.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Message ID |
### `joomla_message_send`
Send a private message to a Joomla user.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `user_id_to` | number | Yes | Recipient user ID |
| `subject` | string | Yes | Message subject |
| `message` | string | Yes | Message body |
### `joomla_message_delete`
Delete a private message.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Message ID |
## Media
### `joomla_media_list`
List media files in a folder.
@@ -249,8 +471,88 @@ List media files in a folder.
|-----------|------|----------|-------------|
| `path` | string | No | Folder path relative to media root |
### `joomla_media_file_get`
Get metadata for a specific media file.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | string | Yes | File path relative to media root (e.g. `"images/logo.png"`) |
### `joomla_media_file_delete`
Delete a media file.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | string | Yes | File path relative to media root |
### `joomla_media_folder_create`
Create a new media folder.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `path` | string | Yes | Full folder path to create (e.g. `"images/photos/2026"`) |
## Redirects
### `joomla_redirects_list`
List URL redirects.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `search` | string | No | Search in old URL |
| `state` | `"0"` / `"1"` / `"2"` / `"-2"` | No | 0=disabled, 1=enabled, 2=archived, -2=trashed |
### `joomla_redirect_create`
Create a URL redirect.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `old_url` | string | Yes | Source URL to redirect from |
| `new_url` | string | Yes | Destination URL to redirect to |
| `status_code` | `"301"` / `"302"` | No | 301=permanent, 302=temporary (default 301) |
| `published` | number | No | 1=enabled, 0=disabled |
### `joomla_redirect_delete`
Delete a URL redirect.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `id` | number | Yes | Redirect ID |
## Content History
### `joomla_content_history_list`
List version history for a content item.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `type_alias` | string | Yes | Content type alias (e.g. `"com_content.article"`) |
| `item_id` | number | Yes | Item ID |
## Checkin
### `joomla_checkin`
Check in (unlock) a content item that is checked out.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `context` | string | Yes | Context (e.g. `"com_content.article"`) |
| `id` | number | Yes | Item ID to check in |
## Associations
### `joomla_associations_list`
List multilingual associations for a content item.
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `context` | string | Yes | Context (e.g. `"com_content.article"`) |
| `id` | number | Yes | Item ID to get associations for |
## Configuration
### `joomla_config_get`
Get application configuration.
Get application configuration. No parameters.
### `joomla_config_update`
Update application configuration values.
@@ -259,6 +561,13 @@ Update application configuration values.
|-----------|------|----------|-------------|
| `settings` | object | Yes | Key-value pairs of settings to update |
## Languages
### `joomla_languages_list`
List installed content languages. No parameters.
## Generic
### `joomla_api_request`
Make a raw API request to any Joomla Web Services endpoint.
@@ -276,4 +585,5 @@ List all configured connections. No parameters.
| Date | Version | Author | Notes |
| --- | --- | --- | --- |
| 2026-04-23 | 0.0.1 | jmiller | Initial API reference |
| 2026-04-23 | 0.0.1 | jmiller | Initial API reference (36 tools) |
| 2026-05-07 | 0.1.0 | jmiller | Expanded to 67 tools — full CRUD for contacts, banners, newsfeeds, tags, fields, menu items, messages, media, redirects, associations, checkin, content history |
+26
View File
@@ -0,0 +1,26 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"schedule:weekly",
":disableDependencyDashboard"
],
"labels": ["dependencies"],
"automerge": false,
"platformAutomerge": false,
"rangeStrategy": "bump",
"packageRules": [
{
"matchUpdateTypes": ["patch"],
"automerge": true
},
{
"matchManagers": ["composer"],
"enabled": true
},
{
"matchManagers": ["npm"],
"enabled": true
}
]
}