release: source-dir standardization + npm two-zip release (dev -> main) #39

Merged
jmiller merged 10 commits from dev into main 2026-07-18 18:24:38 +00:00
16 changed files with 344 additions and 45 deletions
@@ -0,0 +1,108 @@
# Copyright (C) 2026 Moko Consulting <hello@mokoconsulting.tech>
# SPDX-License-Identifier: GPL-3.0-or-later
name: "Resolve source dir"
description: >-
Resolve the repository source directory from the MokoGIT metadata API
(entry_point) and cross-check the Makefile SRC_DIR, erroring on conflict.
Falls back to source/ when neither is available.
inputs:
mokogit_token:
description: "MokoGIT API token used for the metadata lookup (secrets are not accessible inside composite actions, so pass it in)."
required: false
default: ""
strict:
description: >-
When 'true', an unreachable/invalid metadata API is a hard error
(refuse to guess). When 'false', warn and treat entry_point as absent.
A genuine metadata-vs-Makefile conflict always errors regardless.
required: false
default: "true"
outputs:
source_dir:
description: "Resolved source directory with trailing slash (e.g. source/)"
value: ${{ steps.resolve.outputs.source_dir }}
source_glob:
description: "Recursive glob for the source directory (e.g. source/**)"
value: ${{ steps.resolve.outputs.source_glob }}
runs:
using: "composite"
steps:
- id: resolve
shell: bash
run: |
set -euo pipefail
STRICT='${{ inputs.strict }}'
normalize() {
# Trim whitespace, strip surrounding quotes, ensure single trailing slash.
# A resolved value of "." / "./" (repo root) is treated as invalid → empty.
local v="$1"
v="$(printf '%s' "$v" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
v="${v%\"}"; v="${v#\"}"
v="${v%\'}"; v="${v#\'}"
v="$(printf '%s' "$v" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')"
[ -z "$v" ] && { printf ''; return; }
v="${v%/}/"
if [ "$v" = "./" ] || [ "$v" = "." ]; then
printf ''
return
fi
printf '%s' "$v"
}
# --- Metadata entry_point --------------------------------------------
ENTRY_POINT=""
META_DIR=""
if [ -n "${MOKOGIT_URL:-}" ] && [ -n "${GIT_ORG:-}" ] && [ -n "${GIT_REPO:-}" ]; then
URL="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}/metadata"
CURL_RC=0
META=$(curl -sf -H "Authorization: token ${{ inputs.mokogit_token }}" "$URL") || CURL_RC=$?
if [ "$CURL_RC" -ne 0 ]; then
if [ "$STRICT" = "true" ]; then
echo "::error::metadata API unreachable (curl $CURL_RC); refusing to guess source dir"
exit 1
else
echo "::warning::metadata unreachable; treating entry_point as absent"
META_DIR=""
fi
else
ENTRY_POINT=$(printf '%s' "$META" | python3 -c "import json,sys;print(json.load(sys.stdin).get('entry_point','') or '')") || { echo "::error::metadata not valid JSON"; exit 1; }
META_DIR="$(normalize "$ENTRY_POINT")"
fi
fi
# --- Makefile SRC_DIR -------------------------------------------------
MAKE_DIR=""
if [ -f Makefile ]; then
RAW=$(awk -F':= *' '/^SRC_DIR[[:space:]]*:=/{print $2}' Makefile | head -n1)
if [ -z "$RAW" ]; then
RAW=$(awk -F'\\?= *' '/^SRC_DIR[[:space:]]*\?=/{print $2}' Makefile | head -n1)
fi
MAKE_DIR="$(normalize "$RAW")"
fi
# --- Reconcile --------------------------------------------------------
# A genuine conflict ALWAYS errors, regardless of strict.
SOURCE_DIR=""
if [ -n "$META_DIR" ] && [ -n "$MAKE_DIR" ]; then
if [ "$META_DIR" != "$MAKE_DIR" ]; then
echo "::error::source dir conflict: metadata entry_point='${META_DIR}' vs Makefile SRC_DIR='${MAKE_DIR}'"
exit 1
fi
SOURCE_DIR="$META_DIR"
elif [ -n "$META_DIR" ]; then
SOURCE_DIR="$META_DIR"
elif [ -n "$MAKE_DIR" ]; then
SOURCE_DIR="$MAKE_DIR"
else
SOURCE_DIR="source/"
fi
echo "Resolved source_dir: ${SOURCE_DIR}"
echo "source_dir=${SOURCE_DIR}" >> "$GITHUB_OUTPUT"
echo "source_glob=${SOURCE_DIR}**" >> "$GITHUB_OUTPUT"
+79 -19
View File
@@ -3,8 +3,8 @@
# 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.
# attaches a full-repository zip and a source-directory zip as release
# artifacts, and creates a GitHub Release with tool inventory in the notes.
#
# This replaces the generic auto-release.yml for MCP server repos.
@@ -15,6 +15,9 @@ on:
branches:
- main
paths:
# Transitional: match both the new 'source/' layout and legacy 'src/'.
# This static YAML filter cannot use the resolve-source-dir output.
- 'source/**'
- 'src/**'
- 'package.json'
- 'tsconfig.json'
@@ -51,6 +54,18 @@ jobs:
- name: Install dependencies
run: npm ci
# ── Resolve source directory ─────────────────────────────────────
# Resolves the canonical source dir (e.g. 'source/' or 'src/') via the
# shared composite action. Its 'source_dir' output has a trailing slash.
# Note: the action lives on feature/ci-resolve-source-dir; this path
# resolves once that branch merges.
- name: Resolve source directory
id: srcdir
uses: ./.mokogit/actions/resolve-source-dir
with:
mokogit_token: ${{ secrets.MOKOGIT_TOKEN }}
strict: 'true'
- name: TypeScript compile check
run: npx tsc --noEmit
@@ -68,17 +83,30 @@ jobs:
- name: Generate tool inventory
id: tools
run: |
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || echo "0")
# Resolve the entrypoint from the source dir output, fall back to
# legacy src/index.ts if the resolved file is missing.
SRC="${{ steps.srcdir.outputs.source_dir }}"
INDEX="${SRC}index.ts"
if [ ! -f "$INDEX" ]; then
echo "::warning::resolved entrypoint $INDEX missing; falling back to src/index.ts"
INDEX="src/index.ts"
fi
# Fail loud on an unreadable entry file so it isn't silently
# counted as 0 tools.
[ -r "$INDEX" ] || { echo "::error::entry file unreadable"; exit 1; }
TOOL_COUNT=$(grep -c "server\.tool(" "$INDEX" || 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)
TOOL_LIST=$(grep -oE "'[a-z_]+'" "$INDEX" | 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
grep -B0 -A1 "server\.tool(" "$INDEX" | 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/,$//')
@@ -139,15 +167,6 @@ jobs:
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: >-
@@ -208,6 +227,44 @@ jobs:
git push origin "$TAG"
fi
# ── Release Artifacts ────────────────────────────────────────────
# Built AFTER the release commit/tag so both zips reflect the final
# released tree. Mirrors the Joomla two-artifact model:
# 1. full-repo zip — tracked files only (git archive excludes
# .git/node_modules/dist automatically).
# 2. source zip — just the resolved source subtree. The git
# tree-ish path must NOT have a trailing slash, hence ${SRC%/}.
- name: Package release artifacts
id: package
if: steps.version.outputs.skip != 'true'
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
REPO_NAME="${{ github.event.repository.name }}"
REPO_ZIP="/tmp/${REPO_NAME}-${VERSION}.zip"
SOURCE_ZIP="/tmp/${REPO_NAME}-${VERSION}-source.zip"
# Full-repository "standard" zip (tracked files only).
git archive --format=zip -o "$REPO_ZIP" HEAD
# Source-directory subtree zip.
SRC="${{ steps.srcdir.outputs.source_dir }}"
SRC="${SRC%/}"
if [ -z "$SRC" ] || [ "$SRC" = "." ]; then
echo "::error::source_dir did not resolve to a real subdirectory (got '${SRC}'); refusing to build a misleading source zip"
exit 1
fi
git archive --format=zip -o "$SOURCE_ZIP" "HEAD:${SRC}"
# Fail closed if either archive is missing or empty.
for z in "$REPO_ZIP" "$SOURCE_ZIP"; do
[ -s "$z" ] || { echo "::error::archive $z missing or empty"; exit 1; }
done
echo "repo_zip=${REPO_ZIP}" >> "$GITHUB_OUTPUT"
echo "source_zip=${SOURCE_ZIP}" >> "$GITHUB_OUTPUT"
- name: GitHub Release
if: >-
steps.version.outputs.skip != 'true' &&
@@ -215,6 +272,7 @@ jobs:
env:
GH_TOKEN: ${{ secrets.GH_TOKEN || github.token }}
run: |
set -euo pipefail
VERSION="${{ steps.version.outputs.version }}"
RELEASE_TAG="${{ steps.version.outputs.release_tag }}"
MAJOR="${{ steps.version.outputs.major }}"
@@ -223,8 +281,8 @@ jobs:
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}"
NOTES=$(php /tmp/mokostandards/api/cli/release_notes.php --path . --version "$VERSION" 2>/dev/null || true)
if [ -z "$NOTES" ]; then NOTES="Release ${VERSION}"; fi
{
echo "$NOTES"
@@ -241,20 +299,22 @@ jobs:
EXISTING=$(gh release view "$RELEASE_TAG" --json tagName -q .tagName 2>/dev/null || true)
ARTIFACT="/tmp/${REPO_NAME}-${VERSION}.tar.gz"
# Two artifacts: full-repo zip and source zip (from Package step).
REPO_ZIP="${{ steps.package.outputs.repo_zip }}"
SOURCE_ZIP="${{ steps.package.outputs.source_zip }}"
if [ -z "$EXISTING" ]; then
gh release create "$RELEASE_TAG" \
--title "v${MAJOR} (latest: ${VERSION})" \
--notes-file /tmp/release_notes.md \
--target "$BRANCH" \
"$ARTIFACT"
"$REPO_ZIP" "$SOURCE_ZIP"
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
gh release upload "$RELEASE_TAG" "$REPO_ZIP" "$SOURCE_ZIP" --clobber
echo "Release updated: ${RELEASE_TAG} -> ${VERSION}" >> $GITHUB_STEP_SUMMARY
fi
+22 -5
View File
@@ -10,15 +10,20 @@ name: "MCP: Build & Validate"
on:
push:
branches: [main, dev/**]
paths: ['src/**', 'package.json', 'tsconfig.json']
paths: ['source/**', 'src/**', 'package.json', 'tsconfig.json']
pull_request:
branches: [main]
paths: ['src/**', 'package.json', 'tsconfig.json']
paths: ['source/**', 'src/**', 'package.json', 'tsconfig.json']
permissions:
contents: read
env:
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
jobs:
build:
runs-on: ubuntu-latest
@@ -30,6 +35,13 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Resolve source dir
id: srcdir
uses: ./.mokogit/actions/resolve-source-dir
with:
mokogit_token: ${{ secrets.MOKOGIT_TOKEN }}
strict: 'false'
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
@@ -58,9 +70,14 @@ jobs:
- name: Count registered tools
run: |
TOOL_COUNT=$(grep -c "server\.tool(" src/index.ts || true)
echo "Registered tools: ${TOOL_COUNT}"
INDEX_FILE="${{ steps.srcdir.outputs.source_dir }}index.ts"
if [ ! -f "${INDEX_FILE}" ]; then
echo "::warning::resolved entrypoint ${INDEX_FILE} missing; falling back to src/index.ts"
INDEX_FILE="src/index.ts"
fi
TOOL_COUNT=$(grep -c "server\.tool(" "${INDEX_FILE}" || true)
echo "Registered tools in ${INDEX_FILE}: ${TOOL_COUNT}"
if [ "${TOOL_COUNT}" -eq 0 ]; then
echo "ERROR: No tools registered in src/index.ts"
echo "ERROR: No tools registered in ${INDEX_FILE}"
exit 1
fi
+51
View File
@@ -24,20 +24,70 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 2
- name: Resolve source dir
id: srcdir
uses: ./.mokogit/actions/resolve-source-dir
with:
mokogit_token: ${{ secrets.MOKOGIT_TOKEN }}
strict: 'true'
- name: Detect publishable changes
id: detect
run: |
set -euo pipefail
# workflow_dispatch has no diff base — always treat as changed.
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
echo "::notice::workflow_dispatch — forcing changed=true"
echo "changed=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# Build the source-dir alternative from the resolved dir (escape ERE metachars).
# Class lists ] first and [ last to avoid a [. collating-symbol parse; the
# \\\\& replacement reaches sed as \\& = literal backslash + matched char.
SRC_DIR='${{ steps.srcdir.outputs.source_dir }}'
SRC_RE=$(printf '%s' "$SRC_DIR" | sed 's/[].^$*+?(){}|[]/\\\\&/g')
PATTERN="^(${SRC_RE}|package\.json$|package-lock\.json$|tsconfig\.json$|README\.md$)"
echo "Detect pattern: ${PATTERN}"
BEFORE="${{ github.event.before }}"
if [ -z "${BEFORE}" ] || ! git cat-file -e "${BEFORE}^{commit}" 2>/dev/null; then
BEFORE=$(git rev-parse HEAD^ 2>/dev/null || echo "")
fi
if [ -n "${BEFORE}" ]; then
FILES=$(git diff --name-only "${BEFORE}" HEAD)
else
FILES=$(git show --name-only --pretty=format: HEAD)
fi
if printf '%s\n' "${FILES}" | grep -Eq "${PATTERN}"; then
echo "changed=true" >> "$GITHUB_OUTPUT"
else
echo "::notice::No publishable changes under '${SRC_DIR}'. Files: ${FILES}"
echo "changed=false" >> "$GITHUB_OUTPUT"
fi
- name: Setup Node.js
if: steps.detect.outputs.changed == 'true'
uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- name: Install dependencies
if: steps.detect.outputs.changed == 'true'
run: npm install
- name: Build
if: steps.detect.outputs.changed == 'true'
run: npm run build
- name: Auto-bump patch version
if: steps.detect.outputs.changed == 'true'
run: |
API_BASE="${MOKOGIT_URL}/api/v1/repos/${GIT_ORG}/${GIT_REPO}"
TOKEN="${{ secrets.MOKOGIT_TOKEN }}"
@@ -109,6 +159,7 @@ jobs:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
- name: Publish
if: steps.detect.outputs.changed == 'true'
run: npm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+24 -5
View File
@@ -10,13 +10,18 @@ name: "MCP: Tool Inventory"
on:
push:
branches: [main]
paths: ['src/index.ts']
paths: ['source/index.ts', 'src/index.ts']
workflow_dispatch:
permissions:
contents: read
env:
MOKOGIT_URL: ${{ vars.MOKOGIT_URL || 'https://git.mokoconsulting.tech' }}
GIT_ORG: ${{ vars.MOKOGIT_ORG || github.repository_owner }}
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
jobs:
inventory:
runs-on: ubuntu-latest
@@ -24,15 +29,29 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Resolve source dir
id: srcdir
uses: ./.mokogit/actions/resolve-source-dir
with:
mokogit_token: ${{ secrets.MOKOGIT_TOKEN }}
strict: 'false'
- name: Generate tool inventory
run: |
INDEX_FILE="${{ steps.srcdir.outputs.source_dir }}index.ts"
if [ ! -f "${INDEX_FILE}" ]; then
echo "::warning::resolved entrypoint ${INDEX_FILE} missing; falling back to src/index.ts"
INDEX_FILE="src/index.ts"
fi
[ -r "${INDEX_FILE}" ] || { echo "::error::entry file ${INDEX_FILE} unreadable"; exit 1; }
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 "Auto-generated from \`${INDEX_FILE}\` 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)
TOOL_COUNT=$(grep -c "server\.tool(" "${INDEX_FILE}" || true)
echo "**Total tools: ${TOOL_COUNT}**" >> TOOLS.md
echo "" >> TOOLS.md
@@ -40,10 +59,10 @@ jobs:
echo "| Tool | Description |" >> TOOLS.md
echo "|------|-------------|" >> TOOLS.md
grep -A1 "server\.tool(" src/index.ts | grep -E "^\s*'" | while read -r line; do
grep -A1 "server\.tool(" "${INDEX_FILE}" | 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 "")
DESC=$(grep -A2 "'${TOOL_NAME}'" "${INDEX_FILE}" | grep -E "^\s*'" | tail -1 | sed "s/.*'\([^']*\)'.*/\1/" || echo "")
echo "| \`${TOOL_NAME}\` | ${DESC} |" >> TOOLS.md
done
+42 -1
View File
@@ -45,6 +45,17 @@ env:
GIT_REPO: ${{ vars.MOKOGIT_REPO || github.event.repository.name }}
jobs:
# ─────────────────────────────────────────────────────────────────────
# NOTE (npm/MCP repos): This is the PHP/Joomla pre-release pipeline. It is
# NOT a Node-aware release path — it has no setup-node / npm ci / npm build.
# npm and MCP repos MUST NOT rely on this workflow for releasing; they use
# 'npm-auto-release.yml' instead. The "Check platform eligibility" step
# already sets proceed=false for non-Joomla platforms, so the PHP build
# steps are skipped for npm repos. In addition, the PHP toolchain install
# below is guarded on the ABSENCE of package.json so npm repos don't pull
# in php-cli/composer. TODO(#35): once resolve-source-dir lands everywhere,
# split this into platform-specific reusable workflows.
# ─────────────────────────────────────────────────────────────────────
build:
name: "Build Pre-Release (${{ inputs.stability || github.ref_name }})"
runs-on: release
@@ -76,7 +87,11 @@ jobs:
echo MOKO_CLI=/opt/mokocli/cli >> $GITHUB_ENV
else
echo Falling back to fresh clone
if ! command -v composer > /dev/null 2>&1; then
# Skip the PHP/composer toolchain install for npm/MCP repos
# (detected via package.json). Those repos release through
# npm-auto-release.yml and the Joomla-only steps below are gated
# off via the eligibility check, so the toolchain is unneeded.
if [ ! -f package.json ] && ! command -v composer > /dev/null 2>&1; then
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
fi
rm -rf /tmp/mokocli
@@ -86,8 +101,23 @@ jobs:
echo MOKO_CLI=/tmp/mokocli/cli >> $GITHUB_ENV
fi
# Early clean-exit for npm/MCP repos: this is the Joomla pre-release
# pipeline and does not apply. Detecting here (before any php call)
# avoids invoking platform_detect.php on runners where php may be
# absent (npm repos take the fresh-clone path that skips the PHP
# toolchain install).
- name: Detect npm/MCP repo
id: npmcheck
run: |
if [ -f package.json ]; then
echo "is_npm=true" >> "$GITHUB_OUTPUT"
else
echo "is_npm=false" >> "$GITHUB_OUTPUT"
fi
- name: Detect platform
id: platform
if: steps.npmcheck.outputs.is_npm != 'true'
run: |
# Auto-detect and update platform if not set in manifest
php ${MOKO_CLI}/platform_detect.php --path . --github-output 2>/dev/null || true
@@ -96,7 +126,18 @@ jobs:
- name: Check platform eligibility (Joomla only)
id: eligibility
run: |
# npm/MCP repos: Joomla pre-release does not apply — clean exit.
if [ "${{ steps.npmcheck.outputs.is_npm }}" = "true" ]; then
echo "proceed=false" >> "$GITHUB_OUTPUT"
echo "::notice::npm/MCP repo — pre-release (Joomla) not applicable; use npm-auto-release.yml"
exit 0
fi
PLATFORM="${{ steps.platform.outputs.platform }}"
# Do not silently no-op an eligible Joomla repo if detection failed.
if [ -z "$PLATFORM" ]; then
echo "::warning::platform detection returned empty"
fi
if [[ "$PLATFORM" == joomla* ]] || [[ "$PLATFORM" == "joomla" ]]; then
echo "proceed=true" >> "$GITHUB_OUTPUT"
else
+3 -3
View File
@@ -35,9 +35,9 @@ npm run dev # Development mode
## Architecture
This is an MCP (Model Context Protocol) server. Key files:
- `src/index.ts` -- server entry point and tool registration
- `src/config.ts` -- configuration loading
- `src/tools/` -- individual tool implementations
- `source/index.ts` -- server entry point and tool registration
- `source/config.ts` -- configuration loading
- `source/tools/` -- individual tool implementations
- `dist/` -- compiled output (gitignored)
## Rules
+3
View File
@@ -5,6 +5,9 @@
PROJECT_NAME := {{PROJECT_NAME}}
PROJECT_VERSION := 1.0.0
# Source directory (single source of truth cross-checked by the MokoGIT CI resolver)
SRC_DIR := source
NPM := npm
COLOR_RESET := \033[0m
+1 -1
View File
@@ -44,7 +44,7 @@ Template repository for creating MokoCLI-compliant Model Context Protocol (MCP)
| Path | Purpose |
|---|---|
| `src/` | TypeScript source files (server entry, tool definitions, API client) |
| `source/` | TypeScript source files (server entry, tool definitions, API client) |
| `config.example.json` | Example multi-connection configuration schema |
| `config.json` | Local connection configuration (gitignored) |
| `package.json` | npm dependencies and scripts |
+4 -4
View File
@@ -21,7 +21,7 @@ AI Assistant <--> MCP (stdio) <--> ApiClient <--> REST API
## Components
### `src/index.ts` — Server Entry Point
### `source/index.ts` — Server Entry Point
Registers all MCP tools with `McpServer` from `@modelcontextprotocol/sdk`. Each tool maps to one or more API endpoints. Uses Zod schemas for input validation.
@@ -30,18 +30,18 @@ Includes shared helpers:
- `paginationQuery()` — builds pagination query params
- `ConnectionParam` / `PaginationParams` — reusable Zod parameter spreads
### `src/client.ts` — HTTP Client
### `source/client.ts` — HTTP Client
The `ApiClient` class handles all HTTP communication:
- Uses `node:https` / `node:http` (not `fetch`) for reliable self-signed cert support
- Supports GET, POST, PUT, PATCH, DELETE
- JSON serialization/deserialization with error handling
### `src/config.ts` — Configuration Loader
### `source/config.ts` — Configuration Loader
Loads connection details from `~/.<project>.json`. Supports multiple named connections with a configurable default.
### `src/types.ts` — Type Definitions
### `source/types.ts` — Type Definitions
TypeScript interfaces for `ApiConnection`, `ApiConfig`, and `ApiResponse`.
+1 -1
View File
@@ -11,7 +11,7 @@
"build": "tsc",
"dev": "tsc --watch",
"start": "node dist/index.js",
"lint": "eslint src/",
"lint": "eslint source/",
"setup": "node scripts/setup.mjs",
"clean": "rm -rf dist/"
},
+1 -1
View File
@@ -8,7 +8,7 @@
* DEFGROUP: {{PROJECT_NAME}}.Client
* INGROUP: {{PROJECT_NAME}}
* REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}}
* PATH: /src/client.ts
* PATH: /source/client.ts
* VERSION: 01.00.00
* BRIEF: HTTP client for {{DISPLAY_NAME}} API
*/
+1 -1
View File
@@ -8,7 +8,7 @@
* DEFGROUP: {{PROJECT_NAME}}.Config
* INGROUP: {{PROJECT_NAME}}
* REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}}
* PATH: /src/config.ts
* PATH: /source/config.ts
* VERSION: 01.00.00
* BRIEF: Configuration loader for {{DISPLAY_NAME}} MCP connections
*/
+1 -1
View File
@@ -9,7 +9,7 @@
* DEFGROUP: {{PROJECT_NAME}}.Server
* INGROUP: {{PROJECT_NAME}}
* REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}}
* PATH: /src/index.ts
* PATH: /source/index.ts
* VERSION: 01.00.00
* BRIEF: MCP server entry point registers all {{DISPLAY_NAME}} API tools
*/
+1 -1
View File
@@ -8,7 +8,7 @@
* DEFGROUP: {{PROJECT_NAME}}.Types
* INGROUP: {{PROJECT_NAME}}
* REPO: https://git.mokoconsulting.tech/MokoConsulting/{{PROJECT_NAME}}
* PATH: /src/types.ts
* PATH: /source/types.ts
* VERSION: 01.00.00
* BRIEF: TypeScript type definitions for {{DISPLAY_NAME}} MCP server
*/
+2 -2
View File
@@ -4,7 +4,7 @@
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"rootDir": "./source",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
@@ -14,6 +14,6 @@
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"include": ["source/**/*"],
"exclude": ["node_modules", "dist"]
}