Skip to main content

Packaging

How to wrap your MCP server for distribution. Packaging is about making it easy for someone else to install your server without fighting their environment.

Three distribution shapes

ShapeHow users installGood for
npm packagenpm install + run via nodeJavaScript/TypeScript servers
Python packagepip install + run via pythonPython servers
Standalone binaryDownload + runServers with heavy dependencies or non-JS/Python languages
Docker imagePull + runServers with complex runtime requirements

Most MCP servers are npm or Python packages. Binaries and Docker are for special cases.

npm package layout

my-mcp-server/
├── package.json
├── README.md
├── LICENSE
├── src/
│ └── server.js
├── bin/
│ └── my-mcp-server ← executable shim
└── test/
└── server.test.js

package.json

{
"name": "my-mcp-server",
"version": "0.1.0",
"description": "MCP server for... (one-sentence description)",
"type": "module",
"main": "src/server.js",
"bin": {
"my-mcp-server": "bin/my-mcp-server"
},
"scripts": {
"start": "node src/server.js",
"test": "node --test test/"
},
"keywords": ["mcp", "codebolt", "your-domain"],
"license": "MIT",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.0.0"
},
"files": [
"src/",
"bin/",
"README.md",
"LICENSE"
]
}

Key fields:

  • bin — provides an executable users can invoke by name after npm install -g. The shim in bin/my-mcp-server is a 2-line script: #!/usr/bin/env node\nimport('../src/server.js');.
  • files — what gets published. Exclude test/, .github/, source maps, etc.
  • type: "module" — use ESM. MCP SDK is ESM-first.

README requirements

Every published MCP server needs a README that covers:

  1. What the server does. One sentence.
  2. Which tools it provides. List them with a one-line description each.
  3. How to install. npm install -g my-mcp-server.
  4. How to configure. Env vars, config files, required credentials.
  5. How to add to Codebolt. A copy-pastable .codebolt/mcp-servers.yaml snippet.
  6. Security notes. What access does this server need? What's the blast radius?
  7. License.

This is not optional. An MCP server without a README is unusable by anyone but the author.

Python package layout

my_mcp_server/
├── pyproject.toml
├── README.md
├── LICENSE
├── src/
│ └── my_mcp_server/
│ ├── __init__.py
│ └── server.py
└── tests/
└── test_server.py

pyproject.toml

[project]
name = "my-mcp-server"
version = "0.1.0"
description = "MCP server for..."
readme = "README.md"
requires-python = ">=3.10"
license = { text = "MIT" }
dependencies = [
"mcp>=1.0.0",
]

[project.scripts]
my-mcp-server = "my_mcp_server.server:main"

[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"

Same idea as npm: the [project.scripts] entry gives users an executable after pip install.

Configuration

Servers should be configurable without editing code. Use environment variables for everything config-able:

const config = {
apiKey: process.env.MY_API_KEY,
endpoint: process.env.MY_ENDPOINT ?? "https://api.example.com",
timeout: parseInt(process.env.MY_TIMEOUT_MS ?? "10000"),
};

Document the env vars in the README:

Environment variables:
MY_API_KEY Required. API key for the example service.
MY_ENDPOINT Optional. Override the default API endpoint.
MY_TIMEOUT_MS Optional. Per-request timeout in ms (default 10000).

The user's .codebolt/mcp-servers.yaml sets them:

servers:
my-server:
command: my-mcp-server
env:
MY_API_KEY: ${MY_API_KEY} # reads from shell env
MY_ENDPOINT: https://staging.example.com

Never accept secrets as command-line arguments. They'd show up in process listings and logs.

Capability bundles vs plain MCP servers

Plain MCP servers are standalone executables. Capability bundles are Codebolt-specific packages that can include MCP servers alongside prompts, hooks, and UI panels.

Plain MCP serverCapability bundle
Works in any MCP hostCodebolt-specific
Just toolsTools + prompts + hooks + UI
Simple to authorMore surface area
Good defaultUse when you need the extras

If your server only provides tools, ship it as a plain MCP server. If you need to bundle tools with prompts, agent presets, or UI panels that only make sense together, use a capability bundle.

Dependencies

Keep dependencies minimal. Every dep is a vulnerability surface, a version conflict waiting to happen, and a distribution size multiplier.

  • Lock your dependencies. Use package-lock.json / pyproject.toml + uv.lock / equivalent.
  • Pin major versions of the MCP SDK. Breaking changes happen on major version bumps.
  • Prefer the standard library for anything non-critical. Don't pull in a fifteen-megabyte dep for something you could do in ten lines.
  • Audit dependencies before publishing. npm audit / pip-audit. Fix critical/high vulnerabilities.

Binary distribution

If your server is in Go, Rust, or another language, or has heavy native dependencies:

  1. Build for each target platform (linux-x64, linux-arm64, darwin-x64, darwin-arm64, windows-x64).
  2. Package each as a separate archive (.tar.gz or .zip).
  3. Ship a manifest (JSON) listing each binary with its platform triple and SHA-256.
  4. Users download the appropriate binary for their OS and add it to mcp-servers.yaml with an absolute path.

Codebolt may offer a helper for this in future ("install from URL"), but today binary distribution requires users to download and extract manually. Favor npm/pip when possible.

Docker

For servers with heavy or awkward runtime requirements:

FROM node:20-slim
WORKDIR /app
COPY package*.json ./
RUN npm install --production
COPY src ./src
USER nobody
ENTRYPOINT ["node", "src/server.js"]

Configure in mcp-servers.yaml:

servers:
my-docker-server:
command: docker
args:
- run
- --rm
- -i
- --env-file=.env
- my-mcp-image:latest

The -i is required for stdin (stdio transport). Docker adds overhead; use only when necessary.

Versioning

Follow semver strictly:

  • Patch — bug fixes, no tool changes.
  • Minor — new tools, optional fields added to existing tool schemas.
  • Major — breaking changes to tool schemas, removed tools, incompatible behaviour.

Keep a CHANGELOG.md. Users deciding whether to upgrade will read it.

Security checklist before publishing

  • No hard-coded secrets anywhere in the source.
  • No real API keys in examples or tests.
  • Input validation on every tool argument.
  • No arbitrary code execution from tool inputs (no eval, no exec with user strings).
  • File path arguments are validated against directory traversal.
  • Shell command arguments are properly escaped or avoided.
  • Network requests have timeouts.
  • Dependencies are audited.
  • README documents the server's blast radius.

MCP servers run with the user's credentials. Don't abuse that trust.

See also