## Summary
We keep getting PRs (e.g. #1385, #1368, #1367, #1363, #1354) that try to
"publish" an MCP server by adding files under `servers/` or editing
`data/seed.json`. That isn't how publishing works — servers are
published with the `mcp-publisher` CLI against the live registry API.
This adds automation that detects those PRs, comments with a pointer to
the publishing quickstart, labels them `invalid`, and closes them.
## How it works (two stages, no `pull_request_target`)
`pull_request_target` is intentionally avoided. Instead:
- **Stage 1 — `detect-invalid-publish-prs.yml`** runs on `pull_request`
(opened/reopened) with a **read-only** token. It only reads the changed
file paths and, if the PR *exclusively* touches `servers/**` and/or
`data/seed.json` (and the author isn't a MEMBER/OWNER/COLLABORATOR),
records just the PR number as a short-lived artifact. It can't comment
or close anything.
- **Stage 2 — `close-invalid-publish-prs.yml`** runs via `workflow_run`
in the trusted base-repo context (write token). It picks up the flagged
PR number, **independently re-validates** the PR from the API (still
open, external author, files still match, not already labeled), then
comments, labels `invalid`, and closes.
### Trust boundary
The only thing crossing from the untrusted stage to the privileged stage
is the **PR number**, sanitized to digits only. Stage 2 never trusts
stage 1's verdict — it re-derives every decision. Neither stage ever
checks out or runs PR code.
### Safety against false positives
- Fires only when a PR touches *exclusively* `servers/**` and/or
`data/seed.json` — a real code PR that also happens to touch seed data
is left alone.
- Skips `MEMBER`/`OWNER`/`COLLABORATOR` authors, so maintainers editing
dev seed data aren't auto-closed.
- Idempotent: skips PRs already labeled `invalid` (e.g. if a maintainer
reopens one).
## Notes
- Like any `pull_request`/`workflow_run` automation, this only takes
effect **once merged to `main`** — it is not retroactive and won't run
on this PR. The currently-open offending PRs should be closed manually.
- Pinned action SHAs match the repo's existing convention
(`upload-artifact` reuses the SHA already in `ci.yml`).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
---------
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>