Merge pull request #1648 from modelcontextprotocol/1646-v2-docker-ghcr
Add v2 Dockerfile + GHCR publish job
This commit is contained in:
commit
4e8e772aff
4 changed files with 169 additions and 2 deletions
38
.dockerignore
Normal file
38
.dockerignore
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
# Keep the build context small and force a clean, in-container install/build.
|
||||
# Host node_modules and build outputs are excluded so nothing stale or
|
||||
# wrong-architecture is copied in — the builder stage runs its own
|
||||
# `npm install && npm pack`.
|
||||
|
||||
.git
|
||||
.github
|
||||
|
||||
# Installed deps (root + every client) — reinstalled in the image
|
||||
node_modules
|
||||
clients/*/node_modules
|
||||
test-servers/node_modules
|
||||
|
||||
# Build outputs — rebuilt in the image
|
||||
clients/*/build
|
||||
clients/web/dist
|
||||
test-servers/build
|
||||
storybook-static
|
||||
coverage
|
||||
|
||||
# Docs/specs aren't referenced by the build — trim them from the context.
|
||||
# (test-servers/ stays: `tsc -b` typechecks the integration tests that import
|
||||
# the `@modelcontextprotocol/inspector-test-server` alias.)
|
||||
specification
|
||||
docs
|
||||
|
||||
# Packed tarballs (the image makes its own)
|
||||
*.tgz
|
||||
|
||||
# Local / editor / env
|
||||
.env
|
||||
.env.*
|
||||
.DS_Store
|
||||
.vscode
|
||||
.idea
|
||||
.claude
|
||||
.playwright-mcp
|
||||
*.log
|
||||
62
.github/workflows/main.yml
vendored
62
.github/workflows/main.yml
vendored
|
|
@ -146,3 +146,65 @@ jobs:
|
|||
run: npm publish --access public --provenance
|
||||
env:
|
||||
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
|
||||
# Build and push the multi-arch container image to GHCR on a published
|
||||
# release. The image installs the packed tarball (`Dockerfile`) so it ships
|
||||
# the same artifact as npm. Independent of the npm `publish` job (both gated on
|
||||
# the release event) — a container failure doesn't block the npm publish.
|
||||
publish-github-container-registry:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'release'
|
||||
environment: release
|
||||
needs: build
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
attestations: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Log in to the Container registry
|
||||
uses: docker/login-action@v4
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Extract metadata (tags, labels) for Docker
|
||||
id: meta
|
||||
uses: docker/metadata-action@v6
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
# Be explicit rather than relying on `flavor.latest=auto`: on a release
|
||||
# cut both the version tags and `latest` land, so the README's bare
|
||||
# `ghcr.io/…/inspector` (implicit `:latest`) never 404s.
|
||||
tags: |
|
||||
type=semver,pattern={{version}}
|
||||
type=semver,pattern={{major}}.{{minor}}
|
||||
flavor: |
|
||||
latest=true
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v3
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v4
|
||||
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
platforms: linux/amd64,linux/arm64
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@v4
|
||||
with:
|
||||
subject-name: ghcr.io/${{ github.repository }}
|
||||
subject-digest: ${{ steps.push.outputs.digest }}
|
||||
push-to-registry: true
|
||||
|
|
|
|||
51
Dockerfile
Normal file
51
Dockerfile
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Build stage — install everything, build all clients, and pack the exact
|
||||
# tarball npm would publish. `npm install` runs the root postinstall cascade
|
||||
# (per-client installs, since v2 is not a workspace); `npm pack` runs `prepack`
|
||||
# (`npm run build`) and emits `modelcontextprotocol-inspector-<version>.tgz`.
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM node:22-slim AS builder
|
||||
WORKDIR /build
|
||||
COPY . .
|
||||
RUN npm install && npm pack
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Runtime stage — install just the packed tarball (production deps only),
|
||||
# yielding a clean global `mcp-inspector` bin: the same artifact a user gets
|
||||
# from `npm i -g @modelcontextprotocol/inspector`. The package's postinstall
|
||||
# early-exits under node_modules, and the tarball ships only each client's
|
||||
# build/ output, so there is nothing to build here.
|
||||
# ---------------------------------------------------------------------------
|
||||
FROM node:22-slim AS runner
|
||||
ENV NODE_ENV=production
|
||||
|
||||
COPY --from=builder /build/modelcontextprotocol-inspector-*.tgz /tmp/inspector.tgz
|
||||
RUN npm install -g /tmp/inspector.tgz && rm /tmp/inspector.tgz
|
||||
|
||||
# Serve on all interfaces so the UI is reachable from outside the container, and
|
||||
# never try to open a browser from inside it.
|
||||
ENV HOST=0.0.0.0 \
|
||||
CLIENT_PORT=6274 \
|
||||
MCP_AUTO_OPEN_ENABLED=false
|
||||
EXPOSE 6274
|
||||
|
||||
# Run as the non-root `node` user the base image ships. The inspector resolves
|
||||
# its runtime-state dir (default catalog, OAuth token storage) from `HOME`
|
||||
# (core/storage/store-io.ts), so set it explicitly to the node user's writable
|
||||
# home and work from there.
|
||||
ENV HOME=/home/node
|
||||
USER node
|
||||
WORKDIR /home/node
|
||||
|
||||
# Report readiness by probing the served SPA (`/` needs no auth). Uses Node's
|
||||
# global fetch — no curl/wget in the slim image. Assumes the default `--web`
|
||||
# mode; running `--cli`/`--tui` has no web server, so add `--no-healthcheck` to
|
||||
# `docker run` for those.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \
|
||||
CMD node -e "fetch('http://127.0.0.1:'+(process.env.CLIENT_PORT||6274)+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||||
|
||||
# Default to the web UI; override the args to run --cli / --tui.
|
||||
ENTRYPOINT ["mcp-inspector"]
|
||||
CMD ["--web"]
|
||||
20
README.md
20
README.md
|
|
@ -150,7 +150,10 @@ The `smoke:*` scripts run against the in-repo build tree, which is **not** the p
|
|||
|
||||
### 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`).
|
||||
Publishing is automated by two release-gated jobs in [`.github/workflows/main.yml`](.github/workflows/main.yml) (`github.event_name == 'release'`, both `needs: build`):
|
||||
|
||||
- **`publish`** — the npm package. Runs `npm run pack:verify` as the pre-publish gate, asserts the release tag matches the root `package.json` version, 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`).
|
||||
- **`publish-github-container-registry`** — the container image (see [Docker](#docker)).
|
||||
|
||||
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:
|
||||
|
||||
|
|
@ -162,7 +165,20 @@ git push --follow-tags
|
|||
|
||||
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).
|
||||
### Docker
|
||||
|
||||
A container image is published to GHCR (`ghcr.io/modelcontextprotocol/inspector`, `linux/amd64` + `linux/arm64`) by the release workflow. The [`Dockerfile`](Dockerfile) is a two-stage build: the first stage installs and `npm pack`s the publishable tarball; the second stage `npm install -g`s that tarball, so the image ships the exact same artifact as npm, with a clean `mcp-inspector` bin.
|
||||
|
||||
```bash
|
||||
# run the web UI (reads the auth token from the container logs)
|
||||
docker run --rm -p 6274:6274 ghcr.io/modelcontextprotocol/inspector
|
||||
|
||||
# or build the image locally
|
||||
docker build -t mcp-inspector .
|
||||
docker run --rm -p 6274:6274 mcp-inspector
|
||||
```
|
||||
|
||||
The image defaults to `--web` bound to `0.0.0.0:6274` with browser auto-open disabled; override the args to run another mode (`docker run --rm ghcr.io/modelcontextprotocol/inspector --cli …`). Pass `-e MCP_INSPECTOR_API_TOKEN=…` to set a known token (otherwise one is generated and printed in the logs), or `-e DANGEROUSLY_OMIT_AUTH=true` to disable auth. The image runs as the non-root `node` user and has a `HEALTHCHECK` that probes the web UI — it assumes the default `--web` mode, so add `--no-healthcheck` when running `--cli`/`--tui` (which have no web server).
|
||||
|
||||
## Contributing — `AGENTS.md` and `CLAUDE.md`
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue