feat: mcp oauth 2.1 flow for HTTP/SSE servers #2105
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: PR Validation | |
| on: | |
| pull_request: | |
| types: [opened, edited, synchronize, reopened] | |
| branches: [main, develop, dev] | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| pr-title-check: | |
| name: Validate PR Title | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| pull-requests: read | |
| steps: | |
| - name: Check PR title follows conventional commits | |
| uses: amannn/action-semantic-pull-request@e32d7e603df1aa1ba07e981f2a23455dee596825 # v5 | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| types: | | |
| feat | |
| fix | |
| docs | |
| style | |
| refactor | |
| perf | |
| test | |
| build | |
| ci | |
| chore | |
| revert | |
| requireScope: false | |
| subjectPattern: ^(?![A-Z]).+$ | |
| subjectPatternError: | | |
| The subject "{subject}" must not start with an uppercase letter. | |
| Use lowercase like: "feat: add new feature" | |
| pr-body-check: | |
| name: Validate PR Description | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Validate PR description against template | |
| id: validate | |
| env: | |
| # Pass the body via env, never interpolated into the shell, to avoid | |
| # command injection from attacker-controlled PR descriptions. | |
| PR_BODY: ${{ github.event.pull_request.body }} | |
| PR_BODY_COMMENT_FILE: ${{ runner.temp }}/pr-body-comment.md | |
| run: bun run scripts/validate-pr-body.ts | |
| - name: Post or update failure comment | |
| # Skip on forked PRs: GITHUB_TOKEN is read-only there, so the comment | |
| # API calls would 403. Validation itself still gates forked PRs. | |
| if: failure() && steps.validate.outcome == 'failure' && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const fs = require('node:fs'); | |
| const marker = '<!-- pr-body-validation -->'; | |
| const path = `${process.env.RUNNER_TEMP}/pr-body-comment.md`; | |
| let body; | |
| try { | |
| body = fs.readFileSync(path, 'utf8'); | |
| } catch { | |
| body = `${marker}\n## ❌ PR description does not satisfy the template\n\nPlease fill in every required section of the PR template.`; | |
| } | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 }); | |
| const existing = comments.find((c) => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } else { | |
| await github.rest.issues.createComment({ owner, repo, issue_number, body }); | |
| } | |
| - name: Resolve failure comment on success | |
| # Skip on forked PRs (read-only GITHUB_TOKEN) to avoid a 403 turning a | |
| # passing validation into a failed job. | |
| if: success() && github.event.pull_request.head.repo.full_name == github.repository | |
| uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 | |
| with: | |
| script: | | |
| const marker = '<!-- pr-body-validation -->'; | |
| const { owner, repo } = context.repo; | |
| const issue_number = context.issue.number; | |
| const { data: comments } = await github.rest.issues.listComments({ owner, repo, issue_number, per_page: 100 }); | |
| const existing = comments.find((c) => c.body && c.body.includes(marker)); | |
| if (existing) { | |
| const body = `${marker}\n## ✅ PR description satisfies the template\n\nThanks for filling it in.`; | |
| await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body }); | |
| } | |
| validate: | |
| name: Lint, Typecheck & Test | |
| # Code jobs don't need to re-run when only the PR description is edited. | |
| if: github.event.action != 'edited' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Biome (lint + format + imports) | |
| run: bun run ci | |
| - name: Typecheck | |
| run: bun run typecheck | |
| - name: Test | |
| run: bun run test | |
| acp-registry-bundle: | |
| name: ACP Registry Bundle Freshness | |
| if: github.event.action != 'edited' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Sync bundled ACP registry snapshot | |
| run: bun run sync:acp-icons | |
| - name: Format bundled ACP assets | |
| run: bunx biome format --write src/renderer/assets/agent-icons/acp/ | |
| - name: Verify bundled registry artifacts match CDN registry | |
| run: > | |
| git diff --exit-code -- | |
| src/renderer/assets/agent-icons/acp/ | |
| src-tauri/src/acp_registry_snapshot.rs | |
| rust-checks: | |
| name: Rust Checks | |
| if: github.event.action != 'edited' | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| libdbus-1-dev \ | |
| patchelf | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: clippy | |
| - name: Cache Rust | |
| uses: swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 | |
| with: | |
| workspaces: src-tauri | |
| cache-on-failure: true | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| # Build the Vite web bundle BEFORE the Rust checks so rust-embed embeds a | |
| # non-empty dist-web/ — the web::assets tests then exercise the embedded- | |
| # serving OK-path (200 + correct Content-Type + caching), not just the | |
| # empty-embed 404 branch. (A missing dist-web/ would still compile green | |
| # in debug via allow_missing, but the OK-path assertions need the bundle.) | |
| - name: Build web client (dist-web/) | |
| run: bun run build:web | |
| - name: Assert dist-web/index.html exists | |
| run: test -f dist-web/index.html && test -s dist-web/index.html | |
| - name: Cargo check | |
| working-directory: src-tauri | |
| run: cargo check --all-targets | |
| - name: Cargo test | |
| working-directory: src-tauri | |
| run: cargo test | |
| - name: Cargo clippy | |
| working-directory: src-tauri | |
| run: cargo clippy --all-targets -- -D warnings | |
| web-build: | |
| name: Web Client Build | |
| if: github.event.action != 'edited' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build web client (dist-web/) | |
| run: bun run build:web | |
| - name: Assert dist-web/index.html exists | |
| run: test -f dist-web/index.html && test -s dist-web/index.html | |
| standalone-server-build: | |
| name: Standalone Server Build | |
| if: github.event.action != 'edited' | |
| runs-on: ubuntu-22.04 | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Install Linux dependencies | |
| run: | | |
| sudo apt-get update | |
| sudo apt-get install -y \ | |
| libwebkit2gtk-4.1-dev \ | |
| libappindicator3-dev \ | |
| librsvg2-dev \ | |
| libdbus-1-dev \ | |
| patchelf | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| - name: Cache Rust | |
| uses: swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 | |
| with: | |
| workspaces: src-tauri | |
| cache-on-failure: true | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| # Build the Vite web bundle BEFORE the cargo build so rust-embed embeds a | |
| # non-empty `dist-web/` (build sequencing). A release build with a missing | |
| # bundle fails clearly via the `web_embed_missing` compile_error in | |
| # assets.rs; this dev build compiles green regardless (allow_missing), but | |
| # the embedded bundle is exercised with real content. | |
| - name: Build web client (dist-web/) | |
| run: bun run build:web | |
| - name: Assert dist-web/index.html exists | |
| run: test -f dist-web/index.html && test -s dist-web/index.html | |
| - name: Build termul-server (native host) | |
| working-directory: src-tauri | |
| run: cargo build --bin termul-server --features standalone-server | |
| - name: Clippy termul-server (feature on) | |
| working-directory: src-tauri | |
| run: cargo clippy --bin termul-server --features standalone-server -- -D warnings | |
| rust-windows-check: | |
| name: Rust Windows Smoke Check | |
| if: github.event.action != 'edited' | |
| runs-on: windows-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Rust | |
| uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable | |
| with: | |
| components: clippy | |
| - name: Cache Rust | |
| uses: swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 | |
| with: | |
| workspaces: src-tauri | |
| cache-on-failure: true | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build web client (dist-web/) | |
| run: bun run build:web | |
| - name: Assert dist-web/index.html exists | |
| shell: bash | |
| run: test -f dist-web/index.html && test -s dist-web/index.html | |
| - name: Cargo check | |
| working-directory: src-tauri | |
| run: cargo check --all-targets | |
| - name: Build termul-server (Windows host) | |
| working-directory: src-tauri | |
| run: cargo build --bin termul-server --features standalone-server | |
| build-check: | |
| name: Verify Tauri Frontend Build | |
| if: github.event.action != 'edited' | |
| needs: [validate, rust-checks, rust-windows-check, web-build, standalone-server-build] | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Bun | |
| uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2 | |
| with: | |
| bun-version: 1.3.x | |
| - name: Install Bun dependencies | |
| run: bun install --frozen-lockfile | |
| - name: Build Tauri frontend assets | |
| run: bun run build:frontend:tauri |