---
title: "Publish to npm, bin field, files allowlist, npx -y | Recipe — StudioMeyer Academy"
description: "Make your server installable via 'claude mcp add foo -s user -- npx -y your-package'. The four package.json fields that decide whether it works for users."
canonical: "https://studiomeyer.academy/en/recipes/9.1-npm-publish"
language: en
markdown_versions: ["https://studiomeyer.academy/en/recipes/9.1-npm-publish.md"]
publisher: "StudioMeyer Academy, https://studiomeyer.academy (llms.txt: https://studiomeyer.academy/llms.txt)"
---

[← Alle Recipes](https://studiomeyer.academy/recipes)

Phase 9 · Distribution·7 steps

# Publish to npm, bin field, files allowlist, npx -y

Make your server installable via 'claude mcp add foo -s user -- npx -y your-package'. The four package.json fields that decide whether it works for users.

7 steps0%

Du liest ohne Account. Mit [Login](https://studiomeyer.academy/login?next=/recipes/9.1-npm-publish) speichern wir Step-Fortschritt + Notes.

# Publish to npm, bin field, files allowlist, npx -y

`npx -y your-package` is the lowest-friction install path for an MCP server. User runs `claude mcp add foo -- npx -y your-package`, npm fetches the latest version, runs your binary, done. For that to work, four `package.json` fields need to be exactly right. Get any of them wrong and users hit confusing errors.

### Step 1: The four fields that matter

```json
{
  "name": "your-mcp-server",
  "version": "1.0.0",
  "description": "What your MCP server does in one sentence.",
  "type": "module",
  "main": "dist/server.js",
  "bin": {
    "your-mcp-server": "dist/server.js"
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "engines": {
    "node": ">=18"
  },
  "keywords": [
    "mcp",
    "model-context-protocol",
    "claude",
    "claude-code",
    "claude-desktop",
    "cursor",
    "your-domain"
  ]
}
```

Each field's job:

- **`bin`**, npm symlinks your binary into `node_modules/.bin/`. `npx -y your-package` then finds it. Without this, `npx` fails with `command not found`.
- **`files`**, allowlist of what gets included in the published tarball. **Without this**, npm includes everything not matched by `.npmignore`, your `.git`, `node_modules`, `.env`, `tests/` all get published. Allowlist > denylist.
- **`type: "module"`**, tells Node to use ESM. Required for top-level `await` and modern imports. Without this, your `import` statements fail at runtime.
- **`engines.node`**, npm warns users on incompatible versions. MCP SDK needs Node 18+; some features want 20+.

### Step 2: The shebang (don't forget)

Your `dist/server.js` must start with:

```javascript
#!/usr/bin/env node
```

Without it, `npx` runs your file via the wrong shell and errors with `unexpected token import`. With it, `npx` knows to use Node.

If your build is `tsc`, the shebang must be in your **source** `src/server.ts`. TypeScript preserves it through compilation:

```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
// ...
```

After build, verify:

```bash
head -1 dist/server.js
# → #!/usr/bin/env node
```

### Step 3: Make the binary executable

```bash
chmod +x dist/server.js
```

`tsc` doesn't preserve the executable bit. Add this to your build step:

```json
"scripts": {
  "build": "tsc && chmod +x dist/server.js"
}
```

`npm publish` includes the executable bit in the tarball, so users get it executable when they install.

### Step 4: Pre-publish dry-run

```bash
npm pack --dry-run
```

Output shows exactly what will be published:

```
npm notice 📦  your-mcp-server@1.0.0
npm notice Tarball Contents
npm notice 1.2kB README.md
npm notice 543B  LICENSE
npm notice 12kB  dist/server.js
npm notice 4kB   dist/lib/logger.js
npm notice ...
npm notice Tarball Details
npm notice name:          your-mcp-server
npm notice version:       1.0.0
npm notice filename:      your-mcp-server-1.0.0.tgz
npm notice package size:  18.3 kB
npm notice unpacked size: 92.1 kB
npm notice total files:   23
```

What to check:

- **No `.env`, `.git`, `tests/`, `node_modules`** in the file list. If they show up, your `files` allowlist is wrong.
- **`dist/server.js` is included.**
- **Total size \< 1MB** for a typical MCP server. If it's bigger, you probably accidentally included `node_modules` or build artifacts.

### Step 5: Publish

First publish:

```bash
npm login
npm publish --access public  # only needed for scoped packages (@your-org/foo)
```

For scoped packages, `--access public` is required on first publish (default is private).

Subsequent publishes:

```bash
npm version patch  # 1.0.0 → 1.0.1
npm publish
```

`npm version patch` bumps the version, creates a git tag, and commits. Push the tag separately:

```bash
git push origin main --tags
```

### Step 6: Confirm the install works

After publish, test the install path your users will use:

```bash
# Fresh terminal, no project context
npx -y your-mcp-server@latest --version
# → 1.0.0

# In Claude Code:
claude mcp add my-test -s user -- npx -y your-mcp-server
claude mcp list
# → my-test  ✓ stdio  npx -y your-mcp-server
```

Open Claude Code, send a message, the server should start and respond.

If it doesn't work in the fresh-install case, your real users won't be able to install it either.

### Step 7: Verify

Run `academy_validate_step`. The validator checks `npm view your-package version` returns a version (i.e., the package exists on npm registry).

For the package quality, also run:

```bash
npm view your-mcp-server
# Confirms metadata: description, keywords, dependencies, repository link
```

If `repository` isn't set, npm shows an unhelpful default. Add to `package.json`:

```json
"repository": {
  "type": "git",
  "url": "git+https://github.com/your-org/your-mcp-server.git"
}
```

## Common traps

- **Missing shebang**, `npx` errors with `unexpected token import`. Add `#!/usr/bin/env node` at the top of `src/server.ts`.
- **Missing `chmod +x`**, `npx` errors with `permission denied`. Add `chmod +x dist/server.js` to build.
- **`bin` path doesn't match `files` allowlist**, `npx` errors with `cannot find module dist/server.js`. The path in `bin` must point at a file included in `files`.
- **`type: "module"` missing**, runtime `import` errors. Always include for ESM projects.
- **`@your-org/your-server` published as private by accident**, `npm publish` without `--access public` defaults to private for scoped packages. Use `--access public` on first publish.
- **Forgetting to bump version**, `npm publish` errors with `cannot publish over existing version`. `npm version patch` bumps + tags + commits in one step.
- **Including dev dependencies in `dependencies`**, bloats install size, slows `npx -y`. Move to `devDependencies`.
- **Including `dist/` in git**, pollutes diffs. Add `dist/` to `.gitignore` and rely on `npm publish` to include it via `files`.

## What good looks like

`npm publish` from a clean checkout finishes in 30 seconds. `npx -y your-mcp-server@latest --version` from a fresh terminal prints the right version. `claude mcp add my-test -s user -- npx -y your-mcp-server` adds the server, `claude mcp list` shows it, sending a message in Claude triggers a tool call. Users can copy-paste your README install snippet without reading more than 2 lines.

That's the bar. Anything more friction than `npx -y your-package` and you'll lose half your potential installs to "I'll set this up later", which means never.

Client check · run on your machine

cat package.json 2>/dev/null | python3 -c "import json,sys; p=json.load(sys.stdin); print(\\"name:\\", p.get(\\"name\\")); print(\\"version:\\", p.get(\\"version\\")); print(\\"private:\\", p.get(\\"private\\"))" 2>/dev/null || echo "no package.json"

**Expect:** name + version are set, private is false (or omitted).

**If stuck:** Remove \`"private": true\`, set a unique name, then \`npm publish --access public\`.

[← Blue-green deploys, zero-downt](https://studiomeyer.academy/recipes/8.5-blue-green) [GitHub OSS, trust signals that →](https://studiomeyer.academy/recipes/9.2-github-oss)

Phase 9

npm publish, GitHub OSS, MCP marketplaces, trusted publishing

[Alle Phasen →](https://studiomeyer.academy/recipes)

Concepts

[MCPize](https://studiomeyer.academy/concepts/mcpize)

## Sitemap

Public pages of this site as Markdown: [sitemap](https://studiomeyer.academy/sitemap.md). Curated entry point: [llms.txt](https://studiomeyer.academy/llms.txt), full text: [llms-full.txt](https://studiomeyer.academy/llms-full.txt).
