I just published @atlasforge/agentforge to npm — a CLI that scaffolds MCP server and AI agent projects from 15 templates. Here's what broke during publishing and how I fixed it.
The name collision
My first npm publish attempt failed with:
403 Forbidden - Package name too similar to existing package agent-forge
npm has a name similarity check that blocks packages that are "too close" to existing ones. agentforge vs agent-forge — same words, different delimiter. npm suggested scoping it: @atlasforge/agentforge.
This is fine. Scoped packages are the modern way. The downside is users type more: npx @atlasforge/agentforge init instead of npx agentforge init. But it's unambiguous and won't collide.
The 2FA wall
The second attempt failed with:
403 Forbidden - Two-factor authentication or granular access token with bypass 2fa enabled is required to publish packages.
npm requires either:
- 2FA enabled on your account (TOTP app), or
- A granular access token with "bypass 2FA" enabled
I used option 2. The token creation flow on npmjs.com lets you create a granular access token with:
- Read and write access to all packages
- Bypass 2FA checkbox enabled
- 7-day expiration (max for write tokens)
Then configure npm to use it:
npm config set //registry.npmjs.org/:_authToken npm_xxxxx
The key insight: npm login via CLI authenticates you for reads, but publishing needs either 2FA or the token. The token is separate from your login session.
The template path resolution bug
The CLI scaffolds projects by copying template files. When running from the repo, templates are at ../../templates/. When installed from npm, they're at ./templates/ inside the package.
The original code used:
const templatesDir = new URL("../../templates/", import.meta.url);
This works in the repo but breaks when installed from npm because the relative path is different. The fix:
const packageRoot = path.resolve(fileURLToPath(import.meta.url), "../..");
const templatesDir = path.join(packageRoot, "templates");
This resolves relative to the package root, which is the same regardless of where the package is installed.
The prepublishOnly gotcha
The templates aren't in the npm package by default — they're in the repo root, not in packages/cli/. I needed to copy them into the package before publishing.
The prepublishOnly script in package.json:
"prepublishOnly": "node scripts/copy-templates.js"
This runs automatically before npm publish. The script copies 237 template files from ../../templates/ to ./templates/. The files array in package.json includes "templates" so npm bundles them.
The gotcha: prepublishOnly doesn't run on npm pack or npm install. If you test with npm pack --dry-run without running the copy script first, the templates won't be in the tarball. Always test with npm publish --dry-run which does run prepublishOnly.
The .gitignore interaction
I added packages/cli/templates/ to .gitignore so the copied templates don't get committed. But this means CI needs to run the copy script before testing the CLI:
- name: Test CLI commands
working-directory: packages/cli
run: |
node scripts/copy-templates.js
node src/index.js init test-project --template ts-hello-world
test -f test-project/package.json
Without this, CI passes locally (where templates exist in the repo root) but fails on GitHub Actions (where the copy script hasn't run).
The package-lock.json bloat
Each TypeScript template includes a package-lock.json (~60KB). With 10 TypeScript templates, that's 600KB of lockfiles in the npm package. The total package size is 280KB compressed, 1.2MB uncompressed.
I considered stripping lockfiles from the published package. But users need them — without a lockfile, npm install resolves different versions each time, which breaks reproducibility. The 280KB compressed size is acceptable.
What works now
npx @atlasforge/agentforge init my-server --template ts-hello-world
This downloads the package, runs the CLI, scaffolds a working MCP server, and tells you the next steps. 30 seconds from zero to running server.
The templates include TypeScript MCP servers, Python MCP servers, AI agent patterns, Docker configs, and client integration files for Claude Desktop, Cursor, and Windsurf.
MIT licensed. The package is at https://www.npmjs.com/package/@atlasforge/agentforge and the source is at https://github.com/thenextfreud/agentforge.
Top comments (0)