A GitHub Action to package MCP servers into distributable bundles and publish them to the mpak registry.
MCP (Model Context Protocol) is a standard for AI assistants to interact with external tools and services. An MCP server exposes tools (like "search files" or "query database") that AI assistants can call.
MCPB is a bundle format (.mcpb files) that packages an MCP server with all its dependencies into a single portable file. This makes MCP servers easy to distribute and install.
mpak is a public registry where you can publish and discover MCP bundles. Think of it like npm, but for MCP servers.
This action automates the entire workflow: build your MCP server into a bundle, attach it to your GitHub release, and register it with mpak so others can find and install it.
Your repository needs:
- A
manifest.jsondescribing your MCP server:
{
"name": "@your-github-org/your-server",
"version": "1.0.0",
"description": "What your server does",
"server": {
"type": "python",
"entry_point": "your_package.server",
"mcp_config": {
"command": "python",
"args": ["-m", "your_package.server"]
}
}
}Note: The
@scopemust match your GitHub organization or username. The registry verifies this via OIDC.
- Your MCP server code with stdio entrypoint:
# At end of server.py
if __name__ == "__main__":
mcp.run() # Required for mpak run / Claude DesktopAdd this to .github/workflows/release.yml:
name: Release
on:
release:
types: [published]
permissions:
contents: write
id-token: write
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: NimbleBrainInc/mcpb-pack@v2When you publish a GitHub release, this will:
- Vendor all dependencies into the bundle
- Build a
.mcpbfile - Upload it to your release
- Register it with mpak.dev
Your server is now discoverable via mpak search and installable via mpak bundle pull.
For pure Python/Node servers without native dependencies:
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: NimbleBrainInc/mcpb-pack@v2For servers with native dependencies (C extensions, Rust bindings, etc.), you need to build on each target platform:
jobs:
build:
strategy:
matrix:
include:
- os: linux
arch: x64
runner: ubuntu-latest
- os: linux
arch: arm64
runner: ubuntu-24.04-arm
- os: darwin
arch: arm64
runner: macos-latest
- os: darwin
arch: x64
runner: macos-15-intel
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: NimbleBrainInc/mcpb-pack@v2
with:
output: "{name}-{version}-${{ matrix.os }}-${{ matrix.arch }}.mcpb"Each job builds and registers its own platform-specific bundle. The registry merges them automatically.
| Platform | Runner Label | Architecture | Notes |
|---|---|---|---|
| Linux x64 | ubuntu-latest |
x64 | Free |
| Linux ARM | ubuntu-24.04-arm |
arm64 | Free |
| macOS ARM | macos-latest / macos-15 |
arm64 (M1) | Free, 3 vCPU, 7 GB |
| macOS Intel | macos-15-intel |
x64 | Free, 4 vCPU, 14 GB |
Paid larger runners (Team/Enterprise plans):
| Platform | Runner Label | Architecture | Notes |
|---|---|---|---|
| macOS Intel | macos-15-large |
x64 | 12 vCPU, 30 GB |
| macOS ARM | macos-15-xlarge |
arm64 (M2) | 5 vCPU + 8 GPU, 14 GB |
Note:
macos-13is retiring December 2025. Usemacos-15-intelfor Intel macOS builds.
For CI validation or private servers:
- uses: NimbleBrainInc/mcpb-pack@v2
with:
upload: false
announce: falseIf you already build your .mcpb bundle separately (e.g., committed to the repo or built in a prior step), you can skip the build and just upload/announce:
- uses: NimbleBrainInc/mcpb-pack@v2
with:
directory: packages/mcp/mcpb
bundle-path: context7.mcpb
build: falseThis is useful when:
- Your bundle is pre-built and committed to the repository
- You have a custom build process
- You want to announce an existing bundle to mpak.dev
The action will compute the SHA256 hash and size from the provided bundle, upload it to the release, and announce it to the registry.
For pure Node.js or Python servers without native dependencies, you can announce a single bundle as cross-platform using any:
- uses: NimbleBrainInc/mcpb-pack@v2
with:
platform-os: any
platform-arch: anyThis registers the bundle as universal, so users on any platform can install it. The registry will serve this bundle when no platform-specific build is available.
To re-announce an existing release (e.g., if the registry was down or you're announcing to a different registry), add workflow_dispatch to your workflow:
on:
release:
types: [published]
workflow_dispatch:
inputs:
build:
description: 'Build bundle'
type: boolean
default: true
announce:
description: 'Announce to registry'
type: boolean
default: true
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: NimbleBrainInc/mcpb-pack@v2
with:
build: ${{ inputs.build }}
announce: ${{ inputs.announce }}Then trigger manually from the Actions tab, checking out the release tag. The action uses github.ref_name as the release tag when not triggered by a release event.
Note: The
uploadinput only works with release events. For manual triggers, upload the bundle to the release manually or usegh release upload.
| Input | Default | Description |
|---|---|---|
directory |
. |
Directory containing manifest.json and server code |
bundle-path |
Path to existing .mcpb bundle (use with build: false) |
|
output |
{name}-{version}.mcpb |
Output filename ({name} and {version} are replaced) |
python-version |
3.13 |
Python version for vendoring (if Python server) |
build |
true |
Whether to build the bundle |
upload |
true |
Whether to upload to the GitHub release |
announce |
true |
Whether to register with mpak.dev |
announce-required |
false |
Whether announce failures should fail the workflow |
announce-url |
https://registry.mpak.dev/v1/bundles/announce |
Registry endpoint (change for self-hosted registries) |
platform-os |
Override detected OS (darwin, linux, win32, any) | |
platform-arch |
Override detected arch (x64, arm64, any) |
| Output | Description |
|---|---|
bundle-path |
Path to the generated .mcpb file |
bundle-size |
Size of the bundle in bytes |
bundle-sha256 |
SHA256 hash for integrity checks |
announced |
Whether registration succeeded |
server-json-found |
Whether a server.json was found and uploaded |
permissions:
contents: write # Required to upload to releases
id-token: write # Required for OIDC authentication with registryThe action:
- Detects your server type from
manifest.json(Python or Node.js) - Vendors all dependencies into the bundle (Python:
deps/, Node:node_modules/) - Packages everything into a
.mcpbfile using the mcpb CLI - If a
server.jsonis present, validates it and syncs the version frommanifest.json
When you announce to mpak.dev:
- The action uploads the
.mcpbbundle (andserver.json, if present) to the GitHub release - The action requests an OIDC token from GitHub
- This token cryptographically proves the bundle came from your repository
- The registry verifies the token, registers your bundle, and fetches any
server.jsonfrom the release - No API keys or secrets needed
Each platform build announces its own artifact. The registry tracks all artifacts for a version, so users can install the right bundle for their system.
To build without publishing to the public registry:
- uses: NimbleBrainInc/mcpb-pack@v2
with:
announce: falseYou might want this if:
- Your server is private or internal
- You're using a self-hosted registry
- You want to test before publishing
- You distribute through other channels
You can include a server.json file in your repository to make your MCP server discoverable through the MCP Registry. This file provides metadata that the registry uses to list your server in its discovery API (/v0.1/servers).
Without server.json, your bundle is published to mpak and installable via mpak bundle pull, but it won't appear in MCP Registry discovery endpoints. Adding server.json makes your server findable by AI clients and tools that query the registry.
Only name and description are required:
{
"name": "@your-org/your-server",
"description": "What your server does"
}The version is automatically synced from your manifest.json at build time.
{
"name": "@your-org/your-server",
"description": "What your server does",
"title": "Human-Friendly Server Name",
"repository": {
"url": "https://github.com/your-org/your-repo",
"source": "https://github.com/your-org/your-repo"
}
}| Field | Required | Description |
|---|---|---|
name |
Yes | Package name (must match manifest.json) |
description |
Yes | Short description of the server |
version |
No | Synced automatically from manifest.json |
title |
No | Human-friendly display name |
repository |
No | Source repository URLs |
packages |
No | Populated automatically by the registry with bundle download info |
- The action validates
server.json(checks valid JSON, required fields) - Syncs the
versionfield frommanifest.jsonintoserver.json - Uploads
server.jsonas a release asset alongside the.mcpbbundle - When the registry processes the announce, it fetches
server.jsonfrom the release and stores the metadata
The packages[] array in the registry response is populated automatically from announced bundles. You don't need to include it in your server.json.
| Runtime | Detected via | Dependency vendoring |
|---|---|---|
| Python | server.type: "python" |
uv pip install --target |
| Node.js | server.type: "node" |
npm install --omit=dev |
| Binary | server.type: "binary" |
None (you build the binary) |
For compiled languages, build your binary before running mcpb-pack:
jobs:
build:
strategy:
matrix:
include:
- os: linux
arch: x64
runner: ubuntu-latest
- os: linux
arch: arm64
runner: ubuntu-24.04-arm
- os: darwin
arch: arm64
runner: macos-latest
- os: darwin
arch: x64
runner: macos-15-intel
runs-on: ${{ matrix.runner }}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: "1.22"
- name: Build binary
run: |
mkdir -p bin
go build -o bin/server ./cmd/server
- uses: NimbleBrainInc/mcpb-pack@v2
with:
output: "{name}-{version}-${{ matrix.os }}-${{ matrix.arch }}.mcpb"The manifest.json for binary servers:
{
"name": "@your-org/your-server",
"version": "1.0.0",
"server": {
"type": "binary",
"entry_point": "bin/server",
"mcp_config": {
"command": "${__dirname}/bin/server",
"args": []
}
}
}{
"name": "@your-org/your-server",
"version": "1.0.0",
"server": {
"type": "node",
"entry_point": "dist/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/dist/index.js"]
}
}
}- mcp-echo - Simple Python MCP server with multi-platform builds
- MCP Documentation - Model Context Protocol specification
- mpak Registry - Browse and search published MCP bundles
- mcpb CLI - Command-line tool for building bundles locally
MIT