Guide · AWS Developer Tools · npm

MCP Server npm Publishing via CodeArtifact — .npmrc, Scoped Packages, CI Login

Publishing an MCP server as an npm package through CodeArtifact lets teams version, share, and consume the server SDK across multiple projects without going through the public npm registry. Three patterns account for most publish failures: the .npmrc token line must reference the registry host without the https: scheme — the npm auth token key format is //<host>/<path>/:_authToken=<token>, not https://<host>...; teams that copy their CodeArtifact endpoint URL verbatim see "unable to authenticate, need: Basic realm" errors because the scheme prefix breaks npm's auth lookup; scoped packages (@myorg/mcp-server-name) must map their scope to the CodeArtifact registry in .npmrc via a @myorg:registry=https://... line — without the scope mapping, npm publish sends the package to the public registry regardless of any global registry setting; and CodeArtifact package versions are immutable once published — attempting to republish the same version number produces "PackageVersionAlreadyExists" rather than overwriting, so CI pipelines must bump the version before every publish.

TL;DR

In .npmrc: set @myorg:registry=https://<endpoint>/ for scope routing and //<endpoint-without-scheme>/:_authToken=<token> for auth (no https: prefix on the key). Run aws codeartifact login --tool npm --domain <domain> --repository <repo> in CI pre_build to write a fresh 12-hour token. Bump the patch version before every automated publish — CodeArtifact rejects duplicate versions.

package.json setup for CodeArtifact publishing

// package.json for an MCP server npm package
{
  "name": "@myorg/mcp-server-analytics",
  "version": "1.0.0",
  "description": "Analytics MCP server — exposes query_metrics and list_dashboards tools",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "files": [
    "dist/",
    "README.md"
  ],
  "scripts": {
    "build": "tsc --project tsconfig.json",
    "prepublishOnly": "npm run build"  // Ensure built before publish
  },
  "publishConfig": {
    // Scope-level registry — npm uses this when publishing @myorg/* packages
    // This overrides the global registry for publish only
    "registry": "https://myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/"
  },
  "peerDependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}

The publishConfig.registry field tells npm which registry to use when publishing this specific package. Combined with the @myorg:registry scope mapping in .npmrc, this ensures that both npm publish and npm install @myorg/mcp-server-analytics hit CodeArtifact rather than the public registry.

.npmrc configuration

# .npmrc — project-level (committed to the repo)
# Scope routing: all @myorg/* packages resolve from CodeArtifact
@myorg:registry=https://myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/

# Auth token for the registry — note: key uses // prefix WITHOUT https: scheme
# This line is written by `aws codeartifact login` or set manually
//myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/:_authToken=${CODEARTIFACT_AUTH_TOKEN}

# Keep the public npm registry as the fallback for non-scoped packages
# (Do NOT set `registry=` at the top level if you still want public packages)

Environment variable substitution: npm reads ${CODEARTIFACT_AUTH_TOKEN} in .npmrc as an environment variable reference when the value contains ${...} syntax. This pattern is safe to commit — the token itself never lands in source control, only the variable reference does. Set CODEARTIFACT_AUTH_TOKEN as a CodeBuild environment variable (from SSM or freshly fetched in pre_build).

# Alternative: use aws codeartifact login to write credentials automatically
# This rewrites the token in ~/.npmrc (user-level) rather than project .npmrc
aws codeartifact login \
  --tool npm \
  --domain myorg \
  --domain-owner 123456789012 \
  --repository mcp-servers \
  --region us-east-1

# Verify: print the current registry for the @myorg scope
npm config get @myorg:registry
# → https://myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/

CI buildspec.yml: full publish workflow

# buildspec.yml — publish MCP server npm package to CodeArtifact
version: 0.2

env:
  variables:
    CODEARTIFACT_DOMAIN: myorg
    CODEARTIFACT_REPO: mcp-servers
    AWS_DEFAULT_REGION: us-east-1
  parameter-store:
    CODEARTIFACT_ACCOUNT_ID: /codebuild/codeartifact-account-id

phases:
  pre_build:
    commands:
      # Refresh the CodeArtifact token — valid for 12 hours; always refresh in pre_build
      - echo "Authenticating to CodeArtifact..."
      - aws codeartifact login
          --tool npm
          --domain $CODEARTIFACT_DOMAIN
          --domain-owner $CODEARTIFACT_ACCOUNT_ID
          --repository $CODEARTIFACT_REPO
          --region $AWS_DEFAULT_REGION

      # Install dependencies using CodeArtifact as the registry for @myorg/* packages
      - npm ci

  build:
    commands:
      - echo "Running tests..."
      - npm test

      - echo "Building package..."
      - npm run build

  post_build:
    commands:
      # Gate publish on successful build + test
      - |
        if [ "$CODEBUILD_BUILD_SUCCEEDING" != "1" ]; then
          echo "Build failed — skipping publish"
          exit 0
        fi

      # Bump patch version automatically on every merge to main
      # This prevents "PackageVersionAlreadyExists" errors
      - npm version patch --no-git-tag-version

      - echo "Publishing to CodeArtifact..."
      - npm publish

      # Output the published version for downstream pipeline stages
      - echo "Published $(node -p "require('./package.json').version")"

Version strategy: npm version patch --no-git-tag-version bumps package.json without creating a git tag (CodeBuild does not have git push access). For semantic versioning from commit messages, consider using standard-version or semantic-release instead of the plain npm version command.

Installing CodeArtifact packages in downstream services

# Dockerfile for an ECS service that consumes @myorg/mcp-server-analytics
FROM node:22-alpine AS deps

WORKDIR /app

# Copy package files
COPY package.json package-lock.json ./

# The .npmrc is NOT committed — credentials are injected at build time
# Build arg carries the CodeArtifact token (set by CI build pipeline)
ARG CODEARTIFACT_AUTH_TOKEN
RUN echo "//myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/:_authToken=${CODEARTIFACT_AUTH_TOKEN}" >> .npmrc \
 && echo "@myorg:registry=https://myorg-123456789012.d.codeartifact.us-east-1.amazonaws.com/npm/mcp-servers/" >> .npmrc \
 && npm ci \
 && rm -f .npmrc  # Remove credentials before copying to final stage

FROM node:22-alpine AS runner
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
CMD ["node", "dist/index.js"]

Security note: The ARG CODEARTIFACT_AUTH_TOKEN value is embedded in the Docker build layer cache if BuildKit is not used. Use --secret mounts with BuildKit (RUN --mount=type=secret,id=npmrc,target=.npmrc npm ci) in sensitive environments to keep the token out of the image layer history.

Listing, describing, and deleting package versions

# List all versions of a package in CodeArtifact
aws codeartifact list-package-versions \
  --domain myorg \
  --domain-owner 123456789012 \
  --repository mcp-servers \
  --format npm \
  --package "@myorg/mcp-server-analytics"

# Describe a specific version (status, metadata, tags)
aws codeartifact describe-package-version \
  --domain myorg \
  --domain-owner 123456789012 \
  --repository mcp-servers \
  --format npm \
  --package "@myorg/mcp-server-analytics" \
  --package-version 1.2.3

# Delete a specific version (required before republishing same version number)
aws codeartifact delete-package-versions \
  --domain myorg \
  --domain-owner 123456789012 \
  --repository mcp-servers \
  --format npm \
  --package "@myorg/mcp-server-analytics" \
  --versions 1.2.3

# Copy a package version to another repository (e.g., promote dev → prod)
aws codeartifact copy-package-versions \
  --source-domain myorg \
  --source-domain-owner 123456789012 \
  --source-repository mcp-servers-dev \
  --destination-repository mcp-servers-prod \
  --format npm \
  --package "@myorg/mcp-server-analytics" \
  --versions 1.2.3

Common failure modes

SymptomCauseFix
"npm ERR! 404 Not Found — @myorg/mcp-server-analytics" (install)The @myorg:registry scope mapping is missing from .npmrc; npm looks in the public registryAdd @myorg:registry=https://<endpoint>/ to .npmrc before running npm install
"unable to authenticate, need: Basic realm" on npm publishThe _authToken key in .npmrc uses https://<host>/ instead of //<host>/ (with scheme)Remove the https: scheme prefix — the key must start with //, e.g., //myorg-123.d.codeartifact.../npm/mcp-servers/:_authToken=...
"PackageVersionAlreadyExists" on publishThe version in package.json was already published to this repository; CodeArtifact is immutable per versionBump the version with npm version patch before publishing; never reuse a version number
npm install pulls old cached version from upstreamThe upstream proxy cached the version at first-fetch and won't re-fetch even if the upstream was updatedDelete the cached version in CodeArtifact with delete-package-versions; the next install refetches from the public registry
"npm publish" sends the package to the public registrypublishConfig.registry in package.json is missing or the token expiredSet publishConfig.registry to the CodeArtifact endpoint; refresh the token with aws codeartifact login before publishing
Docker build fails: "401 Unauthorized" fetching @myorg packageThe Dockerfile does not inject the CodeArtifact token — npm ci sees no auth for the CodeArtifact registryPass the token as a build arg and write it to .npmrc before npm ci; remove .npmrc in the same RUN layer or use BuildKit secrets
"403 Forbidden" on npm install for scoped packageThe IAM role lacks codeartifact:ReadFromRepository or the repository resource policy blocks the principalAdd codeartifact:ReadFromRepository to the role and verify the repository resource policy allows the caller's account