build-mcpb

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Build an MCPB (Bundled Local MCP Server)

构建MCPB(捆绑式本地MCP服务器)

MCPB is a local MCP server packaged with its runtime. The user installs one file; it runs without needing Node, Python, or any toolchain on their machine. It's the sanctioned way to distribute local MCP servers.
Use MCPB when the server must run on the user's machine — reading local files, driving a desktop app, talking to localhost services, OS-level APIs. If your server only hits cloud APIs, you almost certainly want a remote HTTP server instead (see
build-mcp-server
). Don't pay the MCPB packaging tax for something that could be a URL.

MCPB是捆绑了运行时环境的本地MCP服务器。用户只需安装一个文件,无需在本地部署Node、Python或任何工具链即可运行。它是官方认可的本地MCP服务器分发方式。
当服务器必须在用户本地机器运行时使用MCPB——比如读取本地文件、驱动桌面应用、调用localhost服务或系统级API。如果你的服务器仅需调用云端API,那你几乎肯定应该使用远程HTTP服务器(详见
build-mcp-server
)。不要为可通过URL实现的功能付出MCPB打包的额外成本。

What an MCPB bundle contains

MCPB包包含的内容

my-server.mcpb              (zip archive)
├── manifest.json           ← identity, entry point, config schema, compatibility
├── server/                 ← your MCP server code
│   ├── index.js
│   └── node_modules/       ← bundled dependencies (or vendored)
└── icon.png
The host reads
manifest.json
, launches
server.mcp_config.command
as a stdio MCP server, and pipes messages. From your code's perspective it's identical to a local stdio server — the only difference is packaging.

my-server.mcpb              (zip压缩包)
├── manifest.json           ← 标识信息、入口点、配置 schema、兼容性
├── server/                 ← 你的MCP服务器代码
│   ├── index.js
│   └── node_modules/       ← 捆绑的依赖(或 vendored)
└── icon.png
宿主程序会读取
manifest.json
,以stdio方式启动
server.mcp_config.command
对应的MCP服务器,并进行消息管道传输。从代码逻辑来看,它与本地stdio服务器完全一致——唯一的区别在于打包方式。

Manifest

清单文件(Manifest)

json
{
  "$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
  "manifest_version": "0.4",
  "name": "local-files",
  "version": "0.1.0",
  "description": "Read, search, and watch files on the local filesystem.",
  "author": { "name": "Your Name" },
  "server": {
    "type": "node",
    "entry_point": "server/index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/server/index.js"],
      "env": {
        "ROOT_DIR": "${user_config.rootDir}"
      }
    }
  },
  "user_config": {
    "rootDir": {
      "type": "directory",
      "title": "Root directory",
      "description": "Directory to expose. Defaults to ~/Documents.",
      "default": "${HOME}/Documents",
      "required": true
    }
  },
  "compatibility": {
    "claude_desktop": ">=1.0.0",
    "platforms": ["darwin", "win32", "linux"]
  }
}
server.type
node
,
python
, or
binary
. Informational; the actual launch comes from
mcp_config
.
server.mcp_config
— the literal command/args/env to spawn. Use
${__dirname}
for bundle-relative paths and
${user_config.<key>}
to substitute install-time config. There's no auto-prefix — the env var names your server reads are exactly what you put in
env
.
user_config
— install-time settings surfaced in the host's UI.
type: "directory"
renders a native folder picker.
sensitive: true
stores in OS keychain. See
references/manifest-schema.md
for all fields.

json
{
  "$schema": "https://raw.githubusercontent.com/anthropics/mcpb/main/schemas/mcpb-manifest-v0.4.schema.json",
  "manifest_version": "0.4",
  "name": "local-files",
  "version": "0.1.0",
  "description": "Read, search, and watch files on the local filesystem.",
  "author": { "name": "Your Name" },
  "server": {
    "type": "node",
    "entry_point": "server/index.js",
    "mcp_config": {
      "command": "node",
      "args": ["${__dirname}/server/index.js"],
      "env": {
        "ROOT_DIR": "${user_config.rootDir}"
      }
    }
  },
  "user_config": {
    "rootDir": {
      "type": "directory",
      "title": "Root directory",
      "description": "Directory to expose. Defaults to ~/Documents.",
      "default": "${HOME}/Documents",
      "required": true
    }
  },
  "compatibility": {
    "claude_desktop": ">=1.0.0",
    "platforms": ["darwin", "win32", "linux"]
  }
}
server.type
——
node
python
binary
。仅作信息说明;实际启动逻辑由
mcp_config
定义。
server.mcp_config
—— 用于启动进程的命令/参数/环境变量。使用
${__dirname}
表示包内相对路径,使用
${user_config.<key>}
替换安装时的配置项。没有自动前缀——服务器读取的环境变量名称与你在
env
中定义的完全一致。
user_config
—— 在宿主程序UI中展示的安装时配置项。
type: "directory"
会渲染原生文件夹选择器。
sensitive: true
会将配置存储在系统钥匙串中。所有字段详见
references/manifest-schema.md

Server code: same as local stdio

服务器代码:与本地stdio服务器完全一致

The server itself is a standard stdio MCP server. Nothing MCPB-specific in the tool logic.
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFile, readdir } from "node:fs/promises";
import { join } from "node:path";
import { homedir } from "node:os";

// ROOT_DIR comes from what you put in manifest's server.mcp_config.env — no auto-prefix
const ROOT = (process.env.ROOT_DIR ?? join(homedir(), "Documents"));

const server = new McpServer({ name: "local-files", version: "0.1.0" });

server.registerTool(
  "list_files",
  {
    description: "List files in a directory under the configured root.",
    inputSchema: { path: z.string().default(".") },
    annotations: { readOnlyHint: true },
  },
  async ({ path }) => {
    const entries = await readdir(join(ROOT, path), { withFileTypes: true });
    const list = entries.map(e => ({ name: e.name, dir: e.isDirectory() }));
    return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
  },
);

server.registerTool(
  "read_file",
  {
    description: "Read a file's contents. Path is relative to the configured root.",
    inputSchema: { path: z.string() },
    annotations: { readOnlyHint: true },
  },
  async ({ path }) => {
    const text = await readFile(join(ROOT, path), "utf8");
    return { content: [{ type: "text", text }] };
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);
Sandboxing is entirely your job. There is no manifest-level sandbox — the process runs with full user privileges. Validate paths, refuse to escape
ROOT
, allowlist spawns. See
references/local-security.md
.
Before hardcoding
ROOT
from a config env var, check if the host supports
roots/list
— the spec-native way to get user-approved directories. See
references/local-security.md
for the pattern.

服务器本身是标准的stdio MCP服务器。工具逻辑中没有任何MCPB特有的代码。
typescript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { readFile, readdir } from "node:fs/promises";
import { join } from "node:path";
import { homedir } from "node:os";

// ROOT_DIR来自清单文件server.mcp_config.env中的配置——无自动前缀
const ROOT = (process.env.ROOT_DIR ?? join(homedir(), "Documents"));

const server = new McpServer({ name: "local-files", version: "0.1.0" });

server.registerTool(
  "list_files",
  {
    description: "List files in a directory under the configured root.",
    inputSchema: { path: z.string().default(".") },
    annotations: { readOnlyHint: true },
  },
  async ({ path }) => {
    const entries = await readdir(join(ROOT, path), { withFileTypes: true });
    const list = entries.map(e => ({ name: e.name, dir: e.isDirectory() }));
    return { content: [{ type: "text", text: JSON.stringify(list, null, 2) }] };
  },
);

server.registerTool(
  "read_file",
  {
    description: "Read a file's contents. Path is relative to the configured root.",
    inputSchema: { path: z.string() },
    annotations: { readOnlyHint: true },
  },
  async ({ path }) => {
    const text = await readFile(join(ROOT, path), "utf8");
    return { content: [{ type: "text", text }] };
  },
);

const transport = new StdioServerTransport();
await server.connect(transport);
沙箱完全由你负责实现。清单文件层面没有沙箱机制——进程会以用户的完整权限运行。请验证路径、禁止越权访问
ROOT
目录、对可执行进程进行白名单限制。详见
references/local-security.md
在从配置环境变量硬编码
ROOT
之前,请检查宿主程序是否支持
roots/list
——这是获取用户授权目录的标准方式。实现模式详见
references/local-security.md

Build pipeline

构建流程

Node

Node环境

bash
npm install
npx esbuild src/index.ts --bundle --platform=node --outfile=server/index.js
bash
npm install
npx esbuild src/index.ts --bundle --platform=node --outfile=server/index.js

or: copy node_modules wholesale if native deps resist bundling

或者:如果原生依赖无法被打包,则直接复制整个node_modules目录

npx @anthropic-ai/mcpb pack

`mcpb pack` zips the directory and validates `manifest.json` against the schema.
npx @anthropic-ai/mcpb pack

`mcpb pack`命令会将目录压缩为zip包,并验证`manifest.json`是否符合schema。

Python

Python环境

bash
pip install -t server/vendor -r requirements.txt
npx @anthropic-ai/mcpb pack
Vendor dependencies into a subdirectory and prepend it to
sys.path
in your entry script. Native extensions (numpy, etc.) must be built for each target platform — avoid native deps if you can.

bash
pip install -t server/vendor -r requirements.txt
npx @anthropic-ai/mcpb pack
将依赖包安装到子目录,并在入口脚本中将其添加到
sys.path
。原生扩展(如numpy等)需要为每个目标平台单独构建——如果可能,请避免使用原生依赖。

MCPB has no sandbox — security is on you

MCPB无内置沙箱——安全完全由你负责

Unlike mobile app stores, MCPB does NOT enforce permissions. The manifest has no
permissions
block — the server runs with full user privileges.
references/local-security.md
is mandatory reading, not optional. Every path must be validated, every spawn must be allowlisted, because nothing stops you at the platform level.
If you came here expecting filesystem/network scoping from the manifest: it doesn't exist. Build it yourself in tool handlers.
If your server's only job is hitting a cloud API, stop — that's a remote server wearing an MCPB costume. The user gains nothing from running it locally, and you're taking on local-security burden for no reason.

与移动应用商店不同,MCPB不强制权限控制。清单文件中没有
permissions
配置块——服务器会以用户的完整权限运行。
references/local-security.md
是必读内容,而非可选。每条路径都必须验证,每个可执行进程都必须在白名单内,因为平台层面不会对你进行任何限制。
如果你期望清单文件能提供文件系统/网络范围限制:这是不存在的。你需要在工具处理逻辑中自行实现。
如果你的服务器仅需调用云端API,请停止使用MCPB——这相当于给远程服务器套了个MCPB的外壳。用户无法从本地运行中获得任何收益,而你却无端承担了本地安全的负担。

MCPB + UI widgets

MCPB + UI组件

MCPB servers can serve UI resources exactly like remote MCP apps — the widget mechanism is transport-agnostic. A local file picker that browses the actual disk, a dialog that controls a native app, etc.
Widget authoring is covered in the
build-mcp-app
skill; it works the same here. The only difference is where the server runs.

MCPB服务器可以像远程MCP应用一样提供UI资源——组件机制与传输方式无关。比如可以实现浏览本地磁盘的文件选择器、控制原生应用的对话框等。
组件开发详见**
build-mcp-app
**技能;在MCPB中的使用方式完全一致。唯一的区别在于服务器的运行位置。

Testing

测试

bash
undefined
bash
undefined

Interactive manifest creation (first time)

交互式创建清单文件(首次使用)

npx @anthropic-ai/mcpb init
npx @anthropic-ai/mcpb init

Run the server directly over stdio, poke it with the inspector

直接通过stdio运行服务器,使用调试工具进行测试

npx @modelcontextprotocol/inspector node server/index.js
npx @modelcontextprotocol/inspector node server/index.js

Validate manifest against schema, then pack

验证清单文件是否符合schema,然后打包

npx @anthropic-ai/mcpb validate npx @anthropic-ai/mcpb pack
npx @anthropic-ai/mcpb validate npx @anthropic-ai/mcpb pack

Sign for distribution

签名用于分发

npx @anthropic-ai/mcpb sign dist/local-files.mcpb
npx @anthropic-ai/mcpb sign dist/local-files.mcpb

Install: drag the .mcpb file onto Claude Desktop

安装:将.mcpb文件拖拽到Claude Desktop中


Test on a machine **without** your dev toolchain before shipping. "Works on my machine" failures in MCPB almost always trace to a dependency that wasn't actually bundled.

---

发布前请在**未安装开发工具链**的机器上进行测试。MCPB的“在我机器上能运行”问题几乎总是源于某个依赖未被正确打包。

---

Reference files

参考文档

  • references/manifest-schema.md
    — full
    manifest.json
    field reference
  • references/local-security.md
    — path traversal, sandboxing, least privilege
  • references/manifest-schema.md
    ——
    manifest.json
    字段完整参考
  • references/local-security.md
    —— 路径遍历、沙箱、最小权限原则