# GitHub Action

> Check MCP server compatibility in GitHub pull requests. Configure baselines, failure thresholds, migration hints and PR comments.

## Preview access required

The standalone Action has not been released at `mcp-inspect/action@v1`.
The implementation repository is private. Use only a reviewed CLI build or
Action supplied through approved preview access; there is no public source
checkout or installable Action release yet. The reference below describes the
implemented behavior, not a working public distribution.

A CI check captures your server in the runner and compares it with a baseline
committed to the pull request's base branch. The hosted service is optional for
that local comparison. [Getting started](/docs/getting-started) covers the
hosted console and preview prerequisites.

## Standalone Action reference (release pending)

The following describes the implemented Action, whose release is still pending.
Its `uses` location is a placeholder and cannot currently be copied into CI.

```yaml
name: MCP Compatibility
on: pull_request

permissions:
  contents: read
  pull-requests: write

jobs:
  mcp-check:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with: { fetch-depth: 0 } # the git baseline needs history
      - uses: actions/setup-node@v4
        with: { node-version: 22 }
      - run: npm ci && npm run build
      - uses: mcp-inspect/action@v1
        with:
          fail-on: BREAKING
```

## Inputs

| Input               | Default              | Meaning                                        |
| ------------------- | -------------------- | ---------------------------------------------- |
| `config`            | `.mcp-inspect.yml`   | Config path.                                   |
| `baseline`          | the PR's base branch | Git ref to compare against.                    |
| `baseline-file`     | —                    | Explicit snapshot file; wins over `baseline`.  |
| `fail-on`           | `BREAKING`           | Threshold. `never` makes the Action advisory.  |
| `comment`           | `true`               | Post or update a pull request comment.         |
| `annotate`          | `true`               | Emit `::error` / `::warning` annotations.      |
| `api-key`           | —                    | Hosted key. Enables usage evidence and `push`. |
| `push`              | `false`              | Record this run as a deployment.               |
| `working-directory` | `.`                  | For monorepos.                                 |

Outputs: `compared`, `passed`, `breaking`, `potentially-breaking`, `summary`, `report`,
`fingerprint`.

## The comment

```markdown
### MCP Compatibility

**1 breaking, 2 potentially breaking, 1 non-breaking**

🚫 **`search`** `.limit`
`limit` changed from optional to required.

> Accept calls that omit it for at least one release, then require it.

⚠ **`create_issue`** `.description`
Description changed substantially. Agents may alter tool-selection behavior.

➕ **`archive_issue`**
New tool.
```

One comment, found by a hidden marker, updated in place. A pull request with
twenty pushes has one current comment rather than twenty stale ones.

## Usage evidence

With an `api-key`, observed usage is folded in where it changes the decision:

> 🚫 **`legacy_search`** — removed
> `legacy_search` accounted for 0.03% of calls in the last 90 days, but **4
> distinct clients** called it, most recently 2 days ago. Removing it will break
> them.

That sentence is the product. It is also why the Action takes a key but does not
require one: the compatibility check is useful alone.

## Failure modes

The job summary is written **before** the comment is attempted, because a fork
pull request has a read-only token and the summary is the only output that always
survives. Failing to reach the hosted service is a warning, never a failure. The
Action exits non-zero only when the threshold was exceeded.

## Know when protection is active

With no baseline, the job exits 0 but reports **Comparison skipped — baseline
needed** and `compared=false`. `passed=true` alone means the job did not block.
A real passing comparison requires both `compared=true` and `passed=true`.

Merge the initial snapshot into the PR base branch, then run a check on a later
PR. After reviewing an intentional change, regenerate and commit the snapshot
with that change to keep the baseline current.
