Skip to content

docs: add release process runbook and openapi refresh automation - #112

Open
marekdano wants to merge 6 commits into
mainfrom
606-release-docs
Open

docs: add release process runbook and openapi refresh automation#112
marekdano wants to merge 6 commits into
mainfrom
606-release-docs

Conversation

@marekdano

Copy link
Copy Markdown
Contributor

Summary

  • Adds RELEASE.md, documenting the end-to-end release process: refreshing the pinned API contract, bumping the UI version, tagging main, and publishing the GitHub release.
  • Adds scripts/refresh-openapi.sh (wired up as npm run openapi:refresh), automating the "regenerate openapi.json from a sibling mcp-context-forge checkout" step: pulls upstream main, regenerates the spec, pins info.version to <API version>+<commit hash>, updates the two README references to that version, regenerates the API client, and opens a signed-off PR with the result. Supports --dry-run to inspect the diff before anything is committed.

No application code changes — docs and release tooling only.

… test coverage

Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Marek Dano <mk.dano@gmail.com>
Signed-off-by: Marek Dano <mk.dano@gmail.com>

@gcgoncalves gcgoncalves left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The RELEASE.md instructions are clear and simple, I like it! :)

A few considerations about the script:

  1. The script leans on gh, supposing the user has it installed on the first place. Can we replace it with git?
  2. The script ends pushing the changes by default. Do we actually want that?

When running the script locally, I got the following error:

==> Updating ~/Projects/mcp-context-forge
==> Generating openapi.json from 63d69a
2026-09-08T13:23:22 - mcpgateway.config - INFO - Using SQLite database. Consider PostgreSQL for production.
2026-09-08T13:23:22 - mcpgateway.config - INFO - SIEM URL allowlist is empty — all outbound destination URLs are permitted
Traceback (most recent call last):
...
  File "~/Projects/mcp-context-forge/mcpgateway/config.py", line 1671, in _enforce_secret_strength
    raise SecurityConfigurationError(f"{field_name}: unset placeholder (__REPLACE_ME__) rejected. {remediation}{hint}")
mcpgateway.config.SecurityConfigurationError: jwt_secret_key: unset placeholder (__REPLACE_ME__) rejected. Run 'python -m mcpgateway.scripts.init_secrets' to generate strong values, or use 'make init-secrets-patch-env' to write them directly into .env.
 ELIFECYCLE  Command failed with exit code 1.

This means that .env has no JWT secret set. I think we need to document this and, ideally, handle this error on the script and instruct the user on how to proceed.

REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
API_DIR="${API_DIR_ARG:-${OPENAPI_SOURCE_DIR:-$(dirname "$REPO_ROOT")/mcp-context-forge}}"

if [[ ! -d "$API_DIR/.git" ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for adding the dirty-tree guard. Could we also verify this checkout has the canonical IBM/mcp-context-forge remote before checking it out, pulling it, and importing its Python app? Right now any local .git path supplied through the argument or environment variable is trusted and its code runs in the release operator environment.

f.write("\n")
PY

if diff -q "$TMP_SPEC" "$REPO_ROOT/openapi.json" >/dev/null 2>&1; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice idempotency check. Could README synchronization happen before this early return, or be evaluated separately? If openapi.json already matches while its README version references are stale, the command reports nothing to do and skips the advertised README update.

Comment thread scripts/refresh-openapi.sh Outdated

BRANCH="chore/openapi-${API_VERSION}-${API_COMMIT_SHORT}"
echo "==> Creating branch $BRANCH"
git -C "$REPO_ROOT" checkout -B "$BRANCH"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we base this branch explicitly on origin/main? checkout -B uses whichever commit the caller currently has checked out; running from a clean feature or stale branch would make the generated PR include unrelated commits.

marekdano and others added 2 commits September 8, 2026 14:27
  - Verify the sibling checkout's remote points at IBM/mcp-context-forge before pulling and importing its Python app
  - Check README references for staleness independently of the openapi.json idempotency check, so a stale README alone still updates
  - Base the new branch explicitly on origin/main instead of whatever is currently checked out
  - Require --push to push/open a PR instead of doing it by default, with gh now optional
  - Fail fast with a clear message when the sibling repo's .env still has unset secret placeholders

Signed-off-by: Marek Dano <mk.dano@gmail.com>
PY

SPEC_CHANGED=0
if diff -q "$TMP_SPEC" "$REPO_ROOT/openapi.json" >/dev/null 2>&1; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for anchoring the created branch to origin/main. Could change detection also use that same target? These flags are calculated before the fetch/checkout. If a clean caller branch already contains the refreshed spec but origin/main does not, SPEC_CHANGED is zero; the new branch starts from stale origin/main and never copies the refreshed spec.

while read -r remote_name; do
[[ -z "$remote_name" ]] && continue
remote_url="$(git -C "$API_DIR" remote get-url "$remote_name" 2>/dev/null || true)"
if [[ "$remote_url" =~ [:/][Ii][Bb][Mm]/mcp-context-forge(\.git)?$ ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Appreciate the new remote check. Could we require the exact canonical GitHub URL and retain that remote name for the fetch? This pattern also accepts an attacker host such as https://evil.example/IBM/mcp-context-forge.git, and validating any remote does not ensure main pulls from that remote before its Python code is imported.

README_CHANGED=1
fi

if [[ "$SPEC_CHANGED" == 0 && "$README_CHANGED" == 0 ]]; then

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small flow issue: after a successful local-only run, re-running with --push finds no spec/README changes and returns here before reaching the push block. The documented re-run with --push path therefore cannot push the branch. Please handle --push for the existing generated branch, or adjust the guidance.

@vishu-bh vishu-bh left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for addressing prior feedback — branch anchoring, README checks, secret guidance, and opt-in push behavior are all stronger. I found three remaining release-flow/security issues in the new inline comments. Please address them before merge.

@a-effort a-effort left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the diff. One blocker.

The refresh-openapi.sh script generates the spec by importing and calling app.openapi() from mcpgateway.main directly in Python. This means the script executes arbitrary code from the sibling checkout — including all startup side effects of that module. The script guards against running from an unverified remote, which is good, but the guard checks only remote URL pattern by string match, not that the working tree matches the remote. A checkout with a valid remote URL but local modifications (hotfixes, experiments) would pass the guard and execute modified code. The clean working tree check for the API dir catches this: [[ -n "$(git -C "$API_DIR" status --porcelain)" ]] aborts if there are uncommitted changes. That is an adequate guard, but it does not catch the case where the working tree is clean but is on a non-main branch that has diverged from the remote. The script calls git checkout main --quiet before pulling, which addresses this, but only if there are no local main branch commits that haven't been pushed. If origin/main and local main have diverged, pull --ff-only will fail with an error, which is the correct behavior. No change needed; noting this so reviewers understand the trust model.

Separate issue: the script writes openapi.json using Python's json.dump with indent=2 but does not set ensure_ascii=False. If the spec contains non-ASCII characters (unlikely but possible in description fields), they will be escaped to \uXXXX sequences in the output, which would produce a diff against any version that had those characters unescaped. This is a cosmetic issue but could produce spurious noise in openapi.json diffs. Consider adding ensure_ascii=False to both json.dump calls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants