Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 78 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
name: Run install, format, lint, build, and test on every push

on: [push]
on:
push:
# A published GitHub release triggers the `publish` job below. The release's
# target commit determines which workflow runs — so this only publishes when a
# release is cut from a commit that carries this (v2) workflow.
release:
types: [published]

jobs:
build:
Expand Down Expand Up @@ -69,3 +75,74 @@ jobs:
- name: Run Storybook play-function tests
working-directory: ./clients/web
run: npm run test:storybook

# Publish the single `@modelcontextprotocol/inspector` package to npm on a
# published GitHub release. v2 is not an npm workspace, so there is no
# `publish-all` / `--workspaces` (v1) — just one `npm publish`, whose `prepack`
# (`npm run build`) builds every client bundle into the `files` allowlist.
# `pack:verify` runs first as the pre-publish gate: it builds, packs the real
# tarball, installs it into a clean throwaway consumer, and drives the
# installed `mcp-inspector` bin (web/cli/tui) end to end — so a broken package
# is caught before it reaches npm rather than after.
publish:
runs-on: ubuntu-latest
if: github.event_name == 'release'
environment: release
needs: build
# Serialize publishes so two releases cut in quick succession can't run
# overlapping `npm publish`es. Never cancel an in-flight publish.
concurrency:
group: publish-npm
cancel-in-progress: false
permissions:
contents: read
# Required for npm provenance (`--provenance` mints a signed attestation
# via GitHub's OIDC token). The repo is public, so provenance is available.
id-token: write
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v6
with:
node-version: '22.x'
cache: 'npm'
registry-url: 'https://registry.npmjs.org'

- name: Assert release tag matches package version
# `npm publish` ships whatever `version` is in the root package.json,
# regardless of the release's tag. Fail fast (before the heavy install /
# pack:verify) if they disagree — e.g. a release drafted without running
# `npm version`, or cut from the wrong commit — since publishing is
# irreversible. Tolerates the conventional leading `v` (npm version tags
# as `vX.Y.Z`). The tag arrives via `env:` (not spliced into the script)
# to avoid the script-injection surface of interpolating `${{ }}` into a
# `run:` block.
env:
TAG: ${{ github.event.release.tag_name }}
run: |
PKG="$(node -p "require('./package.json').version")"
if [ "${TAG#v}" != "$PKG" ]; then
echo "Release tag '$TAG' does not match package.json version '$PKG'"
exit 1
fi

- name: Install dependencies (root + all clients)
run: npm install

- name: Verify the publishable tarball end to end
# Builds, `npm pack`s, installs the tarball into a clean consumer, and
# drives the installed bin. Needs registry access to pull the tarball's
# runtime deps — available here. `smoke:tui` inside it self-skips on CI.
run: npm run pack:verify

- name: Publish to npm (single package, with provenance)
# `prepack` (`npm run build`) rebuilds the client bundles into the tarball.
# The build runs three times on this path (build job → pack:verify →
# prepack); the redundancy is intentional — each is a clean-tree rebuild
# and the `prepack` one is what actually populates the published tarball,
# so don't "optimize" it away.
run: npm publish --access public --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
42 changes: 27 additions & 15 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,49 +123,61 @@ All work should be driven by items on the project board.
- Run format, lint, typecheck, build, and test — ensure all checks pass
- Open a PR against the matching base branch (`main` for v1, `v2/main` for v2) and set the item's Status to **In review**
- **Link the PR to its issue.** The PR body's **first line must be `Closes #<ISSUE_NUMBER>`**. ⚠️ Note: closing keywords only auto-link/auto-close for PRs targeting the repo's **default branch** (`main`). Because v2 PRs target `v2/main` (a non-default branch), `Closes #N` there is only a cross-reference — it will **not** create a hard link or close the issue on merge. (There is no `gh` flag for manual linking — `gh pr edit` has no `--add-issue`; closing keywords are the only mechanism GitHub exposes, and they're gated to the default branch.)
- **On merge of a v2 PR, manually close its issue and move the board item to Done** (option id `1bbc5632`), since auto-close won't fire on `v2/main`. Keep the `Closes #N` line anyway so the issues close automatically if/when `v2/main` is eventually merged to `main`.
- **On merge of a v2 PR, manually close its issue and move the board item to Done** (option id `248a3910`), since auto-close won't fire on `v2/main`. Keep the `Closes #N` line anyway so the issues close automatically if/when `v2/main` is eventually merged to `main`.
- If new tasks are discovered or requested during development, create issues and add them to the board.

#### V2 board (#28) `gh` recipes

The board is an **org project**, so all commands use `--owner modelcontextprotocol` and the numeric project `28`. The IDs below are stable; if a command rejects one, re-fetch with `gh project field-list 28 --owner modelcontextprotocol --format json`.
The board is an **org project**, so all commands use `--owner modelcontextprotocol` and the numeric project `28`. The project node id and Status field id are stable. **The Status *option* ids are NOT stable — they are regenerated whenever the Status field's option list is edited** (see the ⚠️ hazard below). If any option id here is rejected, re-fetch the current set with:

```sh
gh project field-list 28 --owner modelcontextprotocol --format json \
| jq '.fields[] | select(.name=="Status") | .options'
```

| Thing | ID |
| --- | --- |
| Project node ID | `PVT_kwDOCt2Azc4BJVxt` |
| Status field ID | `PVTSSF_lADOCt2Azc4BJVxtzg5iI8c` |

Status option IDs (`--single-select-option-id`):
Status option IDs (`--single-select-option-id`) — **last verified 2026-07-09**:

| Status | Option ID |
| --- | --- |
| Backlog | `6080ca99` |
| Building CLI / TUI / CORE | `fe170c62` |
| Building Web | `4faeae7a` |
| MCP Apps Extension | `588c6a63` |
| In progress | `d43284fe` |
| In review | `fb2103f2` |
| Done | `1bbc5632` |

Use **In progress** for general work, one of the **Building** statuses (or **MCP Apps Extension**) while actively coding that surface, **In review** once a PR is open, and **Done** on merge.
| Todo | `fbdaf21e` |
| Building CLI / TUI / CORE | `4ac261ee` |
| Building Web | `c28da89f` |
| MCP Apps Extension | `73d0b807` |
| SDK V2 + New Spec | `1bbb6f57` |
| In Progress | `195df262` |
| In Review | `159c8a02` |
| Done | `248a3910` |

Use **Todo** for approved-but-not-started work, **In Progress** for general active work, one of the **Building** statuses (or **MCP Apps Extension** / **SDK V2 + New Spec**) while actively coding that surface, **In Review** once a PR is open, and **Done** on merge.

> ⚠️ **Never add, rename, or remove a board column (Status option) with the `updateProjectV2Field` GraphQL mutation unless you pass every existing option's `id`.** That mutation does a **full replace** of the option list: if you resend options by name/color/description but omit their `id`s, GitHub **deletes all existing options and mints new ones**, which **orphans the Status of every card on the board** (all items go blank) *and* invalidates every option id in the table above. This has happened once (required reconstructing ~197 items' statuses by inference). Safe alternatives, in order of preference:
> 1. **Add/rename/remove a column in the GitHub web UI** (Project #28 → Status field settings). This preserves ids of untouched options and never orphans cards.
> 2. If you must script it, first `gh api graphql` the current options **with their `id`s**, then call `updateProjectV2Field` echoing back every existing option **including its `id`**, appending only the new one. Verify afterward that no card lost its Status.
>
> `gh project item-add` and `gh project item-edit` are always safe — they set a card's value and never touch the field schema. When option ids change for any reason, **re-verify and update the table above** (and the `248a3910` / `195df262` references in the recipes below and the merge step above).

```sh
# 1. Add an issue to the board — prints the item id (PVTI_…); capture it.
gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json

# 2. Set its Status (here: In progress). Use the option id from the table above.
# 2. Set its Status (here: In Progress). Use the option id from the table above.
gh project item-edit \
--project-id PVT_kwDOCt2Azc4BJVxt \
--id <item-id-from-step-1> \
--field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c \
--single-select-option-id d43284fe
--single-select-option-id 195df262
```

The one-liner that does both, capturing the item id (use the option id for the status you want):

```sh
ITEM_ID=$(gh project item-add 28 --owner modelcontextprotocol --url <issue-url> --format json --jq '.id')
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id d43284fe
gh project item-edit --project-id PVT_kwDOCt2Azc4BJVxt --id "$ITEM_ID" --field-id PVTSSF_lADOCt2Azc4BJVxtzg5iI8c --single-select-option-id 195df262
```

### Always test new or modified code
Expand Down
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,22 @@ The root `package.json` `"files"` allowlist is the source of truth for the tarba

The `smoke:*` scripts run against the in-repo build tree, which is **not** the published package. `npm run pack:verify` (`scripts/pack-and-verify.mjs`) closes that gap: it builds, `npm pack`s the publishable tarball (asserting no source maps ship and that the runtime-required files are present), installs the tarball into a **clean throwaway consumer** — a fresh temp directory where it runs a real `npm install <tgz>` (pulls runtime deps, runs `postinstall`), exactly as `npx @modelcontextprotocol/inspector` would — and drives the installed `mcp-inspector` bin end to end: `--help` dispatch, a real `--cli tools/list` over stdio, and a prod `--web` boot that must serve `/` from the shipped `dist`. It catches "works in `--dev`, breaks under `npx …`" path/packaging failures. It requires network access (the install pulls deps), so it is a local / release check, **not** part of the fast `validate`/`ci` loop.

### Cutting a release

Publishing is automated by the `publish` job in [`.github/workflows/main.yml`](.github/workflows/main.yml), gated on a **published GitHub release** (`github.event_name == 'release'`). On release it runs `npm run pack:verify` as the pre-publish gate, then `npm publish --access public --provenance` — a single `npm publish` (v2 is not an npm workspace, so there is no v1-style `publish-all`/`--workspaces`), with a signed provenance attestation via GitHub OIDC (`id-token: write`, `environment: release`, `NPM_TOKEN`).

Because there is **one version number** (only the root `package.json` has one — the clients carry none, so there is nothing to keep in sync and no `check-version` step), the release flow is just:

```bash
npm version <major|minor|patch> # bumps the root package.json + tags
git push --follow-tags
# then draft & publish a GitHub Release for that tag → triggers `publish`
```

The release's target commit selects which workflow runs, so this only publishes when a release is cut from a commit carrying this (v2) workflow.

> **Docker / GHCR is not wired up yet.** v1 published a container image to GHCR, but v2 has no Dockerfile (its `client/server/cli` layout is gone). A v2 image + GHCR job is tracked separately in [#1646](https://github.com/modelcontextprotocol/inspector/issues/1646).

## Contributing — `AGENTS.md` and `CLAUDE.md`

**[`AGENTS.md`](./AGENTS.md) is the contract for changing this codebase, and it applies to humans and AI agents alike.** It is not agent-only boilerplate — it holds the project's real conventions: the issue-and-board workflow, branch/label rules, the TypeScript and Mantine/React standards, the testing and coverage requirements, and the mandatory pre-push gate. Read it before making changes, and keep it up to date when you change structure, tooling, or rules.
Expand Down
Loading
Loading