name: Conformance Tests

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:

concurrency:
  group: conformance-${{ github.ref }}
  cancel-in-progress: true

permissions:
  contents: read

env:
  # Pinned conformance harness package spec (passed verbatim to `npx --yes`).
  # Use a published version, e.g. @modelcontextprotocol/conformance@0.2.0-alpha.7.
  # Bump deliberately and reconcile both
  # .github/actions/conformance/expected-failures*.yml files in the same change.
  #
  # Temporarily pinned to the pkg.pr.new build of conformance main@4944b268
  # (0.2.0-alpha.8, which includes #372: fail checks whose prerequisite is
  # missing instead of skipping them) — alpha.8 is not published to npm yet.
  # Pinned by commit SHA so the tarball cannot move under us;
  # CONFORMANCE_PKG_SHA256 pins the bytes and the fetch-and-verify step below
  # downloads, checks the digest, and repoints CONFORMANCE_PKG at the
  # verified local copy. Repin to the next published @modelcontextprotocol/
  # conformance release (>=0.2.0-alpha.8) once it ships, then drop
  # CONFORMANCE_PKG_SHA256 and the fetch-and-verify steps.
  CONFORMANCE_PKG: "https://pkg.pr.new/@modelcontextprotocol/conformance@4944b268"
  CONFORMANCE_PKG_SHA256: "0f70c035782d319d72ab427653c5275db5c50429d59fae0241a645b33aeda1a7"

jobs:
  server-conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          version: 0.9.5
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: 24
      - name: Fetch and verify conformance harness
        # Only when CONFORMANCE_PKG is a URL: download, check the recorded
        # sha256, and re-point CONFORMANCE_PKG at the verified local tarball.
        # When CONFORMANCE_PKG is a registry spec, this step is a no-op (npm's
        # own integrity check applies).
        run: |
          case "$CONFORMANCE_PKG" in
            https://*)
              curl -fsSL "$CONFORMANCE_PKG" -o /tmp/conformance.tgz
              echo "$CONFORMANCE_PKG_SHA256  /tmp/conformance.tgz" | sha256sum -c -
              echo "CONFORMANCE_PKG=file:/tmp/conformance.tgz" >> "$GITHUB_ENV"
              ;;
          esac
      - run: uv sync --frozen --all-extras --package mcp-everything-server
      - name: Run server conformance (active suite)
        run: >-
          ./.github/actions/conformance/run-server.sh
          --suite active
          --expected-failures ./.github/actions/conformance/expected-failures.yml
          --output-dir conformance-results/server-active
      - name: Run server conformance (draft suite)
        run: >-
          ./.github/actions/conformance/run-server.sh
          --suite draft
          --expected-failures ./.github/actions/conformance/expected-failures.yml
          --output-dir conformance-results/server-draft
      - name: Run server conformance (2026-07-28 wire, all suite)
        run: >-
          ./.github/actions/conformance/run-server.sh
          --suite all
          --spec-version 2026-07-28
          --expected-failures ./.github/actions/conformance/expected-failures.2026-07-28.yml
          --output-dir conformance-results/server-2026-07-28
      - name: Run server conformance (all suite, extension scenarios)
        # A bare `--suite all` (no --spec-version) selects every scenario
        # shipped with the pinned harness — including the extension-tagged
        # tasks-* scenarios and pending-listed ones like server-sse-polling,
        # which no other leg reaches (extension scenarios never match a
        # --spec-version filter, and the pending list keeps them out of the
        # active suite). Running the full set keeps unimplemented surfaces
        # visible as baselined known failures in expected-failures.yml instead
        # of silent exclusions, and stays robust to scenarios moving between
        # harness suite lists across pin bumps. `--suite pending` would cover
        # the same union slightly faster; the full set is preferred for the
        # self-contained run and for parity with typescript-sdk's CI.
        run: >-
          ./.github/actions/conformance/run-server.sh
          --suite all
          --expected-failures ./.github/actions/conformance/expected-failures.yml
          --output-dir conformance-results/server-all
      - name: Upload conformance results
        # The log has only summary counts; per-check data is in checks.json.
        if: failure()
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: server-conformance-results
          path: conformance-results/
          if-no-files-found: ignore

  client-conformance:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false
      - uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
        with:
          enable-cache: true
          version: 0.9.5
      - uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
        with:
          node-version: 24
      - name: Fetch and verify conformance harness
        # Only when CONFORMANCE_PKG is a URL: download, check the recorded
        # sha256, and re-point CONFORMANCE_PKG at the verified local tarball.
        # When CONFORMANCE_PKG is a registry spec, this step is a no-op (npm's
        # own integrity check applies).
        run: |
          case "$CONFORMANCE_PKG" in
            https://*)
              curl -fsSL "$CONFORMANCE_PKG" -o /tmp/conformance.tgz
              echo "$CONFORMANCE_PKG_SHA256  /tmp/conformance.tgz" | sha256sum -c -
              echo "CONFORMANCE_PKG=file:/tmp/conformance.tgz" >> "$GITHUB_ENV"
              ;;
          esac
      # --compile-bytecode: without it, ~40 concurrently spawned interpreters
      # race to byte-compile site-packages during the timing-sensitive window.
      - run: uv sync --frozen --all-extras --package mcp --compile-bytecode
      - name: Pre-compile bytecode (editable sources)
        run: uv run --frozen python -m compileall -q src .github/actions/conformance
      - name: Run client conformance (all suite)
        # The harness runs all scenarios via unbounded Promise.all; with 40
        # scenarios on a 2-core runner the slowest one (sse-retry, which has a
        # real-time SSE reconnect wait) needs more than the 30s default budget.
        # `.venv/bin/python` (not `uv run`) avoids lockfile re-checks in ~40
        # concurrent spawns; run-client.sh re-runs unexpected failures solo.
        run: >-
          ./.github/actions/conformance/run-client.sh
          --command '.venv/bin/python .github/actions/conformance/client.py'
          --suite all
          --timeout 60000
          --expected-failures ./.github/actions/conformance/expected-failures.yml
          --output-dir conformance-results/client-all
      - name: Run client conformance (2026-07-28 wire, all suite)
        run: >-
          ./.github/actions/conformance/run-client.sh
          --command '.venv/bin/python .github/actions/conformance/client.py'
          --suite all
          --timeout 60000
          --spec-version 2026-07-28
          --expected-failures ./.github/actions/conformance/expected-failures.2026-07-28.yml
          --output-dir conformance-results/client-2026-07-28
      - name: Upload conformance results
        # The log has only summary counts; per-check data is in checks.json.
        # Also on FLAKE_RESCUED: rescued-flake evidence is otherwise discarded.
        if: failure() || hashFiles('conformance-results/**/FLAKE_RESCUED') != ''
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: client-conformance-results
          path: conformance-results/
          if-no-files-found: ignore
