Skip to main content

The required check on an MCP server repository

This page is for the owner of a remote MCP server whose tools a ChatGPT Business, Enterprise, or Edu admin has already approved. The same repository check is what a Claude organisation would point at a default config. This page did not measure a Claude organisation. Measured 2026-09-30.

1. The hole

Help Center article Developer mode and MCP apps in ChatGPT, read 2026-09-30. Curl to help.openai.com that day returned HTTP 403. The words below are the page reader's text of the FAQ answer to "Do MCP app changes auto-update in my workspace?":

No. After an admin first approves an MCP app for the workspace, ChatGPT uses a "frozen" snapshot of its available tools and inputs. Changes made later by the app's developer are not applied until an admin reviews and publishes an update.

If the live app no longer matches the frozen snapshot, tool calls can error.

https://help.openai.com/en/articles/12584461-developer-mode-and-mcp-apps-in-chatgpt

The same article's answer on updating after publish, same read:

Business admins/owners cannot currently update apps after publishing; recreate and republish to update tools or metadata. Enterprise/Edu admins/owners can enable or disable app/connector actions after publishing.

The publish section, same read, on Enterprise and Edu action control after publish:

Updates to the MCP server are not automatically enabled - you can click the Refresh button to obtain the latest set of actions, or updates to existing actions. New actions are disabled by default, and changes to existing actions are shown as a diff.

The frozen sentence says "available tools and inputs". The Business sentence is the one that says "tools or metadata". The article does not mention a pull request or a required status check.

On the repository, that review is no merge check. Enterprise and Edu do show a diff of action changes after Refresh. That diff is in Workspace settings. It is not a check on the commit that changed the tool file. Business recreate-and-republish does not say it shows a diff. Neither paragraph stops a merge.

The personal app on ChatGPT Plus is a different page. It reports and does not issue receipts: the ChatGPT app page. Dots was not run: dots and the gate.

2. The recipe

Install the GitHub App on the repository that holds the tool file: github.com/apps/coderifts.

.coderifts.yml names paths. parseConfig, read from the app tree at 8c847f7 (src/config-parser.js, not in that tree's dirty set), accepts schema as a list of strings. Every entry must be a string. The function does not return a type key. An object entry is a parse error. One named file, in the shape the parser accepts:

schema:
  - mcp/tools.json

The type mcp_manifest is assigned by the contract-gate Action, not by that file. On contract-gate v0.11.3, commit 97d4cea, published 2026-10-06 19:50 UTC, the Action does not classify mcp.json, and a schema entry whose name is an MCP client configuration is dropped before the App reads it. src/artifacts.js classify refuses that name first, then matches (^|/)(mcp[^/]*\.json|tools?-catalog\.json|mcp-manifest\.json). Refused, so they are not read from git and not sent: mcp.json, .mcp.json, .cursor/mcp.json, .vscode/mcp.json, claude_desktop_config.json, mcp_settings.json, and cline_mcp_settings.json. A server manifest whose filename is mcp.json is refused by that same name rule, including .well-known/mcp.json. mcp-tool-manifest.json, tools-catalog.json, tool-catalog.json, and mcp-manifest.json still match. mcp/tools.json does not match the Action. The Action derives artifacts from the diff. It does not read schema from .coderifts.yml. The schema list is what the App webhook fetches. The example above, mcp/tools.json, is a path looksLikeContractPath still treats as a contract. Listing one path does not stop the Action from classifying a second matching file in the same diff.

The customer workflow is examples/contract-gate.yml at 06b90dc. The two contexts, verbatim, from that file and from the App:

- uses: coderifts/contract-gate@v0

name: "CodeRifts / contract-gate"     # the required context, verbatim

# The App posts the other context:
# CodeRifts / issuer

@v0 is the example's ref. It is a moving tag. Copying the example as written does not pin a commit. The workflow file on coderifts/demo pull request 4, head a81e6092fea0ca82696a6d252441afde4117f2fa, pins coderifts/contract-gate@f01c6f155f258ee9156cb2b6ef378478c6bce26e and names the aggregator job CodeRifts / contract-gate.

The ruleset, measured the same day on coderifts/demo ruleset 22074842 (coderifts-enforcement, enforcement active): bypass_actors is empty. strict_required_status_checks_policy is true. The required checks are CodeRifts / issuer bound to integration id 2860592 and CodeRifts / contract-gate bound to integration id 15368. Classic branch protection on main, read the same day, lists the same two contexts and the same app ids, and its strict is false. The published steps for a customer ruleset are in ENFORCEMENT.md at the same commit. This page does not apply them.

3. A pull request that changes a tool schema or a description

Measured 2026-10-03, coderifts/demo pull request 26 is OPEN, mergeStateStatus BLOCKED, head bee32065e6adaff2b9f2c7200609038fdf4d29ac, and its diff is agent-api-demo/mcp-tool-manifest.json: on list_payments the status parameter becomes required, and the tool description gains an instruction. CodeRifts / contract-gate conclusion FAILURE. CodeRifts / issuer conclusion FAILURE. The same rollup shows contract-gate (Action) conclusion FAILURE and canary conclusion SKIPPED.

The issuer output.title on that head:

STOP · merge · sha256:53f7f6a…3678ab · 2026-09-30T23:40:09Z · REQUIRED_FIELD_ADDED, OPTIONAL_TO_REQUIRED, TOOL_INSTRUCTION_DRIFT, TOOL_INPUT_SCHEMA_DRIFT

The CodeRifts / contract-gate check-run's output.title and output.summary were null. This page does not invent a log line for it.

coderifts/demo contains agent-api-demo/mcp-tool-manifest.json. The classifier above matches that filename. .coderifts.yml on that repository, read 2026-10-03, lists one path, api/openapi.yaml. It does not list the manifest. Pull request 4 stays the OpenAPI example. It changes api/openapi.yaml only. Title: feat!: breaking changes v1.5.0 — remove phone field + narrow order status enum. State OPEN. mergeStateStatus BLOCKED. Re-read 2026-10-03: still OPEN, mergeStateStatus BLOCKED, and both required contexts conclusion FAILURE.

On pull request 4, head a81e6092fea0ca82696a6d252441afde4117f2fa, both required contexts concluded failure. CodeRifts / issuer is app id 2860592, check_run 111270526393. CodeRifts / contract-gate is app id 15368, check_run 111270560848. The issuer output.title is the STOP line quoted in the next paragraph. The issuer output.summary begins with the five lines in the next section. The contract-gate check-run's output.title was null and its output.summary was empty. This page does not invent a log line for it. The rollup also showed contract-gate (Action) failed and canary skipped.

Re-read on 2026-10-03, the latest CodeRifts / issuer check-run on coderifts/demo pull request 4 is check_run 111270526393, head a81e6092fea0ca82696a6d252441afde4117f2fa, GitHub completed_at 2026-10-03T18:58:29Z, conclusion failure. The output title is STOP · merge · sha256:871f500…92c021 · 2026-10-03T22:58:22Z · REQUEST_FIELD_REMOVED, FIELD_REMOVAL, TOOL_RESULT_SHAPE_DRIFT, TOOL_INPUT_SCHEMA_DRIFT. The summary reports Found 3 breaking, 4 safe, and 0 unclassified change(s) across 1 schema file(s). · Risk Score: 59/100 · 1 policy violation(s) (blocking). An earlier issuer check on head df3de0d3ac4bdd1da82ed6449c7780b27ac13c7d, completed 2026-09-28T20:13:03Z, reported 6 breaking changes.

The gate judges an enum change by direction (ruleset enum-direction/2026-10-03): a request value removed is breaking, a request value added is not breaking, a response value removed is INFO, and a response value added is breaking and the decision is REQUIRE_APPROVAL, not BLOCK.

The committed note on the direction rule says the 3.0 engine adds a schema-level enum entry with an assumed request direction, and a response-side removal stays an INFO change. On this pull request Order.status drops delivered, and Order is a response body on GET /orders, POST /orders, and GET /orders/{id}, so that narrowing is an INFO change and sits outside the 3 breaking changes in check_run 111270526393.

The breaking changes behind the STOP line are the phone field removals, including the removal from the request: a response property on POST /users, a response property on GET /users/{id}, and a request property on POST /users. The title cites REQUEST_FIELD_REMOVED, FIELD_REMOVAL, TOOL_RESULT_SHAPE_DRIFT, and TOOL_INPUT_SCHEMA_DRIFT.

Pull request 4 is still open and its merge state is BLOCKED, so it has not merged. Check_run 111270526393 concluded failure, and its summary records 1 policy violation(s) (blocking).

The issuer summary on pull request 4 also says it is not claiming enforcement: it could not read branch protection (missing_permission). The ruleset read above is a separate API call, and it does list those two contexts as required. Pull request 4, with mergeStateStatus BLOCKED, is the measurement that the OpenAPI change did not merge. It is an OpenAPI change. It is not an mcp_manifest change.

4. For the admin who re-publishes your app

The five lines of the issuer check summary on pull request 4, head a81e6092fea0ca82696a6d252441afde4117f2fa, check_run 111270526393, with the receipt digest in the third line. On this run the digest is n/a. The place is "issued in this run — not in the commit".

STOP · merge · sha256:871f500…92c021 · 2026-10-03T22:58:22Z · REQUEST_FIELD_REMOVED, FIELD_REMOVAL, TOOL_RESULT_SHAPE_DRIFT, TOOL_INPUT_SCHEMA_DRIFT
signature_present_not_locally_verified
n/a · issued in this run — not in the commit
npx @coderifts/receipt-verifier
the approver is not in the signature · https://coderifts.com/docs/proof-boundaries/

On this run the first line carries STOP, the operation merge, sha256:871f500…92c021, the timestamp 2026-10-03T22:58:22Z, and the reason codes REQUEST_FIELD_REMOVED, FIELD_REMOVAL, TOOL_RESULT_SHAPE_DRIFT, TOOL_INPUT_SCHEMA_DRIFT. GitHub records completed_at 2026-10-03T18:58:29Z for the same check. The third line is the receipt digest, then where the receipt sits. When a digest is present, that field is the identifier of the change set the grant was bound to. It is the thing you can hold next to the frozen snapshot. It is not a rendering of the snapshot, and it is not the git diff. The git diff of the tool file is on the pull request. The Workspace settings diff, on Enterprise and Edu after Refresh, is the article's diff. This summary is neither of those.

What this summary shows: those five lines, and behind them a details block (on this run: Found 3 breaking, 4 safe, and 0 unclassified change(s), Risk Score 59/100, 1 policy violation(s) (blocking), and the enforcement-not-verified paragraph). What it does not show: the approver, the frozen tool list, or a digest for this run.

5. Two boundaries

the receipt does not name the approver — the GitHub review does

you would be the first foreign repository with this check required; here is the demo as the only measured stop

The stop measured on 2026-09-30 is pull request 4, ruleset 22074842, both contexts failed, merge state blocked. Re-read 2026-10-03: pull request 4 is still that OpenAPI stop, and pull request 26 is the MCP tool-manifest stop. The blocking pull requests measured on coderifts/demo are pull request 4, an OpenAPI change, and pull request 26, an MCP tool manifest. This round did not enumerate other GitHub App installations.

6. When a check is a lock

a check is a lock only when it is required on the default branch, bound to the right app, bypass closed, SHA pinned — without all four it is a comment

On the demo ruleset the first three hold: required on main, bound to 15368 and 2860592, bypass empty. The pull request's workflow file is SHA pinned. The published example's @v0 is not that pin. Classic protection on the same branch has strict: false. The issuer check on this run does not claim the lock: it could not read protection.

7. Price

From the pricing page: During the public beta every organization gets Team features at $0 with a self-serve key, public and private repositories alike, no separate Team key to request, and each key allows 1,000 requests a month: after that authorize returns BETA_LIMIT_REACHED, analysis keeps working, and one email to hello@coderifts.com raises the limit. This page adds no price.

What this page does not prove

It does not prove .coderifts.yml lists the manifest. The file read on 2026-10-03 lists api/openapi.yaml only. Pull request 26 changes the manifest, and the Action classifies that filename from the diff.

It does not prove a foreign repository has either context required. The sentence in section 5 is the measurement.

It does not prove the receipt names a person. Line five says the approver is not in the signature.

It does not prove the issuer check verified the ruleset. The summary says enforcement status not verified.

It does not prove @v0 is a commit. The example ref and the pull request's pin are different strings.

It does not prove a personal ChatGPT app can issue a receipt. Analyze needs no key. Authorize needs a key. That limit is on the ChatGPT app page.

It does not prove a shell command, a raw credential, or a tool call outside the guarded table is stopped. The boundary on this page is the merge of the repository that holds the tool file.

It does not prove the Enterprise Workspace diff is this check. That diff is OpenAI's, after Refresh. This check is GitHub's, on the pull request.