diff --git a/.github/upstream-projects.yaml b/.github/upstream-projects.yaml new file mode 100644 index 00000000..6456d929 --- /dev/null +++ b/.github/upstream-projects.yaml @@ -0,0 +1,70 @@ +# Human-maintained source-of-truth for upstream projects whose releases +# trigger doc-update PRs. +# +# Renovate watches `repo:` + `version:` pairs here as a custom-regex +# manager (see renovate.json) and opens a version-bump PR whenever an +# upstream releases a new tag. .github/workflows/upstream-release-docs.yml +# reacts to those PRs, shallow-clones the upstream, and adds +# source-verified content edits via the upstream-release-docs skill. +# +# For `stacklok/toolhive` specifically, most reference artifacts are +# declared as `assets:` below (synced by sync-assets.mjs) — plus one +# workflow-step in upstream-release-docs.yml that downloads the CRD +# manifests tarball and runs extract-crd-schemas.mjs + generate-crd- +# pages.mjs to produce our opinionated per-CRD MDX pages. +# +# You can also edit `version:` by hand (to backfill or reset) and +# dispatch the upstream-release-docs workflow manually in bootstrap +# or retry mode. + +projects: + - id: toolhive-registry-server + repo: stacklok/toolhive-registry-server + version: v1.2.1 + docs_paths: + - docs/toolhive/guides-registry + - docs/toolhive/concepts/registry-criteria.mdx + # Files copied from the upstream repo at the pinned tag whenever + # version: bumps. Source paths are relative to the upstream repo + # root; destination paths are relative to this repo root. Keeping + # the swagger locally (rather than referencing via jsdelivr) lets + # the docs build offline and keeps the pin implicit in git. + assets: + - source: docs/thv-registry-api/swagger.yaml + destination: static/api-specs/toolhive-registry-api.yaml + + - id: toolhive + repo: stacklok/toolhive + version: v0.23.1 + docs_paths: + - docs/toolhive/guides-cli + - docs/toolhive/guides-k8s + - docs/toolhive/guides-vmcp + assets: + - release_asset: swagger.yaml + destination: static/api-specs/toolhive-api.yaml + - release_asset: thv-cli-docs.tar.gz + destination: docs/toolhive/reference/cli + extract: tar-gz + # toolhive-core schemas re-exported by toolhive at release time + # (see stacklok/toolhive#4982). Previously downloaded from the + # toolhive-core repo via a go.mod-derived version lookup. + - release_asset: toolhive-legacy-registry.schema.json + destination: static/api-specs/toolhive-legacy-registry.schema.json + - release_asset: upstream-registry.schema.json + destination: static/api-specs/upstream-registry.schema.json + - release_asset: publisher-provided.schema.json + destination: static/api-specs/publisher-provided.schema.json + - release_asset: skill.schema.json + destination: static/api-specs/skill.schema.json + + - id: toolhive-studio + repo: stacklok/toolhive-studio + version: v0.30.0 + docs_paths: + - docs/toolhive/guides-ui + + - id: toolhive-cloud-ui + repo: stacklok/toolhive-cloud-ui + version: v0.5.1 + docs_paths: [] diff --git a/.github/workflows/update-toolhive-reference.yml b/.github/workflows/update-toolhive-reference.yml deleted file mode 100644 index 567e1753..00000000 --- a/.github/workflows/update-toolhive-reference.yml +++ /dev/null @@ -1,156 +0,0 @@ -name: Update ToolHive Reference Docs - -on: - workflow_dispatch: - inputs: - version: - description: 'ToolHive version to update reference docs for' - required: true - default: 'latest' - assign_to: - description: 'GitHub username to request PR review from (optional)' - required: false - repository_dispatch: - types: [published-release] - -permissions: - contents: write - pull-requests: write - -concurrency: - group: update-toolhive-reference - cancel-in-progress: false - -jobs: - update-reference: - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - - - name: Setup - uses: ./.github/actions/setup - - - name: Set up Git - run: | - git config --global user.name "github-actions[bot]" - git config --global user.email "github-actions[bot]@users.noreply.github.com" - - - name: Determine version - id: get-version - run: | - if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then - echo "version=${{ github.event.client_payload.version }}" >> $GITHUB_OUTPUT - else - echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT - fi - - - name: Determine reviewer - id: get-reviewer - run: | - if [[ "${{ github.event_name }}" == "repository_dispatch" ]]; then - REVIEWER="${{ github.event.client_payload.assign_to }}" - else - REVIEWER="${{ github.event.inputs.assign_to }}" - fi - # Filter out github-actions bot and stacklokbot (can't be requested as reviewers) - if [[ "$REVIEWER" =~ ^github-actions ]] || [[ "$REVIEWER" == "stacklokbot" ]]; then - REVIEWER="" - fi - echo "assign_to=$REVIEWER" >> $GITHUB_OUTPUT - - - name: Run update script - id: imports - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - chmod +x scripts/update-toolhive-reference.sh - if ! scripts/update-toolhive-reference.sh ${{ steps.get-version.outputs.version }}; then - echo "::error::Failed to update ToolHive reference docs" - exit 1 - fi - - - name: Check for changes - id: git-diff - run: | - git add . - if git diff --cached --quiet; then - echo "No changes to commit." - echo "changed=false" >> $GITHUB_OUTPUT - else - echo "Changes detected." - echo "changed=true" >> $GITHUB_OUTPUT - fi - - - name: Detect newly added CRDs - id: new-crds - if: steps.git-diff.outputs.changed == 'true' - run: | - # A new upstream CRD results in an added *.schema.json under - # static/api-specs/crds/. New CRDs publish automatically with - # schema-derived prose; flag them so reviewers can decide whether - # to add a hand-written override in scripts/lib/crd-intros.mjs. - NEW=() - while IFS= read -r schema_file; do - [ -z "$schema_file" ] && continue - kind=$(jq -r '."x-kubernetes-kind"' "$schema_file") - NEW+=("$kind") - done < <(git diff --cached --name-only --diff-filter=A -- 'static/api-specs/crds/*.schema.json') - - if [ ${#NEW[@]} -gt 0 ]; then - { - echo "note_block< [!NOTE]" - echo "> **New CRD(s) in this release.** The following Kind(s) were" - echo "> added upstream and have been auto-published with" - echo "> schema-derived prose:" - echo ">" - for kind in "${NEW[@]}"; do - echo "> - \`$kind\`" - done - echo ">" - echo "> This does not block the release. To polish the page(s)," - echo "> add an override entry in \`scripts/lib/crd-intros.mjs\`" - echo "> (all fields optional) and push to this branch, or land a" - echo "> follow-up PR after merge." - echo "EOF" - } >> $GITHUB_OUTPUT - else - echo "note_block=" >> $GITHUB_OUTPUT - fi - - - name: Create Pull Request - if: steps.git-diff.outputs.changed == 'true' - uses: peter-evans/create-pull-request@5f6978faf089d4d20b00c7766989d076bb2fc7f1 # v8 - with: - branch: update-toolhive-reference-${{ steps.imports.outputs.version }} - title: | - Update ToolHive reference docs for ${{ steps.imports.outputs.version }} - body: | - This PR was auto-generated from ToolHive release **${{ steps.imports.outputs.version }}**.${{ steps.get-reviewer.outputs.assign_to != '' && format(' @{0} is tagged as the person who cut this release.', steps.get-reviewer.outputs.assign_to) || '' }} - ${{ steps.new-crds.outputs.note_block }} - - ## What's in this PR - - Only machine-generated reference files that are built from the ToolHive source code during the release process: - - - **CLI reference** (`docs/toolhive/reference/cli/`) - help text for every `thv` command - - **REST API spec** (`static/api-specs/toolhive-api.yaml`) - OpenAPI/Swagger spec - - **CRD reference** (`docs/toolhive/reference/crds/`, `static/api-specs/crds/`) - per-CRD MDX pages, extracted JSON schemas, and YAML examples - - **Registry JSON schemas** (`static/api-specs/*.schema.json`) - registry validation schemas - - ## What's NOT in this PR - - This PR does **not** contain guide, tutorial, or conceptual content. Any documentation updates beyond reference files are handled in separate, manually authored PRs. - - ## Review guidance - - These files are generated directly from the ToolHive source and are not hand-edited. A quick scan for obvious issues is sufficient - this is intended to be merged shortly after the release is cut so the docs stay in sync. - commit-message: | - Update ToolHive reference docs for ${{ steps.imports.outputs.version }} - labels: | - autogen-docs - reviewers: ${{ steps.get-reviewer.outputs.assign_to }} - delete-branch: true - sign-commits: true diff --git a/.github/workflows/upstream-release-docs.yml b/.github/workflows/upstream-release-docs.yml new file mode 100644 index 00000000..8e21ad4e --- /dev/null +++ b/.github/workflows/upstream-release-docs.yml @@ -0,0 +1,648 @@ +name: Upstream Release Docs + +# Reacts to Renovate-authored PRs that bump a `version:` in +# .github/upstream-projects.yaml. For all four tracked projects +# (toolhive, toolhive-registry-server, toolhive-studio, +# toolhive-cloud-ui), the flow is identical: +# +# 1. Renovate opens a version-bump PR +# 2. This workflow fires on the PR event, detects which project +# changed, shallow-clones the upstream at the new tag +# 3. Syncs declared upstream assets into static/ via sync-assets.mjs +# (release-asset downloads, tarball extractions, file-from-clone +# copies — see `assets:` in upstream-projects.yaml) +# 4. For toolhive specifically, downloads the CRD manifests tarball +# and runs extract-crd-schemas.mjs + generate-crd-pages.mjs + +# bundle-upstream-schema.mjs to produce our opinionated reference +# MDX (the CRD tarball and toolhive-core schemas are shipped as +# release assets by stacklok/toolhive#4982) +# 5. Runs the upstream-release-docs skill (3 passes) to produce +# source-verified content edits +# 6. Commits everything to the PR branch, augments the PR body, +# assigns reviewers from non-bot release contributors +# +# Renovate is configured with rebaseWhen: never + recreateWhen: never +# so we can push commits without force-push races. + +on: + pull_request: + types: [opened, reopened] + paths: + - '.github/upstream-projects.yaml' + workflow_dispatch: + inputs: + pr_number: + description: 'Retry-only: PR number to re-augment (must be an open Renovate PR). Leave blank to bootstrap a new PR via project_id + new_tag.' + required: false + type: string + project_id: + description: 'Bootstrap a new PR: id from .github/upstream-projects.yaml (e.g. toolhive-registry-server). Requires new_tag.' + required: false + type: string + new_tag: + description: 'Bootstrap a new PR: the upstream tag to document (e.g. v1.3.0). Requires project_id.' + required: false + type: string + +permissions: + contents: write + pull-requests: write + # Required by anthropics/claude-code-action@v1 for OIDC token exchange. + id-token: write + +concurrency: + # Workflow-level group so two simultaneous upstream releases don't + # run the skill in parallel on shared concept pages. + group: upstream-release-docs + cancel-in-progress: false + +jobs: + augment: + runs-on: ubuntu-latest + timeout-minutes: 90 + # Gate: two accepted entry points. + # - pull_request: Renovate-authored PR touching upstream-projects.yaml + # (the `paths:` filter on the trigger already narrows to YAML edits). + # - workflow_dispatch: human manually retries or bootstraps. + # Human-authored PRs that happen to edit the YAML are out of scope and + # should be reviewed normally without skill augmentation. + if: | + github.event_name == 'workflow_dispatch' || + ( + github.event_name == 'pull_request' && + github.event.pull_request.user.login == 'renovate[bot]' + ) + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + steps: + - name: Validate workflow_dispatch inputs + if: github.event_name == 'workflow_dispatch' + env: + PR_NUMBER_IN: ${{ github.event.inputs.pr_number }} + PROJECT_ID_IN: ${{ github.event.inputs.project_id }} + NEW_TAG_IN: ${{ github.event.inputs.new_tag }} + run: | + # Exactly one of: (pr_number) OR (project_id + new_tag). + if [ -n "$PR_NUMBER_IN" ] && { [ -n "$PROJECT_ID_IN" ] || [ -n "$NEW_TAG_IN" ]; }; then + echo "::error::Provide either pr_number (retry) or project_id + new_tag (bootstrap), not both." + exit 1 + fi + if [ -z "$PR_NUMBER_IN" ] && { [ -z "$PROJECT_ID_IN" ] || [ -z "$NEW_TAG_IN" ]; }; then + echo "::error::Bootstrap mode requires both project_id and new_tag." + exit 1 + fi + + - name: Resolve PR number and head ref + id: pr + env: + EVENT: ${{ github.event_name }} + DISPATCH_PR: ${{ github.event.inputs.pr_number }} + PROJECT_ID_IN: ${{ github.event.inputs.project_id }} + NEW_TAG_IN: ${{ github.event.inputs.new_tag }} + EVENT_PR: ${{ github.event.pull_request.number }} + EVENT_HEAD_REF: ${{ github.event.pull_request.head.ref }} + run: | + case "$EVENT" in + pull_request) + PR_NUMBER="$EVENT_PR" + HEAD_REF="$EVENT_HEAD_REF" + MODE="react" + ;; + workflow_dispatch) + if [ -n "$DISPATCH_PR" ]; then + PR_NUMBER="$DISPATCH_PR" + HEAD_REF=$(gh pr view "$PR_NUMBER" --json headRefName --jq .headRefName) + AUTHOR=$(gh pr view "$PR_NUMBER" --json author --jq '.author.login') + case "$AUTHOR" in + app/renovate|renovate[bot]|app/github-actions|github-actions[bot]) + ;; + *) + echo "::error::PR #$PR_NUMBER author '$AUTHOR' is not an accepted bot. Retry mode accepts renovate[bot] or github-actions[bot]; use bootstrap (project_id + new_tag) for a fresh manual PR." + exit 1 + ;; + esac + MODE="retry" + else + # Bootstrap: branch and PR are created in the next step. + PR_NUMBER="" + HEAD_REF="" + MODE="bootstrap" + fi + ;; + esac + { + echo "number=$PR_NUMBER" + echo "head_ref=$HEAD_REF" + echo "mode=$MODE" + } >> "$GITHUB_OUTPUT" + echo "Mode: $MODE" + + # Bootstrap: a human manually dispatched with project_id + new_tag. + # Check out the dispatching branch (main in production; any branch + # for pre-merge testing — just dispatch via `gh workflow run --ref + # `), bump the YAML, create the PR, and emit its number + + # branch so the rest of the workflow proceeds as if Renovate had + # opened it. + - name: Checkout dispatching branch for bootstrap + if: steps.pr.outputs.mode == 'bootstrap' + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ github.ref_name }} + fetch-depth: 0 + + - name: Setup (bootstrap) + if: steps.pr.outputs.mode == 'bootstrap' + uses: ./.github/actions/setup + + - name: Set up Git (bootstrap) + if: steps.pr.outputs.mode == 'bootstrap' + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Bootstrap branch and PR + id: bootstrap + if: steps.pr.outputs.mode == 'bootstrap' + env: + PROJECT_ID: ${{ github.event.inputs.project_id }} + NEW_TAG: ${{ github.event.inputs.new_tag }} + BASE_REF: ${{ github.ref_name }} + ACTOR: ${{ github.actor }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + # Branch name intentionally distinct from Renovate's + # docs/upstream- so the two paths don't collide. + BRANCH="manual/upstream-${PROJECT_ID}-${NEW_TAG}" + if git ls-remote --exit-code --heads origin "$BRANCH" >/dev/null 2>&1; then + echo "::error::Branch $BRANCH already exists on origin. Delete it or use retry mode with the existing PR's number." + exit 1 + fi + git checkout -b "$BRANCH" + + node scripts/upstream-release/bump-yaml.mjs \ + --id "$PROJECT_ID" \ + --tag "$NEW_TAG" + + git add .github/upstream-projects.yaml + git commit -m "Bump $PROJECT_ID to $NEW_TAG" + git push origin "$BRANCH" + + # Heredoc so the YAML indent doesn't leak into the PR body. + cat > /tmp/bootstrap-body.md <> "$GITHUB_OUTPUT" + echo "head_ref=$BRANCH" >> "$GITHUB_OUTPUT" + + # Normalize the (possibly bootstrap-created) PR number, head_ref, + # and base_ref into a single step output so later steps reference + # one place. base_ref drives detect-change.mjs (the baseline to + # diff against) and the CRD-regen paths. + - name: Resolve effective PR metadata + id: eff + env: + EVENT: ${{ github.event_name }} + PR_FROM_RESOLVE: ${{ steps.pr.outputs.number }} + HEAD_FROM_RESOLVE: ${{ steps.pr.outputs.head_ref }} + PR_FROM_BOOTSTRAP: ${{ steps.bootstrap.outputs.number }} + HEAD_FROM_BOOTSTRAP: ${{ steps.bootstrap.outputs.head_ref }} + BASE_FROM_EVENT: ${{ github.event.pull_request.base.ref }} + DISPATCH_REF_NAME: ${{ github.ref_name }} + run: | + if [ -n "$PR_FROM_BOOTSTRAP" ]; then + # Bootstrap mode: the PR was just created with --base set to + # the dispatching branch (main in production; feat branch + # when testing pre-merge). + NUMBER="$PR_FROM_BOOTSTRAP" + HEAD="$HEAD_FROM_BOOTSTRAP" + BASE="$DISPATCH_REF_NAME" + elif [ "$EVENT" = "pull_request" ]; then + NUMBER="$PR_FROM_RESOLVE" + HEAD="$HEAD_FROM_RESOLVE" + BASE="$BASE_FROM_EVENT" + else + # workflow_dispatch retry: look up the PR's base_ref. + NUMBER="$PR_FROM_RESOLVE" + HEAD="$HEAD_FROM_RESOLVE" + BASE=$(gh pr view "$NUMBER" --json baseRefName --jq .baseRefName) + fi + { + echo "number=$NUMBER" + echo "head_ref=$HEAD" + echo "base_ref=$BASE" + } >> "$GITHUB_OUTPUT" + + - name: Checkout PR branch + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + ref: ${{ steps.eff.outputs.head_ref }} + fetch-depth: 0 + + # NOTE: we inline the node/deps setup rather than calling the + # ./.github/actions/setup composite because that composite starts + # with its own actions/checkout that overwrites the PR-branch + # checkout above with the dispatching branch. + - name: Set up Node.js + uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6 + with: + node-version: '24.15.0' + + - name: Cache dependencies + id: cache + uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5 + with: + path: ./node_modules + key: modules-${{ hashFiles('package-lock.json') }} + + - name: Install dependencies + if: steps.cache.outputs.cache-hit != 'true' + run: npm ci + + - name: Set up Git + run: | + git config --global user.name "github-actions[bot]" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + + - name: Detect changed project + id: detect + env: + # Pass the PR's actual base (main in production; dispatching + # branch when bootstrap was run via gh workflow run --ref). + BASE_REF: origin/${{ steps.eff.outputs.base_ref }} + run: node scripts/upstream-release/detect-change.mjs + + - name: Verify prev_tag exists upstream + env: + REPO: ${{ steps.detect.outputs.repo }} + PREV_TAG: ${{ steps.detect.outputs.prev_tag }} + run: | + # Sanity check: if the pinned prev_tag doesn't exist upstream + # (e.g., a fake v0.0.0 seed, a retagged release, a deleted tag), + # fail early rather than let the skill produce a confusing diff. + if ! gh api "repos/$REPO/git/refs/tags/$PREV_TAG" --silent 2>/dev/null; then + echo "::error::prev_tag $PREV_TAG does not exist in $REPO. The pinned version in .github/upstream-projects.yaml may be wrong or the upstream tag was deleted. Fix the pinned version and re-run." + exit 1 + fi + + - name: Shallow-clone upstream at new tag + id: clone + env: + REPO: ${{ steps.detect.outputs.repo }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + run: | + SCRATCH=$(mktemp -d) + git clone --depth 1 --branch "$NEW_TAG" \ + "https://github.com/${REPO}.git" \ + "$SCRATCH/upstream" + echo "scratch_dir=$SCRATCH/upstream" >> "$GITHUB_OUTPUT" + + - name: Sync declared assets + env: + PROJECT_ID: ${{ steps.detect.outputs.id }} + CLONE_DIR: ${{ steps.clone.outputs.scratch_dir }} + REPO: ${{ steps.detect.outputs.repo }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + run: | + node scripts/upstream-release/sync-assets.mjs \ + --id "$PROJECT_ID" \ + --clone "$CLONE_DIR" \ + --repo "$REPO" \ + --tag "$NEW_TAG" + + # toolhive ships CRD manifests as a release asset tarball (see + # stacklok/toolhive#4982). We extract it to a temp dir and run our + # opinionated transforms: extract per-CRD JSON schemas + examples, + # generate MDX reference pages. Bundling the upstream-registry + # schema (resolves the remote MCP-server $refs for our JSON-Schema + # viewer) also runs here. + - name: Extract CRDs + generate reference MDX (toolhive) + if: steps.detect.outputs.id == 'toolhive' + env: + REPO: ${{ steps.detect.outputs.repo }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + run: | + TMP=$(mktemp -d) + gh release download "$NEW_TAG" --repo "$REPO" \ + --pattern "thv-crds.tar.gz" --dir "$TMP" + mkdir -p "$TMP/crds" + tar -xzf "$TMP/thv-crds.tar.gz" -C "$TMP/crds" + TOOLHIVE_CRD_DIR="$TMP/crds" node scripts/extract-crd-schemas.mjs + node scripts/generate-crd-pages.mjs + node scripts/bundle-upstream-schema.mjs + rm -rf "$TMP" + + # Commit the refreshed reference assets (synced release-asset + # files + regenerated toolhive CRD MDX if applicable) before the + # skill runs. This keeps the skill's content commit clean and + # lets the autogen-detect step below distinguish skill touches + # from our own legitimate refresh writes. + - name: Commit refreshed reference assets + env: + PROJECT_ID: ${{ steps.detect.outputs.id }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + run: | + git add -A + if git diff --cached --quiet; then + echo "No reference changes for $PROJECT_ID $NEW_TAG." + else + git commit -m "Refresh reference assets for $PROJECT_ID $NEW_TAG" + fi + + - name: Extract reviewers from release compare + id: reviewers + env: + REPO: ${{ steps.detect.outputs.repo }} + PREV: ${{ steps.detect.outputs.prev_tag }} + NEW: ${{ steps.detect.outputs.new_tag }} + run: | + # Capture stderr separately so we can surface a missing-compare + # situation in the PR body rather than silently dropping reviewers. + if COMPARE=$(gh api "repos/$REPO/compare/$PREV...$NEW" \ + --jq '[.commits[].author.login? // empty] | unique | .[]' 2>/dev/null); then + REVIEWERS=$(echo "$COMPARE" | + grep -Ev '(\[bot\]$|^github-actions|^stacklokbot$|^dependabot|^renovate|^copilot)' | + head -5 | paste -sd, -) + echo "compare_ok=true" >> "$GITHUB_OUTPUT" + else + REVIEWERS="" + echo "compare_ok=false" >> "$GITHUB_OUTPUT" + fi + echo "list=$REVIEWERS" >> "$GITHUB_OUTPUT" + echo "Reviewers: ${REVIEWERS:-}" + + - name: Read docs_paths hint + id: hints + env: + PROJECT_ID: ${{ steps.detect.outputs.id }} + run: | + HINTS=$(node -e " + const yaml = require('yaml'); + const fs = require('fs'); + const p = yaml.parse(fs.readFileSync('.github/upstream-projects.yaml','utf8')).projects.find(x=>x.id===process.env.PROJECT_ID); + console.log(JSON.stringify(p?.docs_paths ?? [])); + ") + echo "docs_paths=$HINTS" >> "$GITHUB_OUTPUT" + + - name: Run upstream-release-docs skill (multi-pass) + id: skill + uses: anthropics/claude-code-action@38ec876110f9fbf8b950c79f534430740c3ac009 # v1 + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} + additional_permissions: | + actions: read + prompt: | + You are running in GitHub Actions with no interactive user. Follow + these steps exactly and do NOT ask clarifying questions -- proceed + best-effort at every decision point. + + PROJECT: ${{ steps.detect.outputs.id }} + REPO: ${{ steps.detect.outputs.repo }} + PREV_TAG: ${{ steps.detect.outputs.prev_tag }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + CLONE: ${{ steps.clone.outputs.scratch_dir }} + DOCS_HINTS: ${{ steps.hints.outputs.docs_paths }} + + PASS 1 -- Initial content update: + Run /upstream-release-docs ${{ steps.detect.outputs.repo }} ${{ steps.detect.outputs.new_tag }} + Execute all 6 phases. Prefer reading source code from the + local clone at ${{ steps.clone.outputs.scratch_dir }} + instead of `gh api contents?ref=` -- it's already at + the tag and doesn't consume API quota. + For Phase 2 step 4 (context on major new features), SKIP + writing the "why"/consumer narrative and append one bullet + per gap to GAPS.md at repo root (create if missing). Each + bullet MUST: + - Name the feature + - Reference the PR that introduced it, using the PR + number you found in Phase 2 deep-dive + - @-mention the PR author by their GitHub handle (skip + this for bot authors like renovate[bot] or + github-actions[bot]) + - Describe what context a human needs to supply + Format: `- Feature X (PR #123 by @alice): needs a user + story explaining who this is for and the expected + consumer workflow.` + This routes gaps to the engineer who built the feature + rather than the whole reviewer pool. + Follow the skill's own guidance on auto-generated reference + files (Phase 4 step 5, Phase 4 step 6) -- do not hand-edit + docs/toolhive/reference/cli/, static/api-specs/, or + docs/toolhive/reference/crds/. Those are synced or + regenerated from release assets by earlier steps in this + workflow; if a release genuinely needs hand-written + reference updates, note that in GAPS.md. + + PASS 2 -- Editorial re-review: + Run /docs-review over every file you changed in Pass 1 and + apply every actionable fix. Do NOT re-run upstream-release-docs; + you already have the source verification context in your + history. If docs-review surfaces a factual concern, re-verify + against source code at the tag before changing. + + PASS 3 -- Technical re-verification: + Re-run Phase 5 step 1 of /upstream-release-docs: re-verify + every factual claim in the changed files against source code + at the release tag. Fix any drift found. If no changes are + needed, say so explicitly. + Finally, re-run /docs-review one more time and apply any + remaining fixes. + + If at any point you conclude there are no doc-relevant changes + for this release (Phase 3 impact map is empty), stop and write + NO_CHANGES.md at repo root with a one-line explanation. Still + do not hand-edit any file. + + - name: Capture skill signal files + id: signals + run: | + GAPS_BODY="" + NO_CHANGES_BODY="" + if [ -f GAPS.md ]; then + GAPS_BODY=$(cat GAPS.md) + rm GAPS.md + fi + if [ -f NO_CHANGES.md ]; then + NO_CHANGES_BODY=$(cat NO_CHANGES.md) + rm NO_CHANGES.md + fi + + # Build full markdown fragments here so the PR body edit + # step can treat them as plain strings. + { + echo "note_block< [!NOTE]" + echo "> The skill reported no doc-relevant changes for this" + echo "> release. This PR only bumps the version reference" + echo "> and any pin_files substitutions." + echo ">" + echo "> $NO_CHANGES_BODY" + fi + echo "NOTE_EOF" + + echo "gaps_block<> "$GITHUB_OUTPUT" + + - name: Apply pin_files substitutions + env: + PROJECT_ID: ${{ steps.detect.outputs.id }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + run: | + node scripts/upstream-release/apply-pin-files.mjs \ + --id "$PROJECT_ID" \ + --tag "$NEW_TAG" + + - name: Detect touches to auto-generated paths + id: autogen + # Runs AFTER the skill and AFTER the refresh commit above, so + # the staged diff represents skill-introduced changes only. + run: | + git add -A + TOUCHED=$(git diff --cached --name-only -- \ + 'docs/toolhive/reference/cli/' \ + 'static/api-specs/' \ + 'docs/toolhive/reference/crds/' | paste -sd, - || true) + { + echo "note< [!WARNING]" + echo "> The skill touched files under auto-generated paths:" + echo "> \`$TOUCHED\`" + echo ">" + echo "> These paths are synced or regenerated from release" + echo "> assets earlier in this workflow. Review the skill's" + echo "> changes and revert them if they should come from the" + echo "> refresh step instead." + fi + echo "AUTOGEN_EOF" + } >> "$GITHUB_OUTPUT" + + - name: Commit and push + id: push + env: + PROJECT_ID: ${{ steps.detect.outputs.id }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + HEAD_REF: ${{ steps.eff.outputs.head_ref }} + run: | + # Stage any skill content and add a content commit if non-empty. + # A refresh commit from the earlier step may also be waiting. + git add -A + if ! git diff --cached --quiet; then + git commit -m "Add upstream-release-docs content for $PROJECT_ID $NEW_TAG" + else + echo "No skill content changes to commit." + fi + # Push whatever local commits are ahead of the remote — refresh + # only, content only, or both. Empty push is a no-op. + git push origin "HEAD:$HEAD_REF" + + - name: Augment PR body (marker-delimited section) + # Runs even if earlier steps soft-failed so the augmentation + # survives partial failures; a subsequent workflow_dispatch + # retry will re-enter here. + if: always() && steps.detect.outputs.id != '' + env: + PR_NUMBER: ${{ steps.eff.outputs.number }} + PROJECT_ID: ${{ steps.detect.outputs.id }} + NEW_TAG: ${{ steps.detect.outputs.new_tag }} + PREV_TAG: ${{ steps.detect.outputs.prev_tag }} + REPO: ${{ steps.detect.outputs.repo }} + NOTE_BLOCK: ${{ steps.signals.outputs.note_block }} + GAPS_BLOCK: ${{ steps.signals.outputs.gaps_block }} + AUTOGEN_NOTE: ${{ steps.autogen.outputs.note }} + COMPARE_OK: ${{ steps.reviewers.outputs.compare_ok }} + run: | + START='' + END='' + + # Build our section. + { + echo "$START" + echo "" + echo "## Content additions by upstream-release-docs" + echo "" + echo "Source-verified against \`$REPO\` at tag \`$NEW_TAG\` (was \`$PREV_TAG\`). The \`upstream-release-docs\` and \`docs-review\` skills each ran twice (three total passes) before this update." + echo "" + if [ "$COMPARE_OK" != "true" ]; then + echo "> [!WARNING]" + echo "> Could not compare \`$PREV_TAG\` against \`$NEW_TAG\` upstream, so no reviewers were auto-assigned from release contributors. The pinned previous tag may have been retagged or deleted." + echo "" + fi + if [ -n "$NOTE_BLOCK" ]; then + echo "$NOTE_BLOCK" + echo "" + fi + if [ -n "$AUTOGEN_NOTE" ]; then + echo "$AUTOGEN_NOTE" + echo "" + fi + echo "### Review guidance" + echo "" + echo "Machine-generated reference files under \`docs/toolhive/reference/cli/\`, \`static/api-specs/\`, and \`docs/toolhive/reference/crds/\` are synced or regenerated from upstream release assets (separate commit, titled \"Refresh reference assets\") and should be spot-checked only. The \"Add upstream-release-docs content\" commit contains hand-edited prose; review that one for accuracy, not just style. If the \"Gaps needing human context\" section is populated, the skill deferred those sections to a human; fill them in before merging." + echo "" + if [ -n "$GAPS_BLOCK" ]; then + echo "$GAPS_BLOCK" + echo "" + fi + echo "Reviewers below are non-bot commit authors in the release range." + echo "" + echo "$END" + } > /tmp/section.md + + # Read existing body, replace or append our marked section. + EXISTING=$(gh pr view "$PR_NUMBER" --json body --jq .body) + if echo "$EXISTING" | grep -qF "$START"; then + # Replace existing section. + printf '%s\n' "$EXISTING" | awk -v start="$START" -v end="$END" -v repl_file=/tmp/section.md ' + BEGIN { in_section = 0 } + $0 == start { in_section = 1; while ((getline line < repl_file) > 0) print line; next } + $0 == end { if (in_section) { in_section = 0; next } } + !in_section { print } + ' > /tmp/pr-body.md + else + # Append. + { + printf '%s\n\n---\n\n' "$EXISTING" + cat /tmp/section.md + } > /tmp/pr-body.md + fi + + gh pr edit "$PR_NUMBER" --body-file /tmp/pr-body.md + + - name: Add reviewers + if: always() && steps.reviewers.outputs.list != '' + env: + PR_NUMBER: ${{ steps.eff.outputs.number }} + REVIEWERS: ${{ steps.reviewers.outputs.list }} + run: gh pr edit "$PR_NUMBER" --add-reviewer "$REVIEWERS" + + - name: Comment on augmentation failure + # Runs only when a preceding step failed. Comments a retry + # pointer on the PR so a human can see the run URL. + if: failure() + env: + PR_NUMBER: ${{ steps.eff.outputs.number }} + RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }} + run: | + gh pr comment "$PR_NUMBER" --body "Automated docs augmentation failed. Run: $RUN_URL + + Retry via the \`Upstream Release Docs\` workflow with \`pr_number=$PR_NUMBER\` once the underlying issue is resolved." || true diff --git a/.gitignore b/.gitignore index 62208381..a599175b 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,9 @@ yarn-error.log* .vercel .idea .claude/settings.local.json + +# Signal files written by upstream-release-docs.yml for the skill to +# communicate gaps or no-op releases back to the workflow. Never committed. +/GAPS.md +/NO_CHANGES.md +.claude/scheduled_tasks.lock diff --git a/docs/toolhive/reference/crds/embeddingserver.mdx b/docs/toolhive/reference/crds/embeddingserver.mdx index b47e7051..ef1139db 100644 --- a/docs/toolhive/reference/crds/embeddingserver.mdx +++ b/docs/toolhive/reference/crds/embeddingserver.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `EmbeddingServer` defines a containerized embedding model server managed by the ToolHive operator. The [VirtualMCPServer](./virtualmcpserver.mdx) optimizer references an `EmbeddingServer` to generate vector embeddings for tool discovery. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `emb`, `embedding` ## Example ```yaml title="embeddingserver.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: EmbeddingServer metadata: name: my-embeddingserver diff --git a/docs/toolhive/reference/crds/mcpexternalauthconfig.mdx b/docs/toolhive/reference/crds/mcpexternalauthconfig.mdx index 21386526..6d277afc 100644 --- a/docs/toolhive/reference/crds/mcpexternalauthconfig.mdx +++ b/docs/toolhive/reference/crds/mcpexternalauthconfig.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPExternalAuthConfig` configures how an MCP server or proxy authenticates to external services via token exchange or an embedded authorization server. It is referenced by [MCPServer](./mcpserver.mdx), [MCPRemoteProxy](./mcpremoteproxy.mdx), [MCPServerEntry](./mcpserverentry.mdx), and [VirtualMCPServer](./virtualmcpserver.mdx). -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `extauth`, `mcpextauth` ## Example ```yaml title="mcpexternalauthconfig.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPExternalAuthConfig metadata: name: my-mcpexternalauthconfig diff --git a/docs/toolhive/reference/crds/mcpgroup.mdx b/docs/toolhive/reference/crds/mcpgroup.mdx index 58858fdc..afaef5a0 100644 --- a/docs/toolhive/reference/crds/mcpgroup.mdx +++ b/docs/toolhive/reference/crds/mcpgroup.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPGroup` is a grouping construct for backend workloads. Other resources reference an `MCPGroup` by name to join a shared pool - for example, a [VirtualMCPServer](./virtualmcpserver.mdx) aggregates the tools exposed by every member of its referenced group. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpg`, `mcpgroup` ## Example ```yaml title="mcpgroup.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPGroup metadata: name: my-mcpgroup diff --git a/docs/toolhive/reference/crds/mcpoidcconfig.mdx b/docs/toolhive/reference/crds/mcpoidcconfig.mdx index bd1b3abb..6378e1dc 100644 --- a/docs/toolhive/reference/crds/mcpoidcconfig.mdx +++ b/docs/toolhive/reference/crds/mcpoidcconfig.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPOIDCConfig` defines OIDC authentication settings that can be shared across multiple MCP workloads. [MCPServer](./mcpserver.mdx), [MCPRemoteProxy](./mcpremoteproxy.mdx), and [VirtualMCPServer](./virtualmcpserver.mdx) reference an `MCPOIDCConfig` via `spec.oidcConfigRef` to validate incoming tokens. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpoidc` ## Example ```yaml title="mcpoidcconfig.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPOIDCConfig metadata: name: my-mcpoidcconfig diff --git a/docs/toolhive/reference/crds/mcpregistry.mdx b/docs/toolhive/reference/crds/mcpregistry.mdx index bce1f42a..65d494ed 100644 --- a/docs/toolhive/reference/crds/mcpregistry.mdx +++ b/docs/toolhive/reference/crds/mcpregistry.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPRegistry` deploys a [ToolHive Registry Server](../../guides-registry/intro.mdx) in the cluster. The operator watches `MCPRegistry` resources and provisions the Registry Server, its PostgreSQL backing, and the configured sources (Git, ConfigMap, URL, or Kubernetes discovery) that populate its catalog of MCP server definitions. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpreg`, `registry` ## Example ```yaml title="mcpregistry.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPRegistry metadata: name: my-mcpregistry diff --git a/docs/toolhive/reference/crds/mcpremoteproxy.mdx b/docs/toolhive/reference/crds/mcpremoteproxy.mdx index c163513d..65c82f03 100644 --- a/docs/toolhive/reference/crds/mcpremoteproxy.mdx +++ b/docs/toolhive/reference/crds/mcpremoteproxy.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPRemoteProxy` fronts a remote MCP server (reachable over HTTPS) with the same authentication, telemetry, and tool-filtering features that the operator applies to containerized servers. Use this when you want to apply ToolHive policies to a third-party hosted MCP endpoint. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `rp`, `mcprp` ## Example ```yaml title="mcpremoteproxy.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPRemoteProxy metadata: name: my-mcpremoteproxy diff --git a/docs/toolhive/reference/crds/mcpserver.mdx b/docs/toolhive/reference/crds/mcpserver.mdx index 4489bfb7..9792fc40 100644 --- a/docs/toolhive/reference/crds/mcpserver.mdx +++ b/docs/toolhive/reference/crds/mcpserver.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPServer` defines a containerized MCP server managed by the ToolHive Kubernetes operator. The operator watches `MCPServer` resources and reconciles them into a running, proxied MCP server with the configured transport, authentication, telemetry, and tool filtering. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpserver`, `mcpservers` ## Example ```yaml title="mcpserver.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPServer metadata: name: my-mcpserver diff --git a/docs/toolhive/reference/crds/mcpserverentry.mdx b/docs/toolhive/reference/crds/mcpserverentry.mdx index 60f07127..cb611bd8 100644 --- a/docs/toolhive/reference/crds/mcpserverentry.mdx +++ b/docs/toolhive/reference/crds/mcpserverentry.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPServerEntry` declares a remote MCP server as a first-class member of an [MCPGroup](./mcpgroup.mdx) without running a full [MCPRemoteProxy](./mcpremoteproxy.mdx). Entries appear in registry listings and participate in group-scoped aggregations like a [VirtualMCPServer](./virtualmcpserver.mdx). -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpentry` ## Example ```yaml title="mcpserverentry.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPServerEntry metadata: name: my-mcpserverentry diff --git a/docs/toolhive/reference/crds/mcptelemetryconfig.mdx b/docs/toolhive/reference/crds/mcptelemetryconfig.mdx index 23479459..b81e7bec 100644 --- a/docs/toolhive/reference/crds/mcptelemetryconfig.mdx +++ b/docs/toolhive/reference/crds/mcptelemetryconfig.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPTelemetryConfig` defines telemetry settings that can be shared across multiple MCP workloads. [MCPServer](./mcpserver.mdx), [MCPRemoteProxy](./mcpremoteproxy.mdx), and [VirtualMCPServer](./virtualmcpserver.mdx) reference a single `MCPTelemetryConfig` to emit traces and metrics to a common backend. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `mcpotel` ## Example ```yaml title="mcptelemetryconfig.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPTelemetryConfig metadata: name: my-mcptelemetryconfig diff --git a/docs/toolhive/reference/crds/mcptoolconfig.mdx b/docs/toolhive/reference/crds/mcptoolconfig.mdx index e20fe728..fe0c696c 100644 --- a/docs/toolhive/reference/crds/mcptoolconfig.mdx +++ b/docs/toolhive/reference/crds/mcptoolconfig.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `MCPToolConfig` defines tool-filtering and renaming rules that can be shared across MCP workloads. [MCPServer](./mcpserver.mdx), [MCPRemoteProxy](./mcpremoteproxy.mdx), and [VirtualMCPServer](./virtualmcpserver.mdx) reference an `MCPToolConfig` via `spec.toolConfigRef` to customize the tool surface their clients see. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `tc`, `toolconfig` ## Example ```yaml title="mcptoolconfig.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPToolConfig metadata: name: my-mcptoolconfig diff --git a/docs/toolhive/reference/crds/virtualmcpcompositetooldefinition.mdx b/docs/toolhive/reference/crds/virtualmcpcompositetooldefinition.mdx index fc942327..07ca47c7 100644 --- a/docs/toolhive/reference/crds/virtualmcpcompositetooldefinition.mdx +++ b/docs/toolhive/reference/crds/virtualmcpcompositetooldefinition.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `VirtualMCPCompositeToolDefinition` defines a reusable composite tool workflow - a sequence of backend tool calls exposed to clients as a single high-level tool. Referenced by a [VirtualMCPServer](./virtualmcpserver.mdx) via `spec.config.compositeToolRefs`. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `vmcpctd`, `compositetool` ## Example ```yaml title="virtualmcpcompositetooldefinition.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: VirtualMCPCompositeToolDefinition metadata: name: my-virtualmcpcompositetooldefinition diff --git a/docs/toolhive/reference/crds/virtualmcpserver.mdx b/docs/toolhive/reference/crds/virtualmcpserver.mdx index 4d83ff49..b305ae6a 100644 --- a/docs/toolhive/reference/crds/virtualmcpserver.mdx +++ b/docs/toolhive/reference/crds/virtualmcpserver.mdx @@ -8,13 +8,13 @@ toc_max_heading_level: 4 `VirtualMCPServer` (vMCP) aggregates the backend workloads belonging to an [MCPGroup](./mcpgroup.mdx) into a single endpoint. Clients see one MCP server; the operator handles tool aggregation, conflict resolution, auth, and optional composite tool workflows behind the scenes. -**API:** `toolhive.stacklok.dev/v1alpha1` +**API:** `toolhive.stacklok.dev/v1beta1` · **Scope:** Namespaced · **Short names:** `vmcp`, `virtualmcp` ## Example ```yaml title="virtualmcpserver.yaml" -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: VirtualMCPServer metadata: name: my-virtualmcpserver diff --git a/docusaurus.config.ts b/docusaurus.config.ts index 89136d28..0be1ed9f 100644 --- a/docusaurus.config.ts +++ b/docusaurus.config.ts @@ -214,7 +214,7 @@ const config: Config = { }, { id: 'toolhive-registry-api', - spec: 'https://cdn.jsdelivr.net/gh/stacklok/toolhive-registry-server@latest/docs/thv-registry-api/swagger.yaml', + spec: 'static/api-specs/toolhive-registry-api.yaml', config: path.join(__dirname, 'src/redocly/redocly-toolhive.yaml'), }, ], diff --git a/renovate.json b/renovate.json index 3ba57eb1..5ea18e23 100644 --- a/renovate.json +++ b/renovate.json @@ -32,6 +32,15 @@ ], "datasourceTemplate": "docker", "versioningTemplate": "docker" + }, + { + "customType": "regex", + "description": "Track upstream ToolHive project versions for docs updates. PRs this opens are augmented by .github/workflows/upstream-release-docs.yml.", + "managerFilePatterns": ["/^\\.github/upstream-projects\\.yaml$/"], + "matchStrings": [ + "repo:\\s*(?[\\w.\\-/]+)\\s+version:\\s*(?v[\\w.+\\-]+)" + ], + "datasourceTemplate": "github-releases" } ], "packageRules": [ @@ -74,6 +83,20 @@ "internalChecksFilter": "strict", "prCreation": "not-pending", "labels": ["documentation", "mcp-guides"] + }, + { + "matchManagers": ["custom.regex"], + "matchFileNames": ["**/.github/upstream-projects.yaml"], + "schedule": ["at any time"], + "minimumReleaseAge": "24 hours", + "minimumReleaseAgeBehaviour": "timestamp-optional", + "ignoreUnstable": true, + "rebaseWhen": "never", + "recreateWhen": "never", + "commitMessageTopic": "{{depName}}", + "prBodyNotes": [ + "After this PR opens, `.github/workflows/upstream-release-docs.yml` adds source-verified content edits for the new release. For `stacklok/toolhive`, the same workflow also regenerates reference docs (CLI help, Swagger, CRD schemas)." + ] } ] } diff --git a/scripts/update-toolhive-reference.sh b/scripts/update-toolhive-reference.sh deleted file mode 100755 index 4ce40e63..00000000 --- a/scripts/update-toolhive-reference.sh +++ /dev/null @@ -1,131 +0,0 @@ -#!/bin/bash -# SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc. -# SPDX-License-Identifier: Apache-2.0 - -set -euo pipefail - -REPO_ROOT=$(git rev-parse --show-toplevel) -cd "$REPO_ROOT" - -DOCS_DIR="./docs" -STATIC_DIR="./static" - -CLI_DOCS_DST="${DOCS_DIR}/toolhive/reference/cli" -API_SPEC_DST="${STATIC_DIR}/api-specs/toolhive-api.yaml" -REGISTRY_SCHEMA_DST="${STATIC_DIR}/api-specs/toolhive-legacy-registry.schema.json" -UPSTREAM_REGISTRY_SCHEMA_DST="${STATIC_DIR}/api-specs/upstream-registry.schema.json" -REGISTRY_META_SCHEMA_DST="${STATIC_DIR}/api-specs/publisher-provided.schema.json" -SKILL_SCHEMA_DST="${STATIC_DIR}/api-specs/skill.schema.json" - -if [ ! -d "$DOCS_DIR" ]; then - echo "Docs directory does not exist: $DOCS_DIR" - exit 1 -fi -if [ ! -d "$STATIC_DIR" ]; then - echo "Static directory does not exist: $STATIC_DIR" - exit 1 -fi - -# Check if gh CLI is installed -if ! command -v gh >/dev/null 2>&1; then - echo "Error: 'gh' is required but not installed." - exit 1 -fi - -VERSION=$(echo "${1:-}" | tr -cd '[:alnum:].-') - -# Resolve to actual tag if "latest" or unset -if [ -z "$VERSION" ] || [ "$VERSION" = "latest" ]; then - echo "No tag specified or 'latest' specified, resolving latest release..." - VERSION=$(gh release view --repo stacklok/toolhive --json tagName --jq '.tagName') - echo "Resolved to: $VERSION" -else - echo "Using specified tag: $VERSION" -fi - -# Output the release version for use in CI workflows (if running in GitHub Actions) -if [ -n "${GITHUB_OUTPUT:-}" ]; then - echo "version=$VERSION" >> "$GITHUB_OUTPUT" -fi - -DOWNLOAD_DIR=$(mktemp -d) -trap 'rm -rf "$DOWNLOAD_DIR"' EXIT - -## ToolHive assets -echo "Downloading ToolHive release assets for ${VERSION}..." - -gh release download "$VERSION" \ - --repo stacklok/toolhive \ - --pattern "thv-cli-docs.tar.gz" \ - --pattern "swagger.yaml" \ - --dir "$DOWNLOAD_DIR" - -## CLI reference -echo "Updating ToolHive CLI reference documentation in ${CLI_DOCS_DST}" -# Remove existing CLI reference documentation files in case we remove any commands -rm -f "${CLI_DOCS_DST}"/thv_*.md -tar -xzf "${DOWNLOAD_DIR}/thv-cli-docs.tar.gz" -C "${CLI_DOCS_DST}" -echo "CLI reference documentation updated successfully" - -## API reference -echo "Updating ToolHive API reference at ${API_SPEC_DST}" -cp "${DOWNLOAD_DIR}/swagger.yaml" "${API_SPEC_DST}" -echo "API reference updated successfully" - -## CRD API reference -# Fetch the CRD manifests for this release and generate per-CRD schema files -# and MDX pages. CRDs live in the operator Helm chart inside the repo, so we -# clone the tag shallowly rather than using release assets. -echo "Fetching ToolHive CRD manifests for ${VERSION}..." -TOOLHIVE_CLONE_DIR="${DOWNLOAD_DIR}/toolhive" -git clone --depth 1 --branch "$VERSION" \ - https://github.com/stacklok/toolhive.git \ - "$TOOLHIVE_CLONE_DIR" -CRD_SRC="${TOOLHIVE_CLONE_DIR}/deploy/charts/operator-crds/files/crds" - -echo "Extracting CRD schemas and examples..." -TOOLHIVE_CRD_DIR="$CRD_SRC" node "${REPO_ROOT}/scripts/extract-crd-schemas.mjs" - -echo "Generating CRD reference pages..." -node "${REPO_ROOT}/scripts/generate-crd-pages.mjs" -echo "CRD API reference updated successfully" - -## Derive toolhive-core version from go.mod at the tagged commit -echo "Determining toolhive-core version from go.mod at tag ${VERSION}..." -CORE_VERSION=$(gh api "repos/stacklok/toolhive/contents/go.mod?ref=${VERSION}" \ - --jq '.content' | base64 -d | grep 'github.com/stacklok/toolhive-core' | awk '{print $2}' | head -1) - -if [ -z "$CORE_VERSION" ]; then - echo "Warning: Could not determine toolhive-core version from go.mod; falling back to latest release" - CORE_VERSION=$(gh release view --repo stacklok/toolhive-core --json tagName --jq '.tagName') -fi -echo "Using toolhive-core version: ${CORE_VERSION}" - -## toolhive-core schema assets -echo "Downloading toolhive-core schema assets for ${CORE_VERSION}..." - -gh release download "$CORE_VERSION" \ - --repo stacklok/toolhive-core \ - --pattern "toolhive-legacy-registry.schema.json" \ - --pattern "upstream-registry.schema.json" \ - --pattern "publisher-provided.schema.json" \ - --pattern "skill.schema.json" \ - --dir "$DOWNLOAD_DIR" - -cp "${DOWNLOAD_DIR}/toolhive-legacy-registry.schema.json" "${REGISTRY_SCHEMA_DST}" -echo "Registry JSON schema updated successfully" - -cp "${DOWNLOAD_DIR}/upstream-registry.schema.json" "${UPSTREAM_REGISTRY_SCHEMA_DST}" -echo "Upstream registry JSON schema updated successfully" - -# Bundle the upstream schema to resolve remote $ref references -echo "Bundling upstream registry schema (resolving remote references)..." -node "${REPO_ROOT}/scripts/bundle-upstream-schema.mjs" - -cp "${DOWNLOAD_DIR}/publisher-provided.schema.json" "${REGISTRY_META_SCHEMA_DST}" -echo "Registry extensions JSON schema updated successfully" - -cp "${DOWNLOAD_DIR}/skill.schema.json" "${SKILL_SCHEMA_DST}" -echo "Skill JSON schema updated successfully" - -echo "Release processing completed for: $VERSION (toolhive-core: $CORE_VERSION)" \ No newline at end of file diff --git a/scripts/upstream-release/apply-pin-files.mjs b/scripts/upstream-release/apply-pin-files.mjs new file mode 100644 index 00000000..fa9ce333 --- /dev/null +++ b/scripts/upstream-release/apply-pin-files.mjs @@ -0,0 +1,79 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Applies pin_files substitutions declared for a project in +// .github/upstream-projects.yaml. Called by the upstream-release-docs +// workflow after Renovate has already bumped the `version:` field. +// +// Usage: +// node apply-pin-files.mjs --id --tag +// +// pin_files entries supported: +// { path: '', replace_latest: true } +// Flips `@latest` (or `@vX.Y.Z`) to `@` +// so unrelated `@latest` strings elsewhere in the file are safe. + +import fs from 'node:fs'; +import yaml from 'yaml'; + +const PROJECTS_FILE = '.github/upstream-projects.yaml'; + +function parseArgs(argv) { + const args = {}; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === '--id') { + args.id = argv[i + 1]; + i++; + } else if (argv[i] === '--tag') { + args.tag = argv[i + 1]; + i++; + } + } + return args; +} + +function applyPinFiles(project, newTag) { + for (const pin of project.pin_files ?? []) { + const filePath = pin.path; + if (!fs.existsSync(filePath)) { + console.error(`pin_files: skipping missing file ${filePath}`); + continue; + } + let content = fs.readFileSync(filePath, 'utf8'); + let updated = content; + if (pin.replace_latest) { + const repoEscaped = project.repo.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + const pattern = new RegExp( + `(${repoEscaped})@(?:latest|v[\\w.\\-]+)`, + 'g' + ); + updated = updated.replace(pattern, `$1@${newTag}`); + } + if (updated !== content) { + fs.writeFileSync(filePath, updated); + console.log(`pin_files: updated ${filePath}`); + } + } +} + +function main() { + const { id, tag } = parseArgs(process.argv.slice(2)); + if (!id || !tag) { + console.error( + 'Usage: apply-pin-files.mjs --id --tag ' + ); + process.exit(1); + } + + const parsed = yaml.parse(fs.readFileSync(PROJECTS_FILE, 'utf8')); + const project = parsed.projects.find((p) => p.id === id); + if (!project) { + console.error(`Unknown project id: ${id}`); + process.exit(1); + } + + applyPinFiles(project, tag); +} + +main(); diff --git a/scripts/upstream-release/bump-yaml.mjs b/scripts/upstream-release/bump-yaml.mjs new file mode 100644 index 00000000..edd755a9 --- /dev/null +++ b/scripts/upstream-release/bump-yaml.mjs @@ -0,0 +1,90 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Bumps the `version:` field for the given project id in +// .github/upstream-projects.yaml. Used by the manual-dispatch +// bootstrap path where a human kicks off the workflow with an +// explicit project_id + new_tag instead of waiting for Renovate. +// +// The auto (Renovate-driven) path does NOT use this script — +// Renovate already bumps the version itself. +// +// Usage: +// node bump-yaml.mjs --id --tag +// +// Fails if the project is not in the YAML or the tag already +// matches (no-op — caller should not open a PR). + +import fs from 'node:fs'; +import yaml from 'yaml'; + +const PROJECTS_FILE = '.github/upstream-projects.yaml'; + +function parseArgs(argv) { + const args = {}; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === '--id') { + args.id = argv[i + 1]; + i++; + } else if (argv[i] === '--tag') { + args.tag = argv[i + 1]; + i++; + } + } + return args; +} + +// Preserve comments and formatting by editing the raw text. +function bumpText(text, id, newTag) { + const lines = text.split('\n'); + let inProject = false; + let changed = false; + let previous = null; + for (let i = 0; i < lines.length; i++) { + const idMatch = lines[i].match(/^\s*-\s*id:\s*(\S+)/); + if (idMatch) { + inProject = idMatch[1] === id; + continue; + } + if (inProject && /^\s*version:\s*/.test(lines[i])) { + const m = lines[i].match(/version:\s*(\S+)/); + previous = m ? m[1] : null; + lines[i] = lines[i].replace(/(version:\s*)\S+/, `$1${newTag}`); + changed = true; + break; + } + } + if (!changed) { + throw new Error(`Did not find version: line for project id ${id}`); + } + return { text: lines.join('\n'), previous }; +} + +function main() { + const { id, tag } = parseArgs(process.argv.slice(2)); + if (!id || !tag) { + console.error('Usage: bump-yaml.mjs --id --tag '); + process.exit(1); + } + + const raw = fs.readFileSync(PROJECTS_FILE, 'utf8'); + const parsed = yaml.parse(raw); + if (!parsed.projects.find((p) => p.id === id)) { + console.error(`Unknown project id: ${id}`); + process.exit(1); + } + + const { text, previous } = bumpText(raw, id, tag); + if (previous === tag) { + console.error( + `Project ${id} is already pinned at ${tag}; nothing to bump.` + ); + process.exit(1); + } + + fs.writeFileSync(PROJECTS_FILE, text); + console.log(`Bumped ${id}: ${previous} -> ${tag}`); +} + +main(); diff --git a/scripts/upstream-release/detect-change.mjs b/scripts/upstream-release/detect-change.mjs new file mode 100644 index 00000000..fa79702f --- /dev/null +++ b/scripts/upstream-release/detect-change.mjs @@ -0,0 +1,103 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Detects which project's version: changed between the PR branch and +// origin/main in .github/upstream-projects.yaml. Emits GITHUB_OUTPUT: +// +// id= +// repo= +// prev_tag= +// new_tag= +// +// Fails if: +// - zero projects changed +// - more than one project changed (Renovate is configured not to batch, +// but we fail loudly if that ever slips) +// - the `repo:` field for the changed project was also modified (a PR +// that edits both `repo:` and `version:` could point the workflow at +// a hostile clone URL; only Renovate version bumps should reach this) +// +// Set BASE_REF to override origin/main for local testing. + +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import yaml from 'yaml'; + +const PROJECTS_FILE = '.github/upstream-projects.yaml'; +const REPO_SHAPE = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/; + +function emit(key, value) { + const out = process.env.GITHUB_OUTPUT; + const line = `${key}=${value}\n`; + if (out) { + fs.appendFileSync(out, line); + } else { + process.stdout.write(line); + } +} + +function loadFromRef(ref) { + const text = execFileSync('git', ['show', `${ref}:${PROJECTS_FILE}`], { + encoding: 'utf8', + }); + return yaml.parse(text).projects; +} + +function main() { + const ref = process.env.BASE_REF || 'origin/main'; + const mainProjects = loadFromRef(ref); + const headProjects = yaml.parse( + fs.readFileSync(PROJECTS_FILE, 'utf8') + ).projects; + + const changed = []; + for (const pr of headProjects) { + const base = mainProjects.find((m) => m.id === pr.id); + if (!base) continue; // new project added in this PR; not a version bump + if (base.version !== pr.version) { + if (base.repo !== pr.repo) { + console.error( + `Project ${pr.id} changed repo: ${base.repo} -> ${pr.repo}. ` + + `This workflow only handles version bumps; repo changes must be ` + + `reviewed and merged by a human before version updates proceed.` + ); + process.exit(1); + } + if (!REPO_SHAPE.test(pr.repo)) { + console.error(`Project ${pr.id} has malformed repo value: ${pr.repo}`); + process.exit(1); + } + changed.push({ + id: pr.id, + repo: pr.repo, + prev_tag: base.version, + new_tag: pr.version, + }); + } + } + + if (changed.length === 0) { + console.error( + 'No version changes detected in .github/upstream-projects.yaml' + ); + process.exit(1); + } + if (changed.length > 1) { + console.error( + `Multiple projects changed in one PR: ${changed + .map((c) => c.id) + .join(', ')}. Split the PR or dispatch the workflow per project.` + ); + process.exit(1); + } + + const c = changed[0]; + emit('id', c.id); + emit('repo', c.repo); + emit('prev_tag', c.prev_tag); + emit('new_tag', c.new_tag); + console.log(`Detected: ${c.id} ${c.prev_tag} -> ${c.new_tag}`); +} + +main(); diff --git a/scripts/upstream-release/sync-assets.mjs b/scripts/upstream-release/sync-assets.mjs new file mode 100644 index 00000000..bc1213b9 --- /dev/null +++ b/scripts/upstream-release/sync-assets.mjs @@ -0,0 +1,186 @@ +#!/usr/bin/env node +// SPDX-FileCopyrightText: Copyright 2026 Stacklok, Inc. +// SPDX-License-Identifier: Apache-2.0 + +// Copies declared upstream assets into the docs repo for the given +// project. Assets are declared per-project in +// .github/upstream-projects.yaml under `assets:`. Three source kinds: +// +// - source: # file in the shallow clone +// destination: +// +// - release_asset: # GitHub release asset +// destination: +// +// - release_asset: +// destination: +// extract: tar-gz # extract tarball into dir +// +// Usage: +// node sync-assets.mjs --id --clone [--repo ] +// +// `--clone` is required for `source:` entries (copies from the clone). +// `--repo` defaults to the project's repo from the YAML and is used +// for `release_asset:` downloads. +// +// No-op if the project declares no assets. + +import { execFileSync } from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; +import path from 'node:path'; +import yaml from 'yaml'; + +const PROJECTS_FILE = '.github/upstream-projects.yaml'; + +function parseArgs(argv) { + const args = {}; + for (let i = 0; i < argv.length; i++) { + if (argv[i] === '--id') { + args.id = argv[i + 1]; + i++; + } else if (argv[i] === '--clone') { + args.clone = argv[i + 1]; + i++; + } else if (argv[i] === '--repo') { + args.repo = argv[i + 1]; + i++; + } else if (argv[i] === '--tag') { + args.tag = argv[i + 1]; + i++; + } + } + return args; +} + +function syncFromClone(asset, cloneDir) { + const { source, destination } = asset; + const srcPath = path.join(cloneDir, source); + if (!fs.existsSync(srcPath)) { + console.error( + `Source not found in clone: ${source} (resolved to ${srcPath})` + ); + process.exit(1); + } + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.copyFileSync(srcPath, destination); + console.log(`assets: synced ${source} -> ${destination}`); +} + +function syncFromReleaseAsset(asset, repo, tag) { + const { release_asset: assetName, destination, extract } = asset; + if (!tag) { + console.error( + `release_asset entries require --tag; got: ${JSON.stringify(asset)}` + ); + process.exit(1); + } + const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'sync-assets-')); + try { + // gh release download writes the asset verbatim into --dir. + execFileSync( + 'gh', + [ + 'release', + 'download', + tag, + '--repo', + repo, + '--pattern', + assetName, + '--dir', + tmp, + ], + { stdio: 'inherit' } + ); + const downloadedPath = path.join(tmp, assetName); + if (!fs.existsSync(downloadedPath)) { + console.error( + `Download did not produce expected file: ${downloadedPath}` + ); + process.exit(1); + } + if (extract === 'tar-gz') { + fs.mkdirSync(destination, { recursive: true }); + // Strip pre-existing contents so removed upstream files are reflected. + for (const entry of fs.readdirSync(destination)) { + if (entry.startsWith('thv_') || entry.endsWith('.md')) { + fs.rmSync(path.join(destination, entry), { + recursive: true, + force: true, + }); + } + } + execFileSync('tar', ['-xzf', downloadedPath, '-C', destination], { + stdio: 'inherit', + }); + console.log( + `assets: extracted release_asset ${assetName} -> ${destination}/` + ); + } else if (extract) { + console.error(`Unsupported extract directive: ${extract}`); + process.exit(1); + } else { + fs.mkdirSync(path.dirname(destination), { recursive: true }); + fs.copyFileSync(downloadedPath, destination); + console.log( + `assets: downloaded release_asset ${assetName} -> ${destination}` + ); + } + } finally { + fs.rmSync(tmp, { recursive: true, force: true }); + } +} + +function main() { + const { id, clone, repo: repoArg, tag } = parseArgs(process.argv.slice(2)); + if (!id) { + console.error( + 'Usage: sync-assets.mjs --id --clone [--tag ] [--repo ]' + ); + process.exit(1); + } + + const parsed = yaml.parse(fs.readFileSync(PROJECTS_FILE, 'utf8')); + const project = parsed.projects.find((p) => p.id === id); + if (!project) { + console.error(`Unknown project id: ${id}`); + process.exit(1); + } + + const assets = project.assets ?? []; + if (assets.length === 0) { + console.log(`No assets declared for ${id}; nothing to sync.`); + return; + } + + const repo = repoArg || project.repo; + const resolvedTag = tag || project.version; + + for (const asset of assets) { + if (!asset.destination) { + console.error( + `Asset entry missing destination: ${JSON.stringify(asset)}` + ); + process.exit(1); + } + if (asset.source) { + if (!clone) { + console.error( + `Asset with source: needs --clone. Entry: ${JSON.stringify(asset)}` + ); + process.exit(1); + } + syncFromClone(asset, clone); + } else if (asset.release_asset) { + syncFromReleaseAsset(asset, repo, resolvedTag); + } else { + console.error( + `Asset entry has neither source nor release_asset: ${JSON.stringify(asset)}` + ); + process.exit(1); + } + } +} + +main(); diff --git a/static/api-specs/crds/embeddingservers.example.yaml b/static/api-specs/crds/embeddingservers.example.yaml index 46b7e686..34b19103 100644 --- a/static/api-specs/crds/embeddingservers.example.yaml +++ b/static/api-specs/crds/embeddingservers.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: EmbeddingServer metadata: name: my-embeddingserver diff --git a/static/api-specs/crds/embeddingservers.schema.json b/static/api-specs/crds/embeddingservers.schema.json index 8ddc9780..5b6d2253 100644 --- a/static/api-specs/crds/embeddingservers.schema.json +++ b/static/api-specs/crds/embeddingservers.schema.json @@ -4,7 +4,7 @@ "description": "EmbeddingServer is the Schema for the embeddingservers API", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "EmbeddingServer", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "embeddingservers", "x-kubernetes-short-names": [ "emb", diff --git a/static/api-specs/crds/index.json b/static/api-specs/crds/index.json index cd98ea98..df6b84a2 100644 --- a/static/api-specs/crds/index.json +++ b/static/api-specs/crds/index.json @@ -3,7 +3,7 @@ "kind": "EmbeddingServer", "plural": "embeddingservers", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "emb", "embedding" @@ -25,7 +25,7 @@ "kind": "MCPExternalAuthConfig", "plural": "mcpexternalauthconfigs", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "extauth", "mcpextauth" @@ -68,7 +68,7 @@ "kind": "MCPGroup", "plural": "mcpgroups", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpg", "mcpgroup" @@ -109,7 +109,7 @@ "kind": "MCPOIDCConfig", "plural": "mcpoidcconfigs", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpoidc" ], @@ -142,7 +142,7 @@ "kind": "MCPRegistry", "plural": "mcpregistries", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpreg", "registry" @@ -157,7 +157,7 @@ "kind": "MCPRemoteProxy", "plural": "mcpremoteproxies", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "rp", "mcprp" @@ -204,7 +204,7 @@ "kind": "MCPServerEntry", "plural": "mcpserverentries", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpentry" ], @@ -231,7 +231,7 @@ "kind": "MCPServer", "plural": "mcpservers", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpserver", "mcpservers" @@ -278,7 +278,7 @@ "kind": "MCPTelemetryConfig", "plural": "mcptelemetryconfigs", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "mcpotel" ], @@ -311,7 +311,7 @@ "kind": "MCPToolConfig", "plural": "mcptoolconfigs", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "tc", "toolconfig" @@ -345,7 +345,7 @@ "kind": "VirtualMCPCompositeToolDefinition", "plural": "virtualmcpcompositetooldefinitions", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "vmcpctd", "compositetool" @@ -367,7 +367,7 @@ "kind": "VirtualMCPServer", "plural": "virtualmcpservers", "group": "toolhive.stacklok.dev", - "version": "v1alpha1", + "version": "v1beta1", "shortNames": [ "vmcp", "virtualmcp" diff --git a/static/api-specs/crds/mcpexternalauthconfigs.example.yaml b/static/api-specs/crds/mcpexternalauthconfigs.example.yaml index 77320fba..aef10ac3 100644 --- a/static/api-specs/crds/mcpexternalauthconfigs.example.yaml +++ b/static/api-specs/crds/mcpexternalauthconfigs.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPExternalAuthConfig metadata: name: my-mcpexternalauthconfig diff --git a/static/api-specs/crds/mcpexternalauthconfigs.schema.json b/static/api-specs/crds/mcpexternalauthconfigs.schema.json index b929346d..31241e25 100644 --- a/static/api-specs/crds/mcpexternalauthconfigs.schema.json +++ b/static/api-specs/crds/mcpexternalauthconfigs.schema.json @@ -4,7 +4,7 @@ "description": "MCPExternalAuthConfig is the Schema for the mcpexternalauthconfigs API.\nMCPExternalAuthConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPExternalAuthConfig", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpexternalauthconfigs", "x-kubernetes-short-names": [ "extauth", diff --git a/static/api-specs/crds/mcpgroups.example.yaml b/static/api-specs/crds/mcpgroups.example.yaml index f0ce93e3..1c6dd73a 100644 --- a/static/api-specs/crds/mcpgroups.example.yaml +++ b/static/api-specs/crds/mcpgroups.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPGroup metadata: name: my-mcpgroup diff --git a/static/api-specs/crds/mcpgroups.schema.json b/static/api-specs/crds/mcpgroups.schema.json index f1f5a3c4..e827b3a1 100644 --- a/static/api-specs/crds/mcpgroups.schema.json +++ b/static/api-specs/crds/mcpgroups.schema.json @@ -4,7 +4,7 @@ "description": "MCPGroup is the Schema for the mcpgroups API", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPGroup", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpgroups", "x-kubernetes-short-names": [ "mcpg", diff --git a/static/api-specs/crds/mcpoidcconfigs.example.yaml b/static/api-specs/crds/mcpoidcconfigs.example.yaml index d59addeb..6e394fe1 100644 --- a/static/api-specs/crds/mcpoidcconfigs.example.yaml +++ b/static/api-specs/crds/mcpoidcconfigs.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPOIDCConfig metadata: name: my-mcpoidcconfig diff --git a/static/api-specs/crds/mcpoidcconfigs.schema.json b/static/api-specs/crds/mcpoidcconfigs.schema.json index 669d97df..158e894b 100644 --- a/static/api-specs/crds/mcpoidcconfigs.schema.json +++ b/static/api-specs/crds/mcpoidcconfigs.schema.json @@ -4,7 +4,7 @@ "description": "MCPOIDCConfig is the Schema for the mcpoidcconfigs API.\nMCPOIDCConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPOIDCConfig", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpoidcconfigs", "x-kubernetes-short-names": [ "mcpoidc" diff --git a/static/api-specs/crds/mcpregistries.example.yaml b/static/api-specs/crds/mcpregistries.example.yaml index 5e941635..4ae55608 100644 --- a/static/api-specs/crds/mcpregistries.example.yaml +++ b/static/api-specs/crds/mcpregistries.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPRegistry metadata: name: my-mcpregistry diff --git a/static/api-specs/crds/mcpregistries.schema.json b/static/api-specs/crds/mcpregistries.schema.json index ff151c13..89915cf4 100644 --- a/static/api-specs/crds/mcpregistries.schema.json +++ b/static/api-specs/crds/mcpregistries.schema.json @@ -4,7 +4,7 @@ "description": "MCPRegistry is the Schema for the mcpregistries API", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPRegistry", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpregistries", "x-kubernetes-short-names": [ "mcpreg", diff --git a/static/api-specs/crds/mcpremoteproxies.example.yaml b/static/api-specs/crds/mcpremoteproxies.example.yaml index f4ec6f8a..4e06a587 100644 --- a/static/api-specs/crds/mcpremoteproxies.example.yaml +++ b/static/api-specs/crds/mcpremoteproxies.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPRemoteProxy metadata: name: my-mcpremoteproxy diff --git a/static/api-specs/crds/mcpremoteproxies.schema.json b/static/api-specs/crds/mcpremoteproxies.schema.json index 20424e68..a5587643 100644 --- a/static/api-specs/crds/mcpremoteproxies.schema.json +++ b/static/api-specs/crds/mcpremoteproxies.schema.json @@ -4,7 +4,7 @@ "description": "MCPRemoteProxy is the Schema for the mcpremoteproxies API\nIt enables proxying remote MCP servers with authentication, authorization, audit logging, and tool filtering", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPRemoteProxy", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpremoteproxies", "x-kubernetes-short-names": [ "rp", diff --git a/static/api-specs/crds/mcpserverentries.example.yaml b/static/api-specs/crds/mcpserverentries.example.yaml index 2a7384f3..cdc8eb58 100644 --- a/static/api-specs/crds/mcpserverentries.example.yaml +++ b/static/api-specs/crds/mcpserverentries.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPServerEntry metadata: name: my-mcpserverentry diff --git a/static/api-specs/crds/mcpserverentries.schema.json b/static/api-specs/crds/mcpserverentries.schema.json index 8acc33ba..ed37c587 100644 --- a/static/api-specs/crds/mcpserverentries.schema.json +++ b/static/api-specs/crds/mcpserverentries.schema.json @@ -4,7 +4,7 @@ "description": "MCPServerEntry is the Schema for the mcpserverentries API.\nIt declares a remote MCP server endpoint for vMCP discovery and routing\nwithout deploying any infrastructure.", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPServerEntry", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpserverentries", "x-kubernetes-short-names": [ "mcpentry" diff --git a/static/api-specs/crds/mcpservers.example.yaml b/static/api-specs/crds/mcpservers.example.yaml index b02c28a8..470f1903 100644 --- a/static/api-specs/crds/mcpservers.example.yaml +++ b/static/api-specs/crds/mcpservers.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPServer metadata: name: my-mcpserver diff --git a/static/api-specs/crds/mcpservers.schema.json b/static/api-specs/crds/mcpservers.schema.json index 148bceb6..395fb731 100644 --- a/static/api-specs/crds/mcpservers.schema.json +++ b/static/api-specs/crds/mcpservers.schema.json @@ -4,7 +4,7 @@ "description": "MCPServer is the Schema for the mcpservers API", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPServer", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcpservers", "x-kubernetes-short-names": [ "mcpserver", diff --git a/static/api-specs/crds/mcptelemetryconfigs.example.yaml b/static/api-specs/crds/mcptelemetryconfigs.example.yaml index e630b142..d1a26f4a 100644 --- a/static/api-specs/crds/mcptelemetryconfigs.example.yaml +++ b/static/api-specs/crds/mcptelemetryconfigs.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPTelemetryConfig metadata: name: my-mcptelemetryconfig diff --git a/static/api-specs/crds/mcptelemetryconfigs.schema.json b/static/api-specs/crds/mcptelemetryconfigs.schema.json index 4aecc789..4f299679 100644 --- a/static/api-specs/crds/mcptelemetryconfigs.schema.json +++ b/static/api-specs/crds/mcptelemetryconfigs.schema.json @@ -4,7 +4,7 @@ "description": "MCPTelemetryConfig is the Schema for the mcptelemetryconfigs API.\nMCPTelemetryConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPTelemetryConfig", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcptelemetryconfigs", "x-kubernetes-short-names": [ "mcpotel" diff --git a/static/api-specs/crds/mcptoolconfigs.example.yaml b/static/api-specs/crds/mcptoolconfigs.example.yaml index 9b8de9ca..d911f3a6 100644 --- a/static/api-specs/crds/mcptoolconfigs.example.yaml +++ b/static/api-specs/crds/mcptoolconfigs.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPToolConfig metadata: name: my-mcptoolconfig diff --git a/static/api-specs/crds/mcptoolconfigs.schema.json b/static/api-specs/crds/mcptoolconfigs.schema.json index fbe41e6b..9efb42fc 100644 --- a/static/api-specs/crds/mcptoolconfigs.schema.json +++ b/static/api-specs/crds/mcptoolconfigs.schema.json @@ -4,7 +4,7 @@ "description": "MCPToolConfig is the Schema for the mcptoolconfigs API.\nMCPToolConfig resources are namespace-scoped and can only be referenced by\nMCPServer resources within the same namespace. Cross-namespace references\nare not supported for security and isolation reasons.", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "MCPToolConfig", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "mcptoolconfigs", "x-kubernetes-short-names": [ "tc", diff --git a/static/api-specs/crds/virtualmcpcompositetooldefinitions.example.yaml b/static/api-specs/crds/virtualmcpcompositetooldefinitions.example.yaml index e588ae95..cfae92d1 100644 --- a/static/api-specs/crds/virtualmcpcompositetooldefinitions.example.yaml +++ b/static/api-specs/crds/virtualmcpcompositetooldefinitions.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: VirtualMCPCompositeToolDefinition metadata: name: my-virtualmcpcompositetooldefinition diff --git a/static/api-specs/crds/virtualmcpcompositetooldefinitions.schema.json b/static/api-specs/crds/virtualmcpcompositetooldefinitions.schema.json index c878929c..c2320024 100644 --- a/static/api-specs/crds/virtualmcpcompositetooldefinitions.schema.json +++ b/static/api-specs/crds/virtualmcpcompositetooldefinitions.schema.json @@ -4,7 +4,7 @@ "description": "VirtualMCPCompositeToolDefinition is the Schema for the virtualmcpcompositetooldefinitions API\nVirtualMCPCompositeToolDefinition defines reusable composite workflows that can be referenced\nby multiple VirtualMCPServer instances", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "VirtualMCPCompositeToolDefinition", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "virtualmcpcompositetooldefinitions", "x-kubernetes-short-names": [ "vmcpctd", diff --git a/static/api-specs/crds/virtualmcpservers.example.yaml b/static/api-specs/crds/virtualmcpservers.example.yaml index 664a53df..20167256 100644 --- a/static/api-specs/crds/virtualmcpservers.example.yaml +++ b/static/api-specs/crds/virtualmcpservers.example.yaml @@ -1,4 +1,4 @@ -apiVersion: toolhive.stacklok.dev/v1alpha1 +apiVersion: toolhive.stacklok.dev/v1beta1 kind: VirtualMCPServer metadata: name: my-virtualmcpserver diff --git a/static/api-specs/crds/virtualmcpservers.schema.json b/static/api-specs/crds/virtualmcpservers.schema.json index 03f5db70..d380fbd3 100644 --- a/static/api-specs/crds/virtualmcpservers.schema.json +++ b/static/api-specs/crds/virtualmcpservers.schema.json @@ -4,7 +4,7 @@ "description": "VirtualMCPServer is the Schema for the virtualmcpservers API\nVirtualMCPServer aggregates multiple backend MCPServers into a unified endpoint", "x-kubernetes-group": "toolhive.stacklok.dev", "x-kubernetes-kind": "VirtualMCPServer", - "x-kubernetes-version": "v1alpha1", + "x-kubernetes-version": "v1beta1", "x-kubernetes-plural": "virtualmcpservers", "x-kubernetes-short-names": [ "vmcp", diff --git a/static/api-specs/toolhive-api.yaml b/static/api-specs/toolhive-api.yaml index 5fc9d245..e6c07a3e 100644 --- a/static/api-specs/toolhive-api.yaml +++ b/static/api-specs/toolhive-api.yaml @@ -31,7 +31,7 @@ components: description: Version is the schema version of the registry type: string type: object - github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitBucket: + github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitBucket: description: |- PerUser token bucket configuration for this tool. +optional @@ -47,15 +47,15 @@ components: refillPeriod: $ref: '#/components/schemas/v1.Duration' type: object - github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitConfig: + github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitConfig: description: |- RateLimitConfig contains the CRD rate limiting configuration. When set, rate limiting middleware is added to the proxy middleware chain. properties: perUser: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitBucket' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitBucket' shared: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitBucket' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitBucket' tools: description: |- Tools defines per-tool rate limit overrides. @@ -65,11 +65,11 @@ components: +listMapKey=name +optional items: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.ToolRateLimitConfig' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.ToolRateLimitConfig' type: array uniqueItems: false type: object - github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.ToolRateLimitConfig: + github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.ToolRateLimitConfig: properties: name: description: |- @@ -78,9 +78,9 @@ components: +kubebuilder:validation:MinLength=1 type: string perUser: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitBucket' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitBucket' shared: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitBucket' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitBucket' type: object github_com_stacklok_toolhive_pkg_audit.Config: description: |- @@ -1213,7 +1213,7 @@ components: type: array uniqueItems: false rate_limit_config: - $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1alpha1.RateLimitConfig' + $ref: '#/components/schemas/github_com_stacklok_toolhive_cmd_thv-operator_api_v1beta1.RateLimitConfig' rate_limit_namespace: description: RateLimitNamespace is the Kubernetes namespace for Redis key derivation. diff --git a/static/api-specs/toolhive-registry-api.yaml b/static/api-specs/toolhive-registry-api.yaml new file mode 100644 index 00000000..2ee43748 --- /dev/null +++ b/static/api-specs/toolhive-registry-api.yaml @@ -0,0 +1,1971 @@ +components: + schemas: + github_com_stacklok_toolhive-registry-server_internal_config.FilterConfig: + description: Filtering rules + properties: + names: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_config.NameFilterConfig' + tags: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_config.TagFilterConfig' + type: object + github_com_stacklok_toolhive-registry-server_internal_config.NameFilterConfig: + properties: + exclude: + items: + type: string + type: array + uniqueItems: false + include: + items: + type: string + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_config.SourceType: + description: git, api, file, managed, kubernetes + enum: + - git + - api + - file + - managed + - kubernetes + type: string + x-enum-varnames: + - SourceTypeGit + - SourceTypeAPI + - SourceTypeFile + - SourceTypeManaged + - SourceTypeKubernetes + github_com_stacklok_toolhive-registry-server_internal_config.TagFilterConfig: + properties: + exclude: + items: + type: string + type: array + uniqueItems: false + include: + items: + type: string + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_service.CreationType: + description: API or CONFIG + enum: + - API + - CONFIG + type: string + x-enum-varnames: + - CreationTypeAPI + - CreationTypeCONFIG + github_com_stacklok_toolhive-registry-server_internal_service.EntryVersionInfo: + properties: + createdAt: + type: string + description: + type: string + title: + type: string + updatedAt: + type: string + version: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.RegistryEntriesResponse: + properties: + entries: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryEntryInfo' + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_service.RegistryEntryInfo: + properties: + entryType: + type: string + name: + type: string + sourceName: + type: string + version: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.RegistryInfo: + properties: + claims: + additionalProperties: {} + type: object + createdAt: + type: string + creationType: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.CreationType' + name: + type: string + sources: + items: + type: string + type: array + uniqueItems: false + updatedAt: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.Skill: + properties: + _meta: + additionalProperties: {} + type: object + allowedTools: + items: + type: string + type: array + uniqueItems: false + compatibility: + type: string + createdAt: + type: string + description: + type: string + icons: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SkillIcon' + type: array + uniqueItems: false + id: + type: string + isLatest: + type: boolean + license: + type: string + metadata: + additionalProperties: {} + type: object + name: + type: string + namespace: + type: string + packages: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SkillPackage' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SkillRepository' + status: + type: string + title: + type: string + updatedAt: + type: string + version: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SkillIcon: + properties: + label: + type: string + size: + type: string + src: + type: string + type: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SkillPackage: + properties: + commit: + type: string + digest: + type: string + identifier: + type: string + mediaType: + type: string + ref: + type: string + registryType: + type: string + subfolder: + type: string + url: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SkillRepository: + properties: + type: + type: string + url: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SourceEntriesResponse: + properties: + entries: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceEntryInfo' + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SourceEntryInfo: + properties: + claims: + additionalProperties: {} + type: object + entryType: + type: string + name: + type: string + versions: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.EntryVersionInfo' + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SourceInfo: + properties: + claims: + additionalProperties: {} + description: Authorization claims + type: object + createdAt: + type: string + creationType: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.CreationType' + filterConfig: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_config.FilterConfig' + name: + type: string + sourceConfig: + description: Type-specific source configuration + sourceType: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_config.SourceType' + syncSchedule: + description: Sync interval string + type: string + syncStatus: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceSyncStatus' + type: + description: MANAGED, FILE, REMOTE, KUBERNETES + type: string + updatedAt: + type: string + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SourceListResponse: + properties: + sources: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceInfo' + type: array + uniqueItems: false + type: object + github_com_stacklok_toolhive-registry-server_internal_service.SourceSyncStatus: + properties: + attemptCount: + description: Number of sync attempts + type: integer + lastAttempt: + description: Last sync attempt + type: string + lastSyncTime: + description: Last successful sync + type: string + message: + description: Status or error message + type: string + phase: + description: complete, syncing, failed + type: string + serverCount: + description: Number of servers in registry + type: integer + skillCount: + description: Number of skills in registry + type: integer + type: object + internal_api_v1.meResponse: + properties: + roles: + items: + type: string + type: array + uniqueItems: false + subject: + type: string + type: object + internal_api_v1.publishEntryRequest: + properties: + claims: + additionalProperties: {} + type: object + server: + $ref: '#/components/schemas/v0.ServerJSON' + skill: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.Skill' + type: object + internal_api_v1.registryListResponse: + properties: + registries: + items: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryInfo' + type: array + uniqueItems: false + type: object + internal_api_v1.updateEntryClaimsRequest: + properties: + claims: + additionalProperties: {} + type: object + type: object + internal_api_x_skills.SkillListMetadata: + properties: + count: + type: integer + nextCursor: + type: string + type: object + internal_api_x_skills.SkillListResponse: + properties: + metadata: + $ref: '#/components/schemas/internal_api_x_skills.SkillListMetadata' + skills: + items: + $ref: '#/components/schemas/registry.Skill' + type: array + uniqueItems: false + type: object + model.Argument: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRepeated: + type: boolean + isRequired: + type: boolean + isSecret: + type: boolean + name: + example: --port + type: string + placeholder: + type: string + type: + $ref: '#/components/schemas/model.ArgumentType' + value: + type: string + valueHint: + example: file_path + type: string + variables: + additionalProperties: + $ref: '#/components/schemas/model.Input' + type: object + type: object + model.ArgumentType: + enum: + - positional + - named + example: positional + type: string + x-enum-varnames: + - ArgumentTypePositional + - ArgumentTypeNamed + model.Format: + enum: + - string + - number + - boolean + - filepath + type: string + x-enum-varnames: + - FormatString + - FormatNumber + - FormatBoolean + - FormatFilePath + model.Icon: + properties: + mimeType: + example: image/png + type: string + sizes: + items: + type: string + type: array + uniqueItems: false + src: + example: https://example.com/icon.png + format: uri + maxLength: 255 + type: string + theme: + type: string + type: object + model.Input: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRequired: + type: boolean + isSecret: + type: boolean + placeholder: + type: string + value: + type: string + type: object + model.KeyValueInput: + properties: + choices: + items: + type: string + type: array + uniqueItems: false + default: + type: string + description: + type: string + format: + $ref: '#/components/schemas/model.Format' + isRequired: + type: boolean + isSecret: + type: boolean + name: + example: SOME_VARIABLE + type: string + placeholder: + type: string + value: + type: string + variables: + additionalProperties: + $ref: '#/components/schemas/model.Input' + type: object + type: object + model.Package: + properties: + environmentVariables: + description: EnvironmentVariables are set when running the package + items: + $ref: '#/components/schemas/model.KeyValueInput' + type: array + uniqueItems: false + fileSha256: + description: FileSHA256 is the SHA-256 hash for integrity verification (required + for mcpb, optional for others) + example: fe333e598595000ae021bd27117db32ec69af6987f507ba7a63c90638ff633ce + pattern: ^[a-f0-9]{64}$ + type: string + identifier: + description: |- + Identifier is the package identifier: + - For NPM/PyPI/NuGet: package name or ID + - For OCI: full image reference (e.g., "ghcr.io/owner/repo:v1.0.0") + - For MCPB: direct download URL + example: '@modelcontextprotocol/server-brave-search' + minLength: 1 + type: string + packageArguments: + description: PackageArguments are passed to the package's binary + items: + $ref: '#/components/schemas/model.Argument' + type: array + uniqueItems: false + registryBaseUrl: + description: RegistryBaseURL is the base URL of the package registry (used + by npm, pypi, nuget; not used by oci, mcpb) + example: https://registry.npmjs.org + format: uri + type: string + registryType: + description: RegistryType indicates how to download packages (e.g., "npm", + "pypi", "oci", "nuget", "mcpb") + example: npm + minLength: 1 + type: string + runtimeArguments: + description: RuntimeArguments are passed to the package's runtime command + (e.g., docker, npx) + items: + $ref: '#/components/schemas/model.Argument' + type: array + uniqueItems: false + runtimeHint: + description: RunTimeHint suggests the appropriate runtime for the package + example: npx + type: string + transport: + $ref: '#/components/schemas/model.Transport' + version: + description: Version is the package version (required for npm, pypi, nuget; + optional for mcpb; not used by oci where version is in the identifier) + example: 1.0.2 + minLength: 1 + type: string + type: object + model.Repository: + properties: + id: + example: b94b5f7e-c7c6-d760-2c78-a5e9b8a5b8c9 + type: string + source: + example: github + type: string + subfolder: + example: src/everything + type: string + url: + example: https://github.com/modelcontextprotocol/servers + format: uri + type: string + type: object + model.Status: + enum: + - active + - deprecated + - deleted + type: string + x-enum-varnames: + - StatusActive + - StatusDeprecated + - StatusDeleted + model.Transport: + description: Transport is required and specifies the transport protocol configuration + properties: + headers: + items: + $ref: '#/components/schemas/model.KeyValueInput' + type: array + uniqueItems: false + type: + example: stdio + type: string + url: + example: https://api.example.com/mcp + type: string + variables: + additionalProperties: + $ref: '#/components/schemas/model.Input' + type: object + type: object + registry.Skill: + properties: + _meta: + additionalProperties: {} + description: Meta is an opaque payload with extended meta data details of + the skill. + type: object + allowedTools: + description: |- + AllowedTools is the list of tools that the skill is compatible with. + This is experimental. + items: + type: string + type: array + uniqueItems: false + compatibility: + description: Compatibility is the environment requirements of the skill. + type: string + description: + description: Description is the description of the skill. + type: string + icons: + description: Icons is the list of icons for the skill. + items: + $ref: '#/components/schemas/registry.SkillIcon' + type: array + uniqueItems: false + license: + description: License is the SPDX license identifier of the skill. + type: string + metadata: + additionalProperties: {} + description: |- + Metadata is the official metadata of the skill as reported in the + SKILL.md file. + type: object + name: + description: |- + Name is the name of the skill. + The format is that of identifiers, e.g. "my-skill". + type: string + namespace: + description: |- + Namespace is the namespace of the skill. + The format is reverse-DNS, e.g. "io.github.user". + type: string + packages: + description: Packages is the list of packages for the skill. + items: + $ref: '#/components/schemas/registry.SkillPackage' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/registry.SkillRepository' + status: + description: |- + Status is the status of the skill. + Can be one of "active", "deprecated", or "archived". + type: string + title: + description: |- + Title is the title of the skill. + This is for human consumption, not an identifier. + type: string + version: + description: |- + Version is the version of the skill. + Any non-empty string is valid, but ideally it should be either a + semantic version or a commit hash. + type: string + type: object + registry.SkillIcon: + properties: + label: + description: Label is the label of the icon. + type: string + size: + description: Size is the size of the icon. + type: string + src: + description: Src is the source of the icon. + type: string + type: + description: Type is the type of the icon. + type: string + type: object + registry.SkillPackage: + properties: + commit: + description: Commit is the commit of the package. + type: string + digest: + description: Digest is the digest of the package. + type: string + identifier: + description: Identifier is the OCI identifier of the package. + type: string + mediaType: + description: MediaType is the media type of the package. + type: string + ref: + description: Ref is the reference of the package. + type: string + registryType: + description: |- + RegistryType is the type of registry the package is from. + Can be "oci" or "git". + type: string + subfolder: + description: Subfolder is the subfolder of the package. + type: string + url: + description: URL is the URL of the package. + type: string + type: object + registry.SkillRepository: + description: Repository is the source repository of the skill. + properties: + type: + description: Type is the type of the repository. + type: string + url: + description: URL is the URL of the repository. + type: string + type: object + v0.Metadata: + properties: + count: + type: integer + nextCursor: + type: string + type: object + v0.RegistryExtensions: + properties: + isLatest: + type: boolean + publishedAt: + format: date-time + type: string + status: + $ref: '#/components/schemas/model.Status' + statusChangedAt: + format: date-time + type: string + statusMessage: + type: string + updatedAt: + format: date-time + type: string + type: object + v0.ResponseMeta: + properties: + io.modelcontextprotocol.registry/official: + $ref: '#/components/schemas/v0.RegistryExtensions' + type: object + v0.ServerJSON: + properties: + $schema: + example: https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json + format: uri + minLength: 1 + type: string + _meta: + $ref: '#/components/schemas/v0.ServerMeta' + description: + example: MCP server providing weather data and forecasts via OpenWeatherMap + API + maxLength: 100 + minLength: 1 + type: string + icons: + items: + $ref: '#/components/schemas/model.Icon' + type: array + uniqueItems: false + name: + example: io.github.user/weather + maxLength: 200 + minLength: 3 + pattern: ^[a-zA-Z0-9.-]+/[a-zA-Z0-9._-]+$ + type: string + packages: + items: + $ref: '#/components/schemas/model.Package' + type: array + uniqueItems: false + remotes: + items: + $ref: '#/components/schemas/model.Transport' + type: array + uniqueItems: false + repository: + $ref: '#/components/schemas/model.Repository' + title: + example: Weather API + maxLength: 100 + minLength: 1 + type: string + version: + example: 1.0.2 + type: string + websiteUrl: + example: https://modelcontextprotocol.io/examples + format: uri + type: string + type: object + v0.ServerListResponse: + properties: + metadata: + $ref: '#/components/schemas/v0.Metadata' + servers: + items: + $ref: '#/components/schemas/v0.ServerResponse' + type: array + uniqueItems: false + type: object + v0.ServerMeta: + properties: + io.modelcontextprotocol.registry/publisher-provided: + additionalProperties: {} + type: object + type: object + v0.ServerResponse: + properties: + _meta: + $ref: '#/components/schemas/v0.ResponseMeta' + server: + $ref: '#/components/schemas/v0.ServerJSON' + type: object + securitySchemes: + BearerAuth: + description: 'OAuth 2.0 Bearer token authentication. Format: "Bearer {token}"' + in: header + name: Authorization + type: apiKey +externalDocs: + description: "" + url: "" +info: + contact: + url: https://github.com/stacklok/toolhive + description: |- + API for accessing MCP server registry data and deployed server information + This API provides endpoints to query the MCP (Model Context Protocol) server registry, + get information about available servers, and check the status of deployed servers. + + Authentication is required by default. Use Bearer token authentication with a valid + OAuth/OIDC access token. The /.well-known/oauth-protected-resource endpoint provides + OAuth discovery metadata (RFC 9728). + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + title: ToolHive Registry API + version: "0.1" +openapi: 3.1.0 +paths: + /openapi.json: + get: + description: Get the OpenAPI 3.1.0 specification for this API + responses: + "200": + content: + application/json: + schema: + type: object + description: OpenAPI 3.1.0 specification + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal Server Error + summary: OpenAPI specification + tags: + - system + /registry/{registryName}/v0.1/servers: + get: + description: Get a list of available servers from a specific registry + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Pagination cursor for retrieving next set of results + in: query + name: cursor + schema: + type: string + - description: Maximum number of items to return + in: query + name: limit + schema: + type: integer + - description: Search servers by name (substring match) + in: query + name: search + schema: + type: string + - description: Filter by version ('latest' for latest version, or an exact version + like '1.2.3') + in: query + name: version + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerListResponse' + description: OK + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "401": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Unauthorized + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Registry not found + security: + - BearerAuth: [] + summary: List servers in specific registry + tags: + - registry + /registry/{registryName}/v0.1/servers/{serverName}/versions: + get: + description: Returns all available versions for a specific MCP server from a + specific registry + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: URL-encoded server name (e.g., \ + in: path + name: serverName + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerListResponse' + description: A list of all versions for the server + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "401": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Unauthorized + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Server not found + security: + - BearerAuth: [] + summary: List all versions of an MCP server in specific registry + tags: + - registry + /registry/{registryName}/v0.1/servers/{serverName}/versions/{version}: + get: + description: |- + Returns detailed information about a specific version of an MCP server from a specific registry. + Use the special version `latest` to get the latest version. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: URL-encoded server name (e.g., \ + in: path + name: serverName + required: true + schema: + type: string + - description: URL-encoded version to retrieve (e.g., \ + in: path + name: version + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/v0.ServerResponse' + description: Detailed server information + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "401": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Unauthorized + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Server or version not found + security: + - BearerAuth: [] + summary: Get specific MCP server version in specific registry + tags: + - registry + /registry/{registryName}/v0.1/x/dev.toolhive/skills: + get: + description: List skills in a registry (paginated, latest versions). + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Filter by name/description substring + in: query + name: search + schema: + type: string + - description: Filter by status (comma-separated, e.g. active,deprecated) + in: query + name: status + schema: + type: string + - description: Max results (default 50, max 100) + in: query + name: limit + schema: + type: integer + - description: Pagination cursor + in: query + name: cursor + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_x_skills.SkillListResponse' + description: List of skills + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: List skills in registry + tags: + - skills + /registry/{registryName}/v0.1/x/dev.toolhive/skills/{namespace}/{name}: + get: + description: Get the latest version of a skill by namespace and name. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Skill namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Skill name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/registry.Skill' + description: Skill details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Skill not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: Get latest skill version + tags: + - skills + /registry/{registryName}/v0.1/x/dev.toolhive/skills/{namespace}/{name}/versions: + get: + description: List all versions of a skill. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Skill namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Skill name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_x_skills.SkillListResponse' + description: List of skill versions + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Skill not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: List skill versions + tags: + - skills + /registry/{registryName}/v0.1/x/dev.toolhive/skills/{namespace}/{name}/versions/{version}: + get: + description: Get a specific version of a skill. + parameters: + - description: Registry name + in: path + name: registryName + required: true + schema: + type: string + - description: Skill namespace (reverse-DNS) + in: path + name: namespace + required: true + schema: + type: string + - description: Skill name + in: path + name: name + required: true + schema: + type: string + - description: Skill version + in: path + name: version + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/registry.Skill' + description: Skill details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Skill or version not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + security: + - BearerAuth: [] + summary: Get specific skill version + tags: + - skills + /v1/entries: + post: + description: Publish a new server or skill entry. Exactly one of 'server' or + 'skill' must be provided. + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + - $ref: '#/components/schemas/internal_api_v1.publishEntryRequest' + description: Entry to publish (server or skill) + summary: request + description: Entry to publish (server or skill) + required: true + responses: + "201": + content: + application/json: + schema: + type: object + description: Published entry (server or skill) + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "409": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Conflict + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Publish entry + tags: + - v1 + /v1/entries/{type}/{name}/claims: + put: + description: Update claims for a published entry name + parameters: + - description: Entry Type (server or skill) + in: path + name: type + required: true + schema: + type: string + - description: Entry Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + oneOf: + - type: object + - $ref: '#/components/schemas/internal_api_v1.updateEntryClaimsRequest' + description: Claims to set + summary: request + description: Claims to set + required: true + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Forbidden + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + "503": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: No managed source available + summary: Update entry claims + tags: + - v1 + /v1/entries/{type}/{name}/versions/{version}: + delete: + description: Delete a published entry version + parameters: + - description: Entry Type (server or skill) + in: path + name: type + required: true + schema: + type: string + - description: Entry Name + in: path + name: name + required: true + schema: + type: string + - description: Version + in: path + name: version + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "204": + description: No Content + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Delete published entry + tags: + - v1 + /v1/me: + get: + description: Returns the authenticated caller's identity and roles + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_v1.meResponse' + description: Caller identity and roles + "401": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Unauthorized + summary: Get current user info + tags: + - v1 + /v1/registries: + get: + description: List all registries + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/internal_api_v1.registryListResponse' + description: Registries list + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: List registries + tags: + - v1 + /v1/registries/{name}: + delete: + description: Delete a registry by name + parameters: + - description: Registry Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "204": + description: Registry deleted + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Cannot modify config-created registry + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Registry not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Delete registry + tags: + - v1 + get: + description: Get a registry by name + parameters: + - description: Registry Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryInfo' + description: Registry details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Registry not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Get registry + tags: + - v1 + put: + description: Create a new registry or update an existing one + parameters: + - description: Registry Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryInfo' + description: Registry updated + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryInfo' + description: Registry created + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Cannot modify config-created registry + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Create or update registry + tags: + - v1 + /v1/registries/{name}/entries: + get: + description: List all entries for a registry + parameters: + - description: Registry Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.RegistryEntriesResponse' + description: Registry entries + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Registry not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: List registry entries + tags: + - v1 + /v1/sources: + get: + description: List all sources + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceListResponse' + description: Sources list + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: List sources + tags: + - v1 + /v1/sources/{name}: + delete: + description: Delete a source by name + parameters: + - description: Source Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "204": + description: Source deleted + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Cannot modify config-created source + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Source not found + "409": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Source in use + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Delete source + tags: + - v1 + get: + description: Get a source by name + parameters: + - description: Source Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceInfo' + description: Source details + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Source not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Get source + tags: + - v1 + put: + description: Create a new source or update an existing one + parameters: + - description: Source Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceInfo' + description: Source updated + "201": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceInfo' + description: Source created + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "403": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Cannot modify config-created source + "409": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Managed source limit reached + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: Create or update source + tags: + - v1 + /v1/sources/{name}/entries: + get: + description: List all entries for a source + parameters: + - description: Source Name + in: path + name: name + required: true + schema: + type: string + requestBody: + content: + application/json: + schema: + type: object + responses: + "200": + content: + application/json: + schema: + $ref: '#/components/schemas/github_com_stacklok_toolhive-registry-server_internal_service.SourceEntriesResponse' + description: Source entries + "400": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Bad request + "404": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Source not found + "500": + content: + application/json: + schema: + additionalProperties: + type: string + type: object + description: Internal server error + summary: List source entries + tags: + - v1