Publish SHA-256 checksums for Windows runtime archives and require both plugin bootstraps to verify fresh and cached downloads before extraction. Co-authored-by: Stefan Broenner <stbrnner@microsoft.com> Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
17 KiB
Phase 3: GitHub Copilot Plugin Publishing
Overview
ExcelMcp plugins are published to the official GitHub Copilot CLI marketplace via a separate published marketplace repository. This document explains the maintainer-side workflow for automatic plugin republishing.
Architecture:
- Source repo (
sbroenne/mcp-server-excel) — Development, releases, skills, and canonical plugin templates - Published repo (
sbroenne/mcp-server-excel-plugins) — Official marketplace artifacts - Two plugins:
excel-mcpandexcel-cli, both published as wrapper/bootstrap bundles plus skills - Auto-sync:
.github/workflows/publish-plugins.ymlbuilds and validates templates after each release
Trigger: After "Release All Components" workflow completes successfully, the publish workflow automatically syncs plugin artifacts to the marketplace.
User Impact: GitHub Copilot CLI users can install both plugins via copilot plugin install.
Key Design:
- Canonical Agent Plugins 1.0 templates live under
.github/plugins/in the source repo - The published repo is output-only and cannot feed legacy manifests back into future builds
- Version and current Agent Skills content are injected and validated during the publish workflow
See GitHub Copilot Plugin Distribution for the user-facing documentation.
What can be automated from this environment?
- Token creation: No — you must create a PAT or obtain an app token outside this workflow and store it as a repository secret.
- Source-repo wiring with the token: Yes — store the secret with
gh. - Workflow readiness checks: Yes — this repo already contains a preflight gate in
publish-plugins.ymlthat fails fast if the secret is missing or the published repo is unreachable.
CLI command to store the token
# Store the PAT or app token as a repository secret in the source repo
gh secret set PLUGINS_REPO_TOKEN --repo sbroenne/mcp-server-excel --body "<token-value>"
Validate the repo-side wiring
# Confirm the secret name exists (GitHub never returns the secret value)
gh secret list -R sbroenne/mcp-server-excel
Required Repository Secret
The workflow needs write access to the published repository. Store a token in the source repo:
Token Setup (Required)
Choose one of these options:
Option A: Personal Access Token (PAT)
- Go to GitHub Settings → Developer settings → Personal access tokens → Tokens (classic)
- Click Generate new token (classic)
- Token name:
ExcelMcp Plugin Publisher - Expiration: 90 days (recommended; rotate every 90 days or manually when workflow fails)
- Scopes: Select
public_repo(minimum scope for publishing to a public repo) - Click Generate token and copy the token value
- Store it in the source repo:
gh secret set PLUGINS_REPO_TOKEN --repo sbroenne/mcp-server-excel --body "<token-value>"
Option B: GitHub App Token
If you've already created a GitHub App for other purposes:
- Generate a temporary app installation token from the app's settings
- Store it as
PLUGINS_REPO_TOKEN(same as above)
Why Stored Token?
- ✅ Simple setup — one secret, no extra variables
- ✅ Works immediately — no browser-based app creation or installation flow
- ✅ Easy to rotate — update the secret when needed
- ✅ Same behavior as the legacy PAT approach
Workflow Behavior
Trigger Conditions
- ✅ Runs ONLY when "Release All Components" workflow completes successfully
- ✅ Runs ONLY on
mainbranch releases - ✅ Maintainers also get a manual re-sync entry point for repair/replay scenarios
- ❌ Does NOT run on failed releases
- ❌ Does NOT run on PR builds or test runs
What It Does
- Resolve Tag + Version — Extracts version from the triggering workflow's HEAD commit tag, or validates the manually supplied source release tag
- Source-Side Sync Gate — Skips downstream publish when the plugin-published source surface did not change since the previous release tag
- Clone Repos — Clones BOTH source and published repos
- Build Plugins — Runs
scripts/Build-Plugins.ps1which:- Copies canonical plugin templates from
.github/plugins/ - Strips committed
.exe/.dllruntime payloads so the published repo stays wrapper/bootstrap-only - Updates
plugin.jsonversion andversion.txt - Preserves plugin-local
bin/wrapper/download assets and runtime-bootstrap metadata - Synchronizes complete skill directories from source (
skills/excel-mcp,skills/excel-cli), removing stale published files - Stamps the release-tag version into each packaged skill's generated
VERSIONfile - Validates Agent Plugins 1.0 manifests, portable
mcp.json, and Agent Skills frontmatter
- Copies canonical plugin templates from
- Migrate Marketplace Layout — Rewrites the published repo into the canonical marketplace layout by applying the source-owned root overlay, writing
.github/plugin/marketplace.json, and removing any legacy rootmarketplace.json - Published-Repo Guards — Rejects downgrade or tag/version mismatch publishes before mutating the published repo
- Sync to Published Repo — Only commits and pushes when the guarded sync path says publication is needed
- Create or Repair Tag — Tags the published repo with the same version (for example
v1.2.3) when the tag is missing - Summary — Generates workflow summary with the publish/skip decision and GitHub Copilot CLI install examples for changed published artifacts
Version Extraction Strategy
Corrected: Uses workflow_run.head_sha plus the checked-out git tag graph to find the annotated source release tag created by the release workflow.
- ✅ Avoids race condition: Uses the exact commit that was just released
- ✅ No drift: If multiple releases happen close together, each publish uses the correct version
- ❌ Old (incorrect) approach: "latest release" could grab the wrong version in rapid succession
Sync Gate
- The hardened source-side flow skips downstream plugin publication when the install-surface inputs have not changed since the prior release tag.
- Result: normal releases still publish all core artifacts, but plugin republishing only happens when plugin-facing content actually changed.
Version and Tag Guards
- Published-side sync rejects downgrade attempts.
- Manual repair/replay runs must keep the requested tag/version aligned with the incoming plugin manifest/version metadata.
- The sync step now rewrites the published repo to the canonical marketplace layout on every needed publish, so legacy root-manifest state is repaired automatically.
- Result: maintainers can re-sync safely without accidentally stamping the wrong release tag onto plugin artifacts.
Concurrency Control
- Only one publish workflow runs at a time
- Does NOT cancel in-progress runs (waits for completion)
- Prevents race conditions during concurrent releases
Idempotency
- Automatic release-follow-on runs skip entirely when no plugin-published source files changed since the previous release tag
- Automatic duplicate publishes are skipped when the published repo already has the same version and tag
- Manual re-sync runs can replay an existing release tag without cutting a new source release
- If the published repo is already in sync, the workflow exits with a clear summary instead of making an empty commit
Testing the Workflow
Test After Token Setup
-
Trigger a test release (or wait for next real release):
# From source repo, trigger a release manually gh workflow run release.yml -f version_bump=patch -
Monitor the publish workflow:
# Watch for publish-plugins workflow to start gh run watch # Or list recent runs gh run list --workflow=publish-plugins.yml -
Verify published repo updated:
cd ../mcp-server-excel-plugins git pull git log -1 # Should see the latest publish commit git tag # Should see new version tag Test-Path .github\plugin\marketplace.json # Should be True after migration
Manual Re-Sync
If the automatic follow-on publish needs to be replayed after a transient failure, use the workflow's manual workflow_dispatch entry point with an existing source release tag:
gh workflow run publish-plugins.yml -f release_tag=v1.2.3
Keep the requested release tag aligned with the plugin manifest/version the workflow is syncing; the published-side guards reject mismatched or downgrade attempts.
Troubleshooting
Workflow fails with "Resource not accessible":
- Token does not have write access to
sbroenne/mcp-server-excel-plugins - Token is expired or revoked
- Check that
PLUGINS_REPO_TOKENsecret exists in the source repo
Workflow fails immediately with a missing configuration message:
- Add repository secret
PLUGINS_REPO_TOKENinsbroenne/mcp-server-excel - Verify the secret contains a valid PAT or app token with write access to the published repo
- Rotate PAT if it has expired or been compromised
Workflow completes but no commit in published repo:
- The source-side sync gate detected no plugin-published source changes since the prior release tag
- OR: The published repo already had the same version and tag, so the automatic duplicate publish was skipped
- OR: Build-Plugins.ps1 generated identical content and the published repo stayed in sync without a new commit
Workflow fails with a version/tag guard message:
- Confirm the requested
release_tagexists in the source repo and matches the plugin manifest/version being synced - Check
.github/plugin/marketplace.json(or the legacy rootmarketplace.jsonif the published repo has not been migrated yet) and existing tags insbroenne/mcp-server-excel-plugins - Downgrade attempts and inconsistent "tag exists but version differs" states are intentionally blocked
Build step fails:
- Build-Plugins.ps1 requires .NET 10.0 SDK
Build-Plugins.ps1requires an explicit-Version; source skill directories do not carry fallbackVERSIONfiles
File Locations
| File | Purpose | Location |
|---|---|---|
publish-plugins.yml |
Workflow definition | .github/workflows/ (source repo) |
Build-Plugins.ps1 |
Plugin build script | scripts/ (source repo) |
Sync-PublishedPluginRepo.ps1 |
Canonical published-repo sync script | scripts/ (source repo) |
| This document | Setup instructions | .github/workflows/docs/ (source repo) |
Maintenance and updates
Updating plugins and published output
The source repository is the only editable source for plugin publication. The
published repository is generated output: unsynchronized hand edits in
sbroenne/mcp-server-excel-plugins are prohibited and can be overwritten by the
next publication.
For every plugin change:
- Change the canonical source first under
.github/plugins/,skills/, or the owning build, sync, and workflow files insbroenne/mcp-server-excel. - Build or regenerate the plugin packages with
scripts/Build-Plugins.ps1. - Run
scripts/Sync-PublishedPluginRepo.ps1against a clean checkout ofsbroenne/mcp-server-excel-pluginsto produce the complete publication tree. - Inspect the generated diff and run the focused generation, sync, instruction,
and plugin validation, including the generated repository's
tests/Test-Plugins.ps1. Fix failures in the source repository, regenerate, and repeat; do not patch the generated output. - Only after the generated diff and tests are clean should the source pull request be merged and the release publication path be allowed to run.
Merging a source pull request alone does not publish plugins. A normal
successful Release All Components run on main triggers
publish-plugins.yml. Maintainers can also run publish-plugins.yml manually
with an existing source release tag to re-sync or repair publication. The
workflow directly commits and pushes the generated output to
sbroenne/mcp-server-excel-plugins/main.
Changing Published Repo Name
If you rename mcp-server-excel-plugins:
- Update
PUBLISHED_REPOenv var inpublish-plugins.yml - Rotate or update
PLUGINS_REPO_TOKENsecret if needed - Update the published-repo metadata owned here (for example
.github/plugins/marketplace-repo/README.md) so the next sync rewrites the target repo correctly
Debugging Build Issues
Run the build + sync scripts locally to test:
# From source repo root
./scripts/Build-Plugins.ps1 -Version 1.2.3
./scripts/Sync-PublishedPluginRepo.ps1 -PublishedRepoDir ..\mcp-server-excel-plugins -BuiltPluginsDir .\plugins -Version 1.2.3
# Verify output
ls plugins/
ls plugins/excel-mcp/
ls plugins/excel-cli/
Test-Path ..\mcp-server-excel-plugins\.github\plugin\marketplace.json
Architecture Notes
Why workflow_run?
The workflow uses workflow_run trigger with head_sha version extraction:
- ✅ Avoids binary race condition — Waits for release workflow to complete, ensuring GitHub Release artifacts exist
- ✅ Atomic trigger — One publish per release, no manual intervention
- ✅ Version alignment — Extracts tag from the exact commit that was just released (not "latest release")
- ✅ No drift — If multiple releases happen close together, each gets the correct version
Version extraction logic:
HEAD_SHA="${{ github.event.workflow_run.head_sha }}"
git fetch --force --tags origin
TAG=$(git tag --points-at "$HEAD_SHA" --sort=-version:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$' | head -n1)
Why source-owned canonical templates?
Build-Plugins.ps1 strategy: copy complete source-owned plugin templates, then inject release metadata and current skills.
- ✅ Single source of truth — manifests, MCP configuration, READMEs, and bootstrap scripts live in this repo
- ✅ Prevents legacy feedback — published artifacts are never reused as build inputs
- ✅ Schema-safe output — build and publish guards reject legacy fields and
.mcp.json - ✅ Build script's job — copy templates, inject version/current skills, validate, and package
What gets copied:
- Plugin structure → From
.github/plugins/excel-mcpand.github/plugins/excel-cli - Skills and all references → Exact directory sync from source repo
skills/excel-mcpandskills/excel-cli - Marketplace repository README → From
.github/plugins/marketplace-repo - Marketplace manifest → Generated by
Sync-PublishedPluginRepo.ps1at.github/plugin/marketplace.json - Runtime bootstrap metadata →
version.txt+ plugin-local helper scripts inbin/
What gets updated:
plugin.jsonversion fieldversion.txt(release-tag metadata consumed by plugin-local bootstrap logic)
Runtime Bootstrap Packaging Rules
- Published plugins ship wrapper/download logic and metadata only.
- Self-contained Windows runtimes stay in the main repo GitHub Releases and are fetched by the plugin on first invocation.
- Each release publishes
SHA256SUMSin GNU-style<hash> <filename>format for both Windows runtime ZIPs. Bootstrap downloads and cached archives must match the exact asset entry before extraction. publish-plugins.ymlnow validates that built plugin artifacts do not contain committed.exe,.dll,.deps.json, or.runtimeconfig.jsonpayloads.- MCP configuration is portable root
mcp.jsonwith explicit transport type and${PLUGIN_ROOT}arguments; legacy.mcp.jsonis rejected. - Standard skills stay under
skills/; any future Copilot-only files belong undercom.github.copilot/.
Why two repos?
Source repo (mcp-server-excel):
- Development, testing, releases
- CI/CD, integration tests, documentation
- Binary build outputs (MCP Server, CLI)
Published repo (mcp-server-excel-plugins):
- Distribution only (lightweight marketplace)
- Canonical Copilot CLI marketplace manifest lives at
.github/plugin/marketplace.json - No build dependencies (just JSON, Markdown, PowerShell scripts)
- Clean separation: users don't clone 200MB source repo to get plugins
Why not git submodules?
The published repo is NOT a submodule of the source repo. Instead:
- Workflow pushes built artifacts directly to published repo
- Published repo is standalone (easier for users to clone/fork)
- No submodule complexity for plugin consumers
- Allows published repo to have different README, docs, structure
Success Criteria
✅ Workflow created: .github/workflows/publish-plugins.yml
✅ Build script created: scripts/Build-Plugins.ps1
✅ Documentation created: This file
⚠️ Token configuration required: User must add repository secret PLUGINS_REPO_TOKEN
⚠️ First run test required: Validate after next release
Status: Implementation complete, pending token setup and first-run validation
Next Steps: User must configure repository secret PLUGINS_REPO_TOKEN in sbroenne/mcp-server-excel, then validate both the automatic release-follow-on path and the manual workflow_dispatch re-sync path. The next successful sync will also migrate the published repo to the canonical marketplace manifest path/layout.