Complete guide for building and distributing custom MCP (Model Context Protocol) connectors as Claude Desktop Extension bundles (.mcpb files). Use this skill whenever a user wants to: create a custom MCP server, package an MCP for Claude Desktop, build a connector for an API or internal tool, distribute an MCP to their team or company, create or fix a manifest.json for a Claude extension, package a .mcpb or .dxt file, set up user_config for API keys, or publish a connector. Also triggers for: "how do I build a Claude connector", "how do I share my MCP with my team", "how do I package my MCP server", or any variation of creating distributable Claude Desktop extensions. ALWAYS use this skill when building or packaging MCPs — it contains the correct spec version, CLI commands, and distribution steps.
How this skill is triggered — by the user, by Claude, or both
Slash command
/mcp-connector-builder:mcp-connector-builderThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
This skill guides you through the full workflow: write an MCP server → package it as a Claude Desktop Extension bundle (`.mcpb`) → distribute to your team.
This skill guides you through the full workflow: write an MCP server → package it as a Claude Desktop Extension bundle (.mcpb) → distribute to your team.
Key terminology:
.mcpb. Formerly called DXT (.dxt). Both extensions install in Claude Desktop, but .mcpb + manifest_version: "0.3" is the current standard.Use Node.js. It ships with Claude Desktop so end users need zero additional setup. Python requires them to have Python installed — adds friction.
my-connector/
├── manifest.json ← Required. Bundle metadata & config.
├── package.json
├── icon.png ← 512×512px PNG recommended (256×256 minimum)
└── servers/
└── server.mjs ← MCP server entry point
mkdir my-connector && cd my-connector
npm init -y
npm install @modelcontextprotocol/sdk zod
In package.json, set "type": "module" so ES module imports work.
Use McpServer + registerTool (the modern API). Validate all inputs with Zod .strict().
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// Read credentials from env (injected by manifest at runtime)
const API_KEY = process.env.MY_API_KEY || '';
// Validate required credentials at startup
if (!API_KEY) {
console.error('ERROR: MY_API_KEY is required. Configure it in connector settings.');
process.exit(1);
}
const server = new McpServer({ name: 'my-connector', version: '1.0.0' });
server.registerTool('tool_name', {
title: 'Human-readable title',
description: `What this tool does and when Claude should use it.
Include examples of good inputs so Claude uses it correctly.`,
inputSchema: z.object({
query: z.string().describe('What to search for. Example: "error logs from last hour"'),
limit: z.number().int().min(1).max(100).default(20).describe('Max results to return'),
}).strict(),
annotations: {
readOnlyHint: true, // true = no side effects (reads only)
destructiveHint: false, // false = won't delete/modify data
idempotentHint: true, // true = safe to retry
openWorldHint: true, // true = calls external APIs
},
}, async (params) => {
try {
const result = await callYourApi(params.query, params.limit);
return { content: [{ type: 'text', text: JSON.stringify(result) }] };
} catch (err) {
return {
content: [{ type: 'text', text: `Error: ${err.message}` }],
isError: true, // tells Claude to surface this as an error
};
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
Good tool design:
This is the most critical file. Use manifest_version: "0.3" — not dxt_version.
{
"manifest_version": "0.3",
"name": "my-connector",
"display_name": "My Connector",
"version": "1.0.0",
"description": "One-line description shown in Claude Desktop.",
"long_description": "Detailed description in markdown. Explain what APIs this connects to and what problems it solves.",
"author": {
"name": "Your Name",
"url": "https://yoursite.com"
},
"icon": "icon.png",
"homepage": "https://yoursite.com",
"license": "MIT",
"keywords": ["keyword1", "keyword2"],
"tools": [
{
"name": "tool_name",
"description": "What this tool does."
}
],
"server": {
"type": "node",
"entry_point": "servers/server.mjs",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/servers/server.mjs"],
"env": {
"MY_API_KEY": "${user_config.api_key}",
"MY_SITE": "${user_config.site}"
}
}
},
"user_config": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "Your API key. Found at Organization Settings → API Keys.",
"sensitive": true,
"required": true
},
"site": {
"type": "string",
"title": "Site",
"description": "Your site URL, e.g. app.example.com",
"sensitive": false,
"required": false,
"default": "app.example.com"
}
},
"compatibility": {
"runtimes": { "node": ">=18" }
}
}
| Field | Required | Notes |
|---|---|---|
manifest_version | Yes | Must be "0.3" |
name | Yes | Machine-readable, no spaces |
display_name | No | Human-friendly name for the UI |
version | Yes | Semver, e.g. "1.0.0" |
description | Yes | Shown in Claude Desktop UI |
author.name | Yes | — |
server | Yes | Runtime config (see below) |
tools | Recommended | Declare all tools statically. Required for directory submission. |
user_config | Recommended | Declare all user-supplied values (API keys, sites, etc.) |
server.type options: "node" (recommended), "python", "binary", "uv" (experimental)
Template variables in args / env:
${__dirname} — extension install directory${user_config.FIELD} — value the user typed in settings${HOME}, ${USER} — system environment variablesuser_config field types: string, number, boolean, directory, file, enum
sensitive: true → stored in OS keychain, masked in UI (use for API keys/passwords)required: true → extension won't enable until the user fills this indefault → pre-fills the field in settings UI# Install the mcpb CLI (one-time global install)
npm install -g @anthropic-ai/mcpb
# Always validate first — catches spec errors before packaging
mcpb validate manifest.json
# Package into a .mcpb bundle
mcpb pack
Output file: <name>-<version>.mcpb
The bundle is a zip of your project files + node_modules (production deps). Dev dependencies and files matching .mcpbignore / .npmignore are excluded automatically.
Important:
mcpbv2.1.2+ is required formanifest_version: "0.3". The olderdxtCLI (v0.2.6) only supports the olddxt_versionkey and will reject the current spec. Always usemcpb.
.mcpb file, oruser_config fields when promptedCheck Claude Desktop logs if something doesn't work:
~/Library/Logs/Claude/Share the .mcpb file via Slack, email, or shared drive. Team members double-click to install. Each person installs individually on their own machine.
Available on Team and Enterprise plans.
.mcpb as a private/custom extensionWarning: Enabling the allowlist removes all previously-installed extensions from team members' Claude Desktop. Give everyone a heads-up before enabling.
macOS MDM profiles and Windows Group Policy can pre-install approved extensions automatically. Requires Claude Desktop v0.13.91+.
Increment version in both manifest.json and package.json, repack, and redistribute. For Option B (allowlist), upload the new .mcpb and existing users can update from their connector settings.
| Symptom | Cause | Fix |
|---|---|---|
dxt_version: Required + manifest_version: Unrecognized | Old dxt CLI | npm install -g @anthropic-ai/mcpb |
| Server starts but tools aren't available | Wrong transport | Use StdioServerTransport, not HTTP |
| Tools fail silently | Env vars not injected | Check user_config → env mappings in manifest |
| Bundle is very large (>20 MB) | Dev deps or test files included | Add .mcpbignore, run npm install --production before packing |
| Credentials not saved between restarts | Hardcoded in server file | Read from process.env — manifest injects them at startup |
mcpb validate passes but install fails | entry_point path wrong | Verify the path in server.entry_point matches your actual file |
The smallest possible connector — one tool, reads one env var:
servers/server.mjs
#!/usr/bin/env node
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
const server = new McpServer({ name: 'hello-connector', version: '1.0.0' });
server.registerTool('say_hello', {
title: 'Say Hello',
description: 'Returns a greeting.',
inputSchema: z.object({ name: z.string().describe('Name to greet') }).strict(),
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
}, async ({ name }) => ({
content: [{ type: 'text', text: `Hello, ${name}!` }],
}));
await server.connect(new StdioServerTransport());
manifest.json
{
"manifest_version": "0.3",
"name": "hello-connector",
"display_name": "Hello Connector",
"version": "1.0.0",
"description": "A minimal example connector.",
"author": { "name": "Your Name" },
"server": {
"type": "node",
"entry_point": "servers/server.mjs",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/servers/server.mjs"]
}
},
"tools": [{ "name": "say_hello", "description": "Returns a greeting." }]
}
Then: mcpb pack → hello-connector-1.0.0.mcpb → double-click to install.
npx claudepluginhub p3nj/p3nj-market --plugin mcp-connector-builderGuides developers building MCP servers for Claude: interrogates use case, selects deployment (remote HTTP, MCPB, stdio), tool patterns, and hands off to specialized skills.
Guides integration of Model Context Protocol (MCP) servers into Claude Code plugins via .mcp.json or plugin.json for external service tools, with scope management (local, project, user).
Creates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.