diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..a3e960e3 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,35 @@ +# Version control +.git +.gitignore + +# Node.js +node_modules +npm-debug.log + +# Build artifacts +client/dist +client/build +server/dist +server/build + +# Environment variables +.env +.env.local +.env.development +.env.test +.env.production + +# Editor files +.vscode +.idea + +# Logs +logs +*.log + +# Testing +coverage + +# Docker +Dockerfile +.dockerignore \ No newline at end of file diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8efb6773..65e98fa4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,6 +26,9 @@ jobs: # - run: npm ci - run: npm install --no-package-lock + - name: Check version consistency + run: npm run check-version + - name: Check linting working-directory: ./client run: npm run lint @@ -62,3 +65,52 @@ jobs: - run: npm run publish-all env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + 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: + - uses: actions/checkout@v4 + + - name: Log in to the Container registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - 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@v2 + with: + subject-name: ghcr.io/${{ github.repository }} + subject-digest: ${{ steps.push.outputs.digest }} + push-to-registry: true diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..f36fb8bb --- /dev/null +++ b/Dockerfile @@ -0,0 +1,52 @@ +# Build stage +FROM node:24-slim AS builder + +# Set working directory +WORKDIR /app + +# Copy package files for installation +COPY package*.json ./ +COPY .npmrc ./ +COPY client/package*.json ./client/ +COPY server/package*.json ./server/ +COPY cli/package*.json ./cli/ + +# Install dependencies +RUN npm ci --ignore-scripts + +# Copy source files +COPY . . + +# Build the application +RUN npm run build + +# Production stage +FROM node:24-slim + +WORKDIR /app + +# Copy package files for production +COPY package*.json ./ +COPY .npmrc ./ +COPY client/package*.json ./client/ +COPY server/package*.json ./server/ +COPY cli/package*.json ./cli/ + +# Install only production dependencies +RUN npm ci --omit=dev --ignore-scripts + +# Copy built files from builder stage +COPY --from=builder /app/client/dist ./client/dist +COPY --from=builder /app/client/bin ./client/bin +COPY --from=builder /app/server/build ./server/build +COPY --from=builder /app/cli/build ./cli/build + +# Set default port values as environment variables +ENV CLIENT_PORT=6274 +ENV SERVER_PORT=6277 + +# Document which ports the application uses internally +EXPOSE ${CLIENT_PORT} ${SERVER_PORT} + +# Use ENTRYPOINT with CMD for arguments +ENTRYPOINT ["npm", "start"] \ No newline at end of file diff --git a/cli/package.json b/cli/package.json index 8e895a55..f1ff1c33 100644 --- a/cli/package.json +++ b/cli/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.14.2", + "version": "0.14.3", "description": "CLI for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/client/package.json b/client/package.json index 540320bf..37a0675b 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-client", - "version": "0.14.2", + "version": "0.14.3", "description": "Client-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", diff --git a/package-lock.json b/package-lock.json index b46e4597..a54bf664 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.14.2", + "version": "0.14.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@modelcontextprotocol/inspector", - "version": "0.14.2", + "version": "0.14.3", "license": "MIT", "workspaces": [ "client", @@ -14,9 +14,9 @@ "cli" ], "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.14.2", - "@modelcontextprotocol/inspector-client": "^0.14.2", - "@modelcontextprotocol/inspector-server": "^0.14.2", + "@modelcontextprotocol/inspector-cli": "^0.14.3", + "@modelcontextprotocol/inspector-client": "^0.14.3", + "@modelcontextprotocol/inspector-server": "^0.14.3", "@modelcontextprotocol/sdk": "^1.12.1", "concurrently": "^9.0.1", "open": "^10.1.0", @@ -40,7 +40,7 @@ }, "cli": { "name": "@modelcontextprotocol/inspector-cli", - "version": "0.14.2", + "version": "0.14.3", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", @@ -63,7 +63,7 @@ }, "client": { "name": "@modelcontextprotocol/inspector-client", - "version": "0.14.2", + "version": "0.14.3", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", @@ -10920,7 +10920,7 @@ }, "server": { "name": "@modelcontextprotocol/inspector-server", - "version": "0.14.2", + "version": "0.14.3", "license": "MIT", "dependencies": { "@modelcontextprotocol/sdk": "^1.12.1", diff --git a/package.json b/package.json index 061ddf1a..7e7be67f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector", - "version": "0.14.2", + "version": "0.14.3", "description": "Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)", @@ -43,9 +43,9 @@ "check-version": "node scripts/check-version-consistency.js" }, "dependencies": { - "@modelcontextprotocol/inspector-cli": "^0.14.2", - "@modelcontextprotocol/inspector-client": "^0.14.2", - "@modelcontextprotocol/inspector-server": "^0.14.2", + "@modelcontextprotocol/inspector-cli": "^0.14.3", + "@modelcontextprotocol/inspector-client": "^0.14.3", + "@modelcontextprotocol/inspector-server": "^0.14.3", "@modelcontextprotocol/sdk": "^1.12.1", "concurrently": "^9.0.1", "open": "^10.1.0", diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 00000000..d6887faf --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,72 @@ +# Version Management Scripts + +This directory contains scripts for managing version consistency across the monorepo. + +## Scripts + +### update-version.js + +Updates the version across all package.json files in the monorepo and updates package-lock.json. + +**Usage:** + +```bash +npm run update-version +# Example: +npm run update-version 0.14.3 +``` + +This script will: + +1. Update the version in all package.json files (root, client, server, cli) +2. Update workspace dependencies in the root package.json +3. Run `npm install` to update package-lock.json +4. Provide next steps for committing and tagging + +### check-version-consistency.js + +Checks that all packages have consistent versions and that package-lock.json is up to date. + +**Usage:** + +```bash +npm run check-version +``` + +This script checks: + +1. All package.json files have the same version +2. Workspace dependencies in root package.json match the current version +3. package-lock.json version matches package.json +4. Workspace packages in package-lock.json have the correct versions + +This check runs automatically in CI on every PR and push to main. + +## CI Integration + +The version consistency check is integrated into the GitHub Actions workflow (`.github/workflows/main.yml`) and will fail the build if: + +- Package versions are inconsistent +- package-lock.json is out of sync + +## Common Workflows + +### Bumping version for a release: + +```bash +# Update to new version +npm run update-version 0.15.0 + +# Verify everything is correct +npm run check-version + +# Commit the changes +git add -A +git commit -m "chore: bump version to 0.15.0" + +# Create a tag +git tag 0.15.0 + +# Push changes and tag +git push && git push --tags +``` diff --git a/scripts/check-version-consistency.js b/scripts/check-version-consistency.js new file mode 100755 index 00000000..379931de --- /dev/null +++ b/scripts/check-version-consistency.js @@ -0,0 +1,177 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Checks version consistency across all package.json files in the monorepo + * Exits with code 1 if versions are inconsistent + * Usage: node scripts/check-version-consistency.js + */ + +console.log("šŸ” Checking version consistency across packages...\n"); + +// List of package.json files to check +const packagePaths = [ + "package.json", + "client/package.json", + "server/package.json", + "cli/package.json", +]; + +const versions = new Map(); +const errors = []; + +// Read version from each package.json +packagePaths.forEach((packagePath) => { + const fullPath = path.join(__dirname, "..", packagePath); + + if (!fs.existsSync(fullPath)) { + console.warn(`āš ļø Skipping ${packagePath} - file not found`); + return; + } + + try { + const packageJson = JSON.parse(fs.readFileSync(fullPath, "utf8")); + const version = packageJson.version; + const packageName = packageJson.name || packagePath; + + versions.set(packagePath, { + name: packageName, + version: version, + dependencies: packageJson.dependencies || {}, + }); + + console.log(`šŸ“¦ ${packagePath}:`); + console.log(` Name: ${packageName}`); + console.log(` Version: ${version}`); + } catch (error) { + errors.push(`Failed to read ${packagePath}: ${error.message}`); + } +}); + +if (errors.length > 0) { + console.error("\nāŒ Errors occurred while reading package files:"); + errors.forEach((error) => console.error(` - ${error}`)); + process.exit(1); +} + +// Check if all versions match +const allVersions = Array.from(versions.values()).map((v) => v.version); +const uniqueVersions = [...new Set(allVersions)]; + +console.log("\nšŸ“Š Version Summary:"); +console.log(` Total packages: ${versions.size}`); +console.log(` Unique versions: ${uniqueVersions.length}`); + +if (uniqueVersions.length > 1) { + console.error("\nāŒ Version mismatch detected!"); + console.error(" Found versions: " + uniqueVersions.join(", ")); + + console.error("\n Package versions:"); + versions.forEach((info, path) => { + console.error(` - ${path}: ${info.version}`); + }); +} else { + console.log(` āœ… All packages are at version: ${uniqueVersions[0]}`); +} + +// Check workspace dependencies in root package.json +const rootPackage = versions.get("package.json"); +if (rootPackage) { + console.log("\nšŸ”— Checking workspace dependencies..."); + const expectedVersion = rootPackage.version; + let dependencyErrors = false; + + Object.entries(rootPackage.dependencies).forEach(([dep, version]) => { + if (dep.startsWith("@modelcontextprotocol/inspector-")) { + const expectedDepVersion = `^${expectedVersion}`; + if (version !== expectedDepVersion) { + console.error( + ` āŒ ${dep}: ${version} (expected ${expectedDepVersion})`, + ); + dependencyErrors = true; + } else { + console.log(` āœ… ${dep}: ${version}`); + } + } + }); + + if (dependencyErrors) { + errors.push("Workspace dependency versions do not match package versions"); + } +} + +// Check if package-lock.json is up to date +console.log("\nšŸ”’ Checking package-lock.json..."); +const lockPath = path.join(__dirname, "..", "package-lock.json"); +let lockFileError = false; + +if (!fs.existsSync(lockPath)) { + console.error(" āŒ package-lock.json not found"); + lockFileError = true; +} else { + try { + const lockFile = JSON.parse(fs.readFileSync(lockPath, "utf8")); + const lockVersion = lockFile.version; + const expectedVersion = rootPackage?.version || uniqueVersions[0]; + + if (lockVersion !== expectedVersion) { + console.error( + ` āŒ package-lock.json version (${lockVersion}) does not match package.json version (${expectedVersion})`, + ); + lockFileError = true; + } else { + console.log(` āœ… package-lock.json version matches: ${lockVersion}`); + } + + // Check workspace package versions in lock file + if (lockFile.packages) { + const workspacePackages = [ + { path: "client", name: "@modelcontextprotocol/inspector-client" }, + { path: "server", name: "@modelcontextprotocol/inspector-server" }, + { path: "cli", name: "@modelcontextprotocol/inspector-cli" }, + ]; + + workspacePackages.forEach(({ path, name }) => { + const lockPkgPath = lockFile.packages[path]; + if (lockPkgPath && lockPkgPath.version !== expectedVersion) { + console.error( + ` āŒ ${name} in lock file: ${lockPkgPath.version} (expected ${expectedVersion})`, + ); + lockFileError = true; + } + }); + } + } catch (error) { + console.error(` āŒ Failed to parse package-lock.json: ${error.message}`); + lockFileError = true; + } +} + +// Final result +console.log("\nšŸŽÆ Result:"); +if (uniqueVersions.length === 1 && errors.length === 0 && !lockFileError) { + console.log(" āœ… Version consistency check passed!"); + process.exit(0); +} else { + console.error(" āŒ Version consistency check failed!"); + if (uniqueVersions.length > 1) { + console.error(" - Package versions are not consistent"); + } + if (errors.length > 0) { + console.error(" - " + errors.join("\n - ")); + } + if (lockFileError) { + console.error(" - package-lock.json is out of sync"); + } + console.error( + '\nšŸ’” Run "npm run update-version " to fix version inconsistencies', + ); + console.error(' or run "npm install" to update package-lock.json'); + process.exit(1); +} diff --git a/scripts/update-version.js b/scripts/update-version.js new file mode 100755 index 00000000..91b69f3b --- /dev/null +++ b/scripts/update-version.js @@ -0,0 +1,104 @@ +#!/usr/bin/env node + +import fs from "fs"; +import path from "path"; +import { execSync } from "child_process"; +import { fileURLToPath } from "url"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +/** + * Updates version across all package.json files in the monorepo + * Usage: node scripts/update-version.js + * Example: node scripts/update-version.js 0.14.2 + */ + +const newVersion = process.argv[2]; + +if (!newVersion) { + console.error("āŒ Please provide a version number"); + console.error("Usage: node scripts/update-version.js "); + console.error("Example: node scripts/update-version.js 0.14.2"); + process.exit(1); +} + +// Validate version format +const versionRegex = /^\d+\.\d+\.\d+(-[\w.]+)?$/; +if (!versionRegex.test(newVersion)) { + console.error( + "āŒ Invalid version format. Please use semantic versioning (e.g., 1.2.3 or 1.2.3-beta.1)", + ); + process.exit(1); +} + +console.log(`šŸ”„ Updating all packages to version ${newVersion}...`); + +// List of package.json files to update +const packagePaths = [ + "package.json", + "client/package.json", + "server/package.json", + "cli/package.json", +]; + +const updatedFiles = []; + +// Update version in each package.json +packagePaths.forEach((packagePath) => { + const fullPath = path.join(__dirname, "..", packagePath); + + if (!fs.existsSync(fullPath)) { + console.warn(`āš ļø Skipping ${packagePath} - file not found`); + return; + } + + try { + const packageJson = JSON.parse(fs.readFileSync(fullPath, "utf8")); + const oldVersion = packageJson.version; + packageJson.version = newVersion; + + // Update workspace dependencies in root package.json + if (packagePath === "package.json" && packageJson.dependencies) { + Object.keys(packageJson.dependencies).forEach((dep) => { + if (dep.startsWith("@modelcontextprotocol/inspector-")) { + packageJson.dependencies[dep] = `^${newVersion}`; + } + }); + } + + fs.writeFileSync(fullPath, JSON.stringify(packageJson, null, 2) + "\n"); + updatedFiles.push(packagePath); + console.log( + `āœ… Updated ${packagePath} from ${oldVersion} to ${newVersion}`, + ); + } catch (error) { + console.error(`āŒ Failed to update ${packagePath}:`, error.message); + process.exit(1); + } +}); + +console.log("\nšŸ“ Summary:"); +console.log(`Updated ${updatedFiles.length} files to version ${newVersion}`); + +// Update package-lock.json +console.log("\nšŸ”’ Updating package-lock.json..."); +try { + execSync("npm install", { stdio: "inherit" }); + console.log("āœ… package-lock.json updated successfully"); +} catch (error) { + console.error("āŒ Failed to update package-lock.json:", error.message); + console.error('Please run "npm install" manually'); + process.exit(1); +} + +console.log("\n✨ Version update complete!"); +console.log("\nNext steps:"); +console.log("1. Review the changes: git diff"); +console.log( + '2. Commit the changes: git add -A && git commit -m "chore: bump version to ' + + newVersion + + '"', +); +console.log("3. Create a git tag: git tag v" + newVersion); +console.log("4. Push changes and tag: git push && git push --tags"); diff --git a/server/package.json b/server/package.json index 9417243d..8f81a9df 100644 --- a/server/package.json +++ b/server/package.json @@ -1,6 +1,6 @@ { "name": "@modelcontextprotocol/inspector-server", - "version": "0.14.2", + "version": "0.14.3", "description": "Server-side application for the Model Context Protocol inspector", "license": "MIT", "author": "Anthropic, PBC (https://anthropic.com)",