DEV Community

Atlas Whoff
Atlas Whoff

Posted on

Ship Your MCP Server to npm in 30 Minutes: The Complete 2026 Guide

MCP (Model Context Protocol) servers are how you extend Claude and other AI assistants with custom tools, data sources, and APIs. Publishing one to npm means anyone can add your server to their Claude Desktop config in one line. Here's the complete guide — from scaffold to published package.

What You're Building

An MCP server is a process that speaks the MCP protocol over stdio (or HTTP with SSE for remote servers). Claude connects to it and can call your tools like function calls. A published npm package means users install it with:

{
  "mcpServers": {
    "your-server": {
      "command": "npx",
      "args": ["-y", "your-mcp-server-name"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

No global installs, no cloning repos — npx -y handles everything.

Scaffold

mkdir my-mcp-server && cd my-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node tsx
Enter fullscreen mode Exit fullscreen mode

tsconfig.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "Node16",
    "moduleResolution": "Node16",
    "outDir": "./dist",
    "rootDir": "./src",
    "strict": true,
    "esModuleInterop": true
  },
  "include": ["src"]
}
Enter fullscreen mode Exit fullscreen mode

Write the Server

// src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js'
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
import {
  CallToolRequestSchema,
  ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js'
import { z } from 'zod'

const server = new Server(
  { name: 'my-mcp-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
)

// Declare your tools
server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'get_weather',
      description: 'Get current weather for a city',
      inputSchema: {
        type: 'object',
        properties: {
          city: { type: 'string', description: 'City name' },
        },
        required: ['city'],
      },
    },
  ],
}))

// Handle tool calls
const GetWeatherInput = z.object({ city: z.string() })

server.setRequestHandler(CallToolRequestSchema, async (request) => {
  if (request.params.name === 'get_weather') {
    const { city } = GetWeatherInput.parse(request.params.arguments)

    // Your actual implementation
    const response = await fetch(
      `https://wttr.in/${encodeURIComponent(city)}?format=j1`
    )
    const data = await response.json()
    const temp = data.current_condition[0].temp_F
    const desc = data.current_condition[0].weatherDesc[0].value

    return {
      content: [{
        type: 'text',
        text: `${city}: ${temp}°F, ${desc}`,
      }],
    }
  }

  throw new Error(`Unknown tool: ${request.params.name}`)
})

// Start
const transport = new StdioServerTransport()
await server.connect(transport)
Enter fullscreen mode Exit fullscreen mode

Package.json Setup for npm

This is where most guides fall short. The bin field is critical:

{
  "name": "my-mcp-server",
  "version": "1.0.0",
  "description": "MCP server for weather data",
  "main": "dist/index.js",
  "bin": {
    "my-mcp-server": "./dist/index.js"
  },
  "scripts": {
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "prepublishOnly": "npm run build"
  },
  "files": ["dist"],
  "engines": { "node": ">=18" },
  "keywords": ["mcp", "claude", "ai", "model-context-protocol"]
}
Enter fullscreen mode Exit fullscreen mode

Then add the shebang to your built file — but do it at the source level so TypeScript doesn't complain:

// src/index.ts — first line
#!/usr/bin/env node
Enter fullscreen mode Exit fullscreen mode

TypeScript will error on the shebang. Fix: add it to the compiled output instead via a build step:

"build": "tsc && echo '#!/usr/bin/env node' | cat - dist/index.js > dist/index.tmp.js && mv dist/index.tmp.js dist/index.js && chmod +x dist/index.js"
Enter fullscreen mode Exit fullscreen mode

Test Locally Before Publishing

npm run build

# Test via Claude Desktop
# Add to ~/Library/Application\ Support/Claude/claude_desktop_config.json:
{
  "mcpServers": {
    "my-mcp-server-dev": {
      "command": "node",
      "args": ["/absolute/path/to/my-mcp-server/dist/index.js"]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Restart Claude Desktop. You should see your tool appear when Claude starts a new conversation.

Alternatively, test with the MCP Inspector:

npx @modelcontextprotocol/inspector node dist/index.js
Enter fullscreen mode Exit fullscreen mode

This opens a browser UI where you can call tools directly without Claude.

Publish to npm

npm login
npm publish --access public
Enter fullscreen mode Exit fullscreen mode

For scoped packages (@yourname/mcp-server-name):

npm publish --access public  # scoped packages default to private
Enter fullscreen mode Exit fullscreen mode

After publishing, test the npx install path:

npx -y my-mcp-server
# Should start the server and wait for MCP connections
Ctrl+C
Enter fullscreen mode Exit fullscreen mode

Handling API Keys

Most useful MCP servers need credentials. The convention is environment variables passed through the config:

{
  "mcpServers": {
    "my-server": {
      "command": "npx",
      "args": ["-y", "my-mcp-server"],
      "env": {
        "API_KEY": "user-api-key-here"
      }
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

In your server:

const apiKey = process.env.API_KEY
if (!apiKey) {
  console.error('API_KEY environment variable required')
  process.exit(1)
}
Enter fullscreen mode Exit fullscreen mode

Fail fast with a clear error message — users copy the config template from your README and often miss the env vars.

README Template

The README is what shows up on npmjs.com and what users copy from. Include:

  1. One-line description of what the server does
  2. The exact JSON snippet to add to Claude Desktop config
  3. List of tools with brief descriptions
  4. Any required env vars

Users should be able to go from README to working in under 2 minutes.

What to Build

High-value MCP servers that don't exist yet (as of April 2026):

  • Company-internal tools (Notion, Linear, Jira) with auth already handled
  • Domain-specific APIs (weather, finance, maps) with smart formatting
  • Local file system helpers scoped to specific directories
  • Database introspection servers that let Claude explore schema and run safe queries

The MCP ecosystem is still early. A good server in an underserved niche gets picked up quickly.


Already built an MCP server? Check out the MCP Security Scanner — automated vulnerability detection for MCP server codebases. Also: Workflow Automator MCP and Trading Signals MCP are live examples of production MCP servers you can reference.

Top comments (0)