Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .github/workflows/auto_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ jobs:
version: ${{ needs.validate.outputs.version }}
secrets: inherit

publish-registry:
needs: [validate, deploy]
uses: ./.github/workflows/publish-registry.yml
with:
version: ${{ needs.validate.outputs.version }}

release:
needs: [validate, deploy]
runs-on: ubuntu-latest
Expand Down
6 changes: 6 additions & 0 deletions .github/workflows/manual_release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,12 @@ jobs:
version: ${{ needs.bump-and-tag.outputs.version }}
secrets: inherit

publish-registry:
needs: [bump-and-tag, deploy]
uses: ./.github/workflows/publish-registry.yml
with:
version: ${{ needs.bump-and-tag.outputs.version }}

release:
needs: [bump-and-tag, deploy]
runs-on: ubuntu-latest
Expand Down
52 changes: 52 additions & 0 deletions .github/workflows/publish-registry.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
name: Publish to MCP Registry

on:
workflow_call:
inputs:
version:
description: Version to publish (must match server.json)
required: true
type: string

permissions:
id-token: write # GitHub OIDC token for mcp-publisher
contents: read

env:
# Pinned deliberately (latest release; see https://github.com/modelcontextprotocol/registry/releases).
# Must be recent enough to accept server.json's $schema (2025-12-11); older builds reject it. Bump intentionally.
MCP_PUBLISHER_VERSION: "1.7.0"

jobs:
publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
with:
persist-credentials: false # publish reads server.json only; don't persist the token to .git/config
# The bumped commit is tagged before this job runs in both release
# paths (auto_release is triggered by the tag; manual_release pushes
# the tag in bump-and-tag, which deploy + this job depend on), so the
# tag always resolves to the version-bumped server.json.
ref: v${{ inputs.version }}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

- name: Verify server.json version matches release
env:
VERSION: ${{ inputs.version }}
run: |
jq -e --arg v "$VERSION" '.version == $v' server.json > /dev/null \
|| { echo "::error::server.json version ($(jq -r '.version' server.json)) does not match release ($VERSION)"; exit 1; }

- name: Install mcp-publisher (pinned)
run: |
# Asset name carries no version segment (changed at v1.3.0); runner is ubuntu-latest (linux/amd64).
curl -sSL \
"https://github.com/modelcontextprotocol/registry/releases/download/v${MCP_PUBLISHER_VERSION}/mcp-publisher_linux_amd64.tar.gz" \
| tar xz mcp-publisher

- name: Authenticate via GitHub OIDC
run: ./mcp-publisher login github-oidc

- name: Publish to MCP Registry
timeout-minutes: 5
run: ./mcp-publisher publish
15 changes: 9 additions & 6 deletions DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,15 +137,18 @@ GitHub Actions runs lint/test/build on every PR and push to main, and handles re

### Workflows

| Workflow | Trigger | What it does |
| -------------------- | -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci.yml` | PR + push to main | `prettier:check`, `lint`, `test`, `build` |
| `auto_release.yml` | `v*` tag push (from your laptop) | Validates `package.json` version matches the tag → calls `deploy.yml` → creates a GitHub Release with auto-generated notes and updates `CHANGELOG.md` |
| `manual_release.yml` | Actions UI (`workflow_dispatch`) | Bumps version, commits, tags, pushes, calls `deploy.yml`, creates the GitHub Release — all inline. Does NOT chain through `auto_release.yml` (a workflow-pushed tag can't trigger another workflow). |
| `deploy.yml` | Reusable (`workflow_call`) | OIDC AWS auth → `sst deploy` → Docker build/push to GHCR → SSH to Lightsail → `docker compose pull && up -d` → `/healthz` gate |
| Workflow | Trigger | What it does |
| ---------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `ci.yml` | PR + push to main | `prettier:check`, `lint`, `test`, `build` |
| `auto_release.yml` | `v*` tag push (from your laptop) | Validates `package.json` version matches the tag → calls `deploy.yml` + `publish-registry.yml` → creates a GitHub Release with auto-generated notes and updates `CHANGELOG.md` |
| `manual_release.yml` | Actions UI (`workflow_dispatch`) | Bumps version, commits, tags, pushes, calls `deploy.yml` + `publish-registry.yml`, creates the GitHub Release — all inline. Does NOT chain through `auto_release.yml` (a workflow-pushed tag can't trigger another workflow). |
| `deploy.yml` | Reusable (`workflow_call`) | OIDC AWS auth → `sst deploy` → Docker build/push to GHCR → SSH to Lightsail → `docker compose pull && up -d` → `/healthz` gate |
| `publish-registry.yml` | Reusable (`workflow_call`) | Publishes `server.json` to the [official MCP Registry](https://registry.modelcontextprotocol.io/) via `mcp-publisher`, authenticating with GitHub OIDC. Runs after `deploy` (so the GHCR image referenced in `server.json` already exists). |

> **Why two release paths?** Tag pushes done by `GITHUB_TOKEN` from inside a workflow can't trigger other workflows (GitHub's anti-loop guard). So `manual_release.yml` has to do its own deploy + release inline instead of relying on `auto_release.yml` firing. `auto_release.yml` still exists for the laptop path — when you push a tag from your terminal, your user account is the actor and the trigger fires normally.

> **MCP Registry publishing is automatic.** Both release paths call `publish-registry.yml`, so the [official MCP Registry](https://registry.modelcontextprotocol.io/) entry tracks every release — no manual `mcp-publisher publish`. It authenticates with [GitHub OIDC](https://modelcontextprotocol.io/registry/github-actions) and needs **no secret**: the registry authorizes the `io.github.<owner>/*` namespace from the OIDC token's repo owner, so a fork publishing `io.github.<your-user>/...` works out of the box (unlike the AWS OIDC role below, which you must provision).

### GitHub OIDC setup (for forkers)

The deploy workflow uses [GitHub OIDC](https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/configuring-openid-connect-in-amazon-web-services) to assume an AWS IAM role without long-lived credentials. If you're forking this project, you need to create your own OIDC provider and IAM role in your AWS account.
Expand Down