Skip to content
Closed
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ This repository holds the BFF and client that sit in front of it. The table belo
Throughout this README, "the API", "the BFF", and "the client" refer to those
three. The browser only ever talks to the BFF, never directly to the API.

This UI targets **ContextForge API v1.0.7**, matching [`openapi.json`](./openapi.json) committed at repo root.
This UI targets **ContextForge API v1.0.10**, matching [`openapi.json`](./openapi.json) committed at repo root.

## Tech Stack

Expand Down Expand Up @@ -138,7 +138,7 @@ npm run preview

## API Types

TypeScript types and fetch clients under `src/generated/` come from [`openapi.json`](./openapi.json) via [Orval](./orval.config.ts). That file is committed and pinned to API v1.0.7, not re-fetched at build time.
TypeScript types and fetch clients under `src/generated/` come from [`openapi.json`](./openapi.json) via [Orval](./orval.config.ts). That file is committed and pinned to API v1.0.10, not re-fetched at build time.

```bash
npm run generate # regenerate src/generated/ from ./openapi.json
Expand Down
67 changes: 67 additions & 0 deletions RELEASE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Release Process

This repo tracks two independent version numbers:

- **UI version** — this repo's own semver, in [`package.json`](./package.json) and tagged as `vA.B.C` on `main`. Bumped every release.
- **Pinned API version** — the upstream [IBM/mcp-context-forge](https://github.com/IBM/mcp-context-forge) commit this UI was built and tested against, recorded as build metadata on [`openapi.json`](./openapi.json)'s `info.version` (e.g. `1.0.0+589c69`). Bumped only when you refresh `openapi.json` from a newer API checkout.

A release usually bumps both, but doesn't have to — a UI-only bugfix can ship a new UI version without touching the API pin.

## 1. Refresh the API contract

Skip this section if the API hasn't changed since the last release.

```bash
npm run openapi:refresh
```

This runs [`scripts/refresh-openapi.sh`](./scripts/refresh-openapi.sh), which:

1. Pulls `main` in a sibling `mcp-context-forge` checkout (default: `../mcp-context-forge`; override with `OPENAPI_SOURCE_DIR` or a path argument).
2. Regenerates `openapi.json` from it and pins `info.version` to `<API version>+<first 6 chars of the commit hash>` — [semver build metadata](https://semver.org/#spec-item-10), no spaces or parentheses (e.g. `1.0.0+589c69`).
3. Updates the two places the README quotes that same pin (the `This UI targets **ContextForge API vX.Y.Z**` line and the codegen note further down).
4. Runs `npm run generate` to regenerate the API client.
5. Commits on a new `chore/openapi-...` branch, pushes, and opens a PR.

Run with `--dry-run` to stop after step 4 and inspect `git diff` yourself before committing anything. Both the API checkout and this repo must have a clean working tree before you run it.

Fix any type errors from the client regeneration (`npm run build`) before merging the PR it opens.

## 2. Bump the UI version

On a branch off `main`, bump [`package.json`](./package.json)'s `version` following semver:

- **patch** — bug fixes, no API pin change
- **minor** — new UI functionality, or an API pin bump that only adds endpoints/fields
- **major** — breaking UI change, or an API pin bump with breaking changes

## 3. PR and merge

Open a PR with the `openapi.json` / README / generated-client changes (if any) and the `package.json` bump. Get it reviewed and merged like any other change — no direct pushes to `main`.

## 4. Tag the release

```bash
git checkout main && git pull
git tag vA.B.C # must match the package.json version from step 2
git push origin vA.B.C
```

## 5. Publish the GitHub release

1. Go to the [tags page](https://github.com/contextforge-org/contextforge-web-ui/tags).
2. On the new tag's `...` menu, click **Create release**.
3. Click **Generate release notes** to populate the changelog from merged PRs.
4. If the release isn't production-ready (e.g. an early cut for testing), check **Set as a pre-release**.
5. Click **Publish release**.

## Rolling back a bad tag

If a tag was pushed by mistake and the release hasn't been publicised yet:

```bash
git push --delete origin vA.B.C
git tag -d vA.B.C
```

Delete the corresponding GitHub release (if one was published) from its page as well. Once a release has been announced or consumed, prefer shipping a new patch version over deleting history.
4 changes: 2 additions & 2 deletions openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"info": {
"title": "ContextForge",
"description": "ContextForge AI Gateway \u2014 an AI gateway, registry, and proxy for MCP, A2A, and REST/gRPC APIs. Exposes a unified control plane with centralized governance, discovery, and observability. Optimizes agent and tool calling, and supports plugins.",
"version": "1.0.9+ (ref: 589c69)"
"version": "1.0.10+13d549"
},
"paths": {
"/metrics/prometheus": {
Expand Down Expand Up @@ -10454,7 +10454,7 @@
"Teams"
],
"summary": "Create Team",
"description": "Create a new team, optionally seeding it with members.\n\nMembers supplied in the request are routed by the server: an address that\nbelongs to an active user is added to the team directly, anything else is\nsent an invitation. Team, memberships and invitations are written as one\ntransaction, so a bad row fails the whole request rather than leaving a\nhalf-populated team behind.\n\nArgs:\n request: Team creation request data\n background_tasks: Response-scoped background task scheduler\n current_user_ctx: Currently authenticated user context\n db: Database session\n\nReturns:\n TeamCreateResponse: Created team data, plus how each seeded member was resolved\n\nRaises:\n HTTPException: If team creation fails\n\nExamples:\n >>> import asyncio\n >>> asyncio.iscoroutinefunction(create_team)\n True",
"description": "Create a new team, optionally seeding it with members.\n\nMembers supplied in the request are routed by the server: an address that\nbelongs to an active user is added to the team directly, anything else is\nsent an invitation. Team, memberships and invitations are written as one\ntransaction, so a bad row fails the whole request rather than leaving a\nhalf-populated team behind.\n\nArgs:\n request: Team creation request data\n background_tasks: Response-scoped background task scheduler\n current_user_ctx: Currently authenticated user context\n db: Database session\n\nReturns:\n TeamCreateResponse: Created team data, plus how each seeded member was resolved\n\nRaises:\n HTTPException 400: Active team with same name exists (platform admin callers).\n HTTPException 403: Team creation disabled; caller is not platform admin.\n HTTPException 409: Active team with same name exists (non-admin; generic message prevents name enumeration).\n HTTPException 500: Unexpected service error.\n\nExamples:\n >>> import asyncio\n >>> asyncio.iscoroutinefunction(create_team)\n True",
"operationId": "create_team_v1_teams__post",
"security": [
{
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"i18n:extract": "formatjs extract 'src/**/*.{ts,tsx}' --out-file src/i18n/extracted.json --id-interpolation-pattern '[sha512:contenthash:base64:6]'",
"i18n:compile": "formatjs compile-folder --ast src/i18n/locales src/i18n/compiled",
"generate": "orval",
"openapi:refresh": "bash scripts/refresh-openapi.sh",
"prepare": "husky || true"
},
"dependencies": {
Expand Down
126 changes: 126 additions & 0 deletions scripts/refresh-openapi.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
#!/usr/bin/env bash
# Regenerates openapi.json from a sibling mcp-context-forge checkout, pins it
# to the commit it came from, updates the README references and generated
# API client, and opens a PR with the result.
#
# Usage:
# scripts/refresh-openapi.sh [path-to-mcp-context-forge]
# scripts/refresh-openapi.sh --dry-run [path-to-mcp-context-forge]
#
# --dry-run stops after writing the local file changes so you can inspect
# `git diff` yourself; it does not commit, push, or open a PR.
#
# Env vars:
# OPENAPI_SOURCE_DIR overrides the sibling repo path (same as the
# positional argument; the argument wins if both are set)
set -euo pipefail

DRY_RUN=0
API_DIR_ARG=""
for arg in "$@"; do
case "$arg" in
--dry-run) DRY_RUN=1 ;;
*) API_DIR_ARG="$arg" ;;
esac
done

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
echo "error: $API_DIR is not a git checkout of mcp-context-forge" >&2
echo " pass its path as an argument, or set OPENAPI_SOURCE_DIR" >&2
exit 1
fi

if [[ -n "$(git -C "$REPO_ROOT" status --porcelain)" ]]; then
echo "error: $REPO_ROOT has uncommitted changes, aborting" >&2
exit 1
fi

if [[ -n "$(git -C "$API_DIR" status --porcelain)" ]]; then
echo "error: $API_DIR has uncommitted changes, refusing to touch it" >&2
exit 1
fi

echo "==> Updating $API_DIR"
git -C "$API_DIR" checkout main --quiet
git -C "$API_DIR" pull --ff-only --quiet

API_COMMIT="$(git -C "$API_DIR" rev-parse HEAD)"
API_COMMIT_SHORT="${API_COMMIT:0:6}"

VENV_PY="$API_DIR/.venv/bin/python"
if [[ -x "$VENV_PY" ]]; then
PYTHON="$VENV_PY"
else
echo "warning: no venv at $API_DIR/.venv, falling back to python3 on PATH" >&2
PYTHON="python3"
fi

echo "==> Generating openapi.json from $API_COMMIT_SHORT"
TMP_SPEC="$(mktemp)"
trap 'rm -f "$TMP_SPEC"' EXIT

(cd "$API_DIR" && "$PYTHON" -c "
import json, sys
from mcpgateway.main import app
json.dump(app.openapi(), open(sys.argv[1], 'w'), indent=2)
" "$TMP_SPEC")

API_VERSION="$(python3 -c "import json; print(json.load(open('$TMP_SPEC'))['info']['version'])")"
PINNED_VERSION="${API_VERSION}+${API_COMMIT_SHORT}"

echo "==> Pinning info.version to $PINNED_VERSION"
python3 - "$TMP_SPEC" "$PINNED_VERSION" <<'PY'
import json, sys
path, version = sys.argv[1], sys.argv[2]
with open(path) as f:
spec = json.load(f)
spec["info"]["version"] = version
with open(path, "w") as f:
json.dump(spec, f, indent=2)
f.write("\n")
PY

if diff -q "$TMP_SPEC" "$REPO_ROOT/openapi.json" >/dev/null 2>&1; then
echo "==> openapi.json is already at $PINNED_VERSION, nothing to do"
exit 0
fi

cp "$TMP_SPEC" "$REPO_ROOT/openapi.json"

echo "==> Updating README references"
if ! grep -qE "targets \*\*ContextForge API v[0-9.]+\*\*" "$REPO_ROOT/README.md"; then
echo "warning: couldn't find the 'targets ContextForge API vX.Y.Z' line in README.md, skipping" >&2
else
sed -i.bak -E "s/targets \*\*ContextForge API v[0-9.]+\*\*/targets **ContextForge API v${API_VERSION}**/" "$REPO_ROOT/README.md"
fi
if ! grep -qE "pinned to API v[0-9.]+," "$REPO_ROOT/README.md"; then
echo "warning: couldn't find the 'pinned to API vX.Y.Z,' line in README.md, skipping" >&2
else
sed -i.bak -E "s/pinned to API v[0-9.]+,/pinned to API v${API_VERSION},/" "$REPO_ROOT/README.md"
fi
rm -f "$REPO_ROOT/README.md.bak"

echo "==> Regenerating API client"
(cd "$REPO_ROOT" && npm run generate)

if [[ "$DRY_RUN" == "1" ]]; then
echo "==> --dry-run set: left openapi.json, README.md and src/generated updated locally."
echo " Review with 'git diff' and commit/branch/PR yourself when ready."
exit 0
fi

BRANCH="chore/openapi-${API_VERSION}-${API_COMMIT_SHORT}"
echo "==> Creating branch $BRANCH"
git -C "$REPO_ROOT" checkout -B "$BRANCH"
git -C "$REPO_ROOT" add openapi.json README.md
git -C "$REPO_ROOT" commit --signoff -m "chore: refresh openapi.json to API v${PINNED_VERSION}"

echo "==> Pushing and opening PR"
git -C "$REPO_ROOT" push -u origin "$BRANCH"
gh pr create \
--repo contextforge-org/contextforge-web-ui \
--title "chore: refresh openapi.json to API v${PINNED_VERSION}" \
--body "Regenerated from [IBM/mcp-context-forge@${API_COMMIT_SHORT}](https://github.com/IBM/mcp-context-forge/commit/${API_COMMIT})."
Loading