name: Build Complete Release
on:
push:
tags:
- 'v*'
branches:
- feature/nsis-installer
pull_request:
branches:
- master
workflow_dispatch:
inputs:
version:
description: 'Version tag (leave empty to auto-detect from package.json)'
required: false
default: ''
dry_run_foundry:
description: 'Test Foundry API call without actually updating registry'
required: false
default: 'false'
type: boolean
jobs:
build-nsis:
runs-on: windows-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.12.2'
- name: Set Package Version
id: version
run: |
try {
if ("${{ github.event.inputs.version }}" -ne "") {
$version = "${{ github.event.inputs.version }}"
Write-Host "β Using manual input version: $version"
# Validate version format
if ($version -notmatch '^v?\d+\.\d+\.\d+') {
throw "Invalid version format: '$version'. Expected format: v0.0.0 or 0.0.0"
}
} elseif ("${{ github.ref_type }}" -eq "tag") {
$version = "${{ github.ref_name }}"
Write-Host "β Using tag version: $version"
# Validate tag version format
if ($version -notmatch '^v?\d+\.\d+\.\d+') {
throw "Invalid tag version format: '$version'. Expected format: v0.0.0"
}
} else {
Write-Host "π Reading version from package.json..."
# Check if package.json exists
if (-not (Test-Path "package.json")) {
throw "package.json not found in repository root"
}
# Read and parse package.json
$packageJson = Get-Content package.json -Raw | ConvertFrom-Json
# Validate version field exists
if (-not $packageJson.version) {
throw "No 'version' field found in package.json"
}
$packageVersion = $packageJson.version
Write-Host "π¦ Found package.json version: $packageVersion"
# Validate version format
if ($packageVersion -notmatch '^\d+\.\d+\.\d+') {
throw "Invalid package.json version format: '$packageVersion'. Expected format: 0.0.0"
}
$version = "v$packageVersion"
Write-Host "β Using package.json version: $version"
}
# Final validation - ensure no invalid characters for filename
if ($version -match '[<>:"/\\|?*]') {
throw "Version contains invalid filename characters: '$version'"
}
Write-Host "π― Final PACKAGE_VERSION: $version"
echo "PACKAGE_VERSION=$version" >> $env:GITHUB_ENV
echo "version=$version" >> $env:GITHUB_OUTPUT
} catch {
Write-Error "β Failed to determine package version: $($_.Exception.Message)"
Write-Error "π Context: github.event.inputs.version='${{ github.event.inputs.version }}', github.ref_type='${{ github.ref_type }}', github.ref_name='${{ github.ref_name }}'"
exit 1
}
- name: Install workspace dependencies (CI)
run: npm ci --workspaces --include-workspace-root
- name: Build shared types
run: npm run build --workspace=shared
- name: Build MCP Server (Bundled)
run: npm run build:bundle --workspace=packages/mcp-server
- name: Setup NSIS
run: choco install nsis -y
- name: Build NSIS Installer
run: |
$env:PATH = "C:\Program Files (x86)\NSIS;$env:PATH"
cd installer
node build-nsis.js --version $env:PACKAGE_VERSION
- name: Create Foundry Module ZIP
run: |
Write-Host "π¦ Creating Foundry module ZIP for registry..."
cd packages/foundry-module
# Create ZIP excluding hidden files and node_modules
$compress = @{
Path = Get-ChildItem -Path "." -Exclude ".*", "node_modules"
DestinationPath = "..\..\foundry-vtt-mcp.zip"
CompressionLevel = "Optimal"
}
Compress-Archive @compress -Force
# Verify ZIP was created
if (Test-Path "..\..\foundry-vtt-mcp.zip") {
$size = (Get-Item "..\..\foundry-vtt-mcp.zip").Length / 1KB
Write-Host "β Foundry module ZIP created ($([math]::Round($size, 1)) KB)"
} else {
Write-Host "β Foundry module ZIP creation failed"
exit 1
}
- name: Create Standalone MCP Server ZIP
run: |
Write-Host "π¦ Creating standalone MCP server ZIP for manual installation..."
# Create a temporary directory for the standalone package
$standalonePath = "standalone-mcp-server"
New-Item -ItemType Directory -Force -Path $standalonePath
# Copy MCP server bundle
Copy-Item "packages\mcp-server\dist\index.bundle.cjs" "$standalonePath\index.js"
# Create package.json for standalone
$standalonePackage = @{
name = "foundry-mcp-server"
version = "${{ env.PACKAGE_VERSION }}".TrimStart('v')
description = "Foundry VTT MCP Server - Standalone Installation"
main = "index.js"
scripts = @{
start = "node index.js"
}
engines = @{
node = ">=18.0.0"
}
} | ConvertTo-Json -Depth 3
$standalonePackage | Out-File -FilePath "$standalonePath\package.json" -Encoding UTF8
# Create README for standalone
$readmeLines = @(
"# Foundry MCP Server - Manual Installation",
"",
"This package contains the standalone MCP server for manual installation on any platform.",
"",
"## Requirements",
"- Node.js 18.0.0 or later",
"- Foundry VTT with MCP Bridge module installed",
"",
"## Installation",
"1. Extract this ZIP to your desired location",
"2. Run: npm start or node index.js",
"3. Configure Claude Desktop to connect to this server",
"4. Install the MCP Bridge module in Foundry VTT",
"",
"For full documentation visit: https://github.com/adambdooley/foundry-vtt-mcp",
"",
"Version: ${{ env.PACKAGE_VERSION }}"
)
$readmeLines | Out-File -FilePath "$standalonePath\README.md" -Encoding UTF8
# Create the ZIP
$compress = @{
Path = $standalonePath
DestinationPath = "foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip"
CompressionLevel = "Optimal"
}
Compress-Archive @compress -Force
# Verify and clean up
if (Test-Path "foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip") {
$size = (Get-Item "foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip").Length / 1KB
Write-Host "β Standalone MCP server ZIP created ($([math]::Round($size, 1)) KB)"
} else {
Write-Host "β Standalone MCP server ZIP creation failed"
exit 1
}
# Clean up temporary directory
Remove-Item -Recurse -Force $standalonePath
- name: Verify installer package
run: |
cd installer/build
dir
if (Test-Path "FoundryMCPServer-Setup-$env:PACKAGE_VERSION.exe") {
Write-Host "β NSIS installer found"
$size = (Get-Item "FoundryMCPServer-Setup-$env:PACKAGE_VERSION.exe").Length / 1MB
Write-Host "π Installer size: $([math]::Round($size, 1)) MB"
} else {
Write-Host "β NSIS installer not found"
exit 1
}
- name: Upload installer as artifact
uses: actions/upload-artifact@v4
with:
name: foundry-mcp-server-installer-${{ env.PACKAGE_VERSION }}
path: installer/build/FoundryMCPServer-Setup-${{ env.PACKAGE_VERSION }}.exe
retention-days: 30
build-mac-pkg:
runs-on: macos-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.12.2'
- name: Set Package Version
id: version
run: |
if [ -n "${{ github.event.inputs.version }}" ]; then
VERSION="${{ github.event.inputs.version }}"
echo "β Using manual input version: $VERSION"
# Validate version format
if ! echo "$VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+'; then
echo "β Invalid version format: '$VERSION'. Expected format: v0.0.0 or 0.0.0"
exit 1
fi
elif [ "${{ github.ref_type }}" = "tag" ]; then
VERSION="${{ github.ref_name }}"
echo "β Using tag version: $VERSION"
# Validate tag version format
if ! echo "$VERSION" | grep -Eq '^v?[0-9]+\.[0-9]+\.[0-9]+'; then
echo "β Invalid tag version format: '$VERSION'. Expected format: v0.0.0"
exit 1
fi
else
echo "π Reading version from package.json..."
# Check if package.json exists
if [ ! -f "package.json" ]; then
echo "β package.json not found in repository root"
exit 1
fi
# Read and parse package.json (using node for JSON parsing)
PACKAGE_VERSION=$(node -p "require('./package.json').version")
# Validate version field exists
if [ -z "$PACKAGE_VERSION" ]; then
echo "β No 'version' field found in package.json"
exit 1
fi
echo "π¦ Found package.json version: $PACKAGE_VERSION"
# Validate version format
if ! echo "$PACKAGE_VERSION" | grep -Eq '^[0-9]+\.[0-9]+\.[0-9]+'; then
echo "β Invalid package.json version format: '$PACKAGE_VERSION'. Expected format: 0.0.0"
exit 1
fi
VERSION="v$PACKAGE_VERSION"
echo "β Using package.json version: $VERSION"
fi
echo "π― Final PACKAGE_VERSION: $VERSION"
echo "PACKAGE_VERSION=$VERSION" >> $GITHUB_ENV
echo "version=$VERSION" >> $GITHUB_OUTPUT
- name: Install workspace dependencies (CI)
run: npm ci --workspaces --include-workspace-root
- name: Build shared types
run: npm run build --workspace=shared
- name: Build MCP Server (Bundled)
run: npm run build:bundle --workspace=packages/mcp-server
- name: Build Foundry Module
run: npm run build --workspace=packages/foundry-module
- name: Build Mac PKG Installer
run: |
cd installer
node build-mac-pkg.js
- name: Build Mac DMG Distribution
run: |
cd installer
node build-dmg.js
- name: Verify Mac DMG
run: |
cd installer/build
ls -lh
# Strip 'v' prefix from version for filename check
VERSION="${{ env.PACKAGE_VERSION }}"
VERSION_NO_V="${VERSION#v}"
if [ -f "FoundryMCPServer-${VERSION_NO_V}.dmg" ]; then
echo "β Mac DMG distribution found"
SIZE=$(du -h "FoundryMCPServer-${VERSION_NO_V}.dmg" | cut -f1)
echo "π DMG size: $SIZE"
# Copy with v prefix for artifact upload consistency
cp "FoundryMCPServer-${VERSION_NO_V}.dmg" "FoundryMCPServer-${VERSION}.dmg"
else
echo "β Mac DMG not found (expected: FoundryMCPServer-${VERSION_NO_V}.dmg)"
exit 1
fi
- name: Upload Mac DMG as artifact
uses: actions/upload-artifact@v4
with:
name: foundry-mcp-server-dmg-${{ env.PACKAGE_VERSION }}
path: installer/build/FoundryMCPServer-${{ env.PACKAGE_VERSION }}.dmg
retention-days: 30
create-release:
needs: [build-nsis, build-mac-pkg]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20.12.2'
- name: Set Package Version
id: version
run: |
VERSION="${{ github.ref_name }}"
# Remove 'v' prefix for filenames if present
PACKAGE_VERSION="${VERSION#v}"
echo "PACKAGE_VERSION=v$PACKAGE_VERSION" >> $GITHUB_ENV
echo "version=v$PACKAGE_VERSION" >> $GITHUB_OUTPUT
- name: Install workspace dependencies
run: npm ci --workspaces --include-workspace-root
- name: Build shared types
run: npm run build --workspace=shared
- name: Build MCP Server (Bundled)
run: npm run build:bundle --workspace=packages/mcp-server
- name: Build Foundry Module
run: npm run build --workspace=packages/foundry-module
- name: Download Windows installer artifact
uses: actions/download-artifact@v4
with:
name: foundry-mcp-server-installer-${{ env.PACKAGE_VERSION }}
path: installer/build/
- name: Download Mac DMG artifact
uses: actions/download-artifact@v4
with:
name: foundry-mcp-server-dmg-${{ env.PACKAGE_VERSION }}
path: installer/build/
- name: Create Foundry Module ZIP
run: |
cd packages/foundry-module
zip -r ../../foundry-vtt-mcp.zip . -x ".*" -x "node_modules/*"
cd ../..
ls -lh foundry-vtt-mcp.zip
- name: Create Standalone MCP Server ZIP
run: |
mkdir -p standalone-mcp-server
cp packages/mcp-server/dist/index.bundle.cjs standalone-mcp-server/index.js
# Create package.json
cat > standalone-mcp-server/package.json << EOF
{
"name": "foundry-mcp-server",
"version": "${{ env.PACKAGE_VERSION }}",
"description": "Foundry VTT MCP Server - Standalone Installation",
"main": "index.js",
"scripts": {
"start": "node index.js"
},
"engines": {
"node": ">=18.0.0"
}
}
EOF
# Create README
cat > standalone-mcp-server/README.md << EOF
# Foundry MCP Server - Manual Installation
This package contains the standalone MCP server for manual installation on any platform.
## Requirements
- Node.js 18.0.0 or later
- Foundry VTT with MCP Bridge module installed
## Installation
1. Extract this ZIP to your desired location
2. Run: npm start or node index.js
3. Configure Claude Desktop to connect to this server
4. Install the MCP Bridge module in Foundry VTT
For full documentation visit: https://github.com/adambdooley/foundry-vtt-mcp
Version: ${{ env.PACKAGE_VERSION }}
EOF
zip -r foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip standalone-mcp-server
rm -rf standalone-mcp-server
ls -lh foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ github.ref_name }}
name: Foundry MCP Server ${{ github.ref_name }}
body: |
## Foundry MCP Server ${{ github.ref_name }}
Complete multi-platform distribution for Foundry MCP Server - AI-powered campaign management for Foundry VTT using Claude Desktop.
### π¦ Installation Options
**Windows Users:**
- Download `FoundryMCPServer-Setup-${{ github.ref_name }}.exe`
- Run installer (no admin rights required)
- Automatic Claude Desktop configuration
**Mac Users:**
- Download `FoundryMCPServer-${{ github.ref_name }}.dmg`
- Open DMG and double-click the PKG installer
- Choose components: MCP Server (required), Foundry Module, ComfyUI AI Maps
- Automatic Claude Desktop configuration
- To uninstall: Double-click `Uninstall.tool` from the DMG
**Manual Installation (All Platforms):**
- Download `foundry-mcp-server-${{ github.ref_name }}.zip`
- Extract and run with Node.js 18+
- Manual Claude Desktop configuration required
**Foundry VTT Users:**
- Install directly from Foundry's module browser
- Or use manifest: `https://raw.githubusercontent.com/adambdooley/foundry-vtt-mcp/master/packages/foundry-module/module.json`
### π Quick Start
1. Choose your installation method above
2. Install MCP Bridge module in Foundry VTT
3. Restart Claude Desktop
4. Start creating AI-powered campaigns!
### π Features
- 25 MCP tools for comprehensive Foundry VTT integration
- Actor creation with natural language processing
- Quest management with HTML generation and updates
- Campaign system with multi-part structure and progress tracking
- Dice roll coordination between Claude and Foundry players
- Actor ownership management with bulk operations
- Enhanced creature index for instant monster searches
- AI-powered battlemap generation with ComfyUI integration
For full documentation, visit: https://github.com/adambdooley/foundry-vtt-mcp
files: |
installer/build/FoundryMCPServer-Setup-${{ env.PACKAGE_VERSION }}.exe
installer/build/FoundryMCPServer-${{ env.PACKAGE_VERSION }}.dmg
foundry-vtt-mcp.zip
foundry-mcp-server-${{ env.PACKAGE_VERSION }}.zip
packages/foundry-module/module.json
generate_release_notes: true
draft: false
prerelease: false
- name: Update Foundry VTT Package Registry
run: |
echo "π Updating Foundry VTT Package Registry..."
# Extract package information
PACKAGE_ID=$(node -p "require('./packages/foundry-module/module.json').id")
VERSION="${{ env.PACKAGE_VERSION }}"
VERSION="${VERSION#v}" # Remove v prefix
MANIFEST_URL="https://github.com/adambdooley/foundry-vtt-mcp/releases/download/${{ github.ref_name }}/module.json"
RELEASE_NOTES_URL="https://github.com/adambdooley/foundry-vtt-mcp/releases/tag/${{ github.ref_name }}"
MINIMUM=$(node -p "require('./packages/foundry-module/module.json').compatibility.minimum")
VERIFIED=$(node -p "require('./packages/foundry-module/module.json').compatibility.verified")
MAXIMUM=$(node -p "require('./packages/foundry-module/module.json').compatibility.maximum")
echo "π¦ Package ID: $PACKAGE_ID"
echo "π Version: $VERSION"
echo "π Manifest URL: $MANIFEST_URL"
echo "π Release Notes: $RELEASE_NOTES_URL"
# Prepare API request body
REQUEST_BODY=$(cat <<EOF
{
"id": "$PACKAGE_ID",
"release": {
"version": "$VERSION",
"manifest": "$MANIFEST_URL",
"notes": "$RELEASE_NOTES_URL",
"compatibility": {
"minimum": "$MINIMUM",
"verified": "$VERIFIED",
"maximum": "$MAXIMUM"
}
}
}
EOF
)
echo "π§ Request Body:"
echo "$REQUEST_BODY"
# Make API call to Foundry VTT Package Registry
RESPONSE=$(curl -X POST \
-H "Content-Type: application/json" \
-H "Authorization: ${{ secrets.FOUNDRY_PACKAGE_RELEASE_TOKEN }}" \
-d "$REQUEST_BODY" \
https://foundryvtt.com/_api/packages/release_version/)
if [ $? -eq 0 ]; then
echo "β
Successfully updated Foundry VTT Package Registry"
echo "π Response: $RESPONSE"
else
echo "β Failed to update Foundry VTT Package Registry"
echo "π Error: $RESPONSE"
exit 1
fi