Model Context Protocol — MCP — is the standard Anthropic introduced for connecting Claude to external tools and data. Your calendar, your codebase, your database, your custom internal APIs. It's the glue between Claude and the rest of your stack, and over the past few months it has gone from a niche protocol for early adopters to something every serious AI integration is being built on top of.
The catch, until recently, was that running an MCP server locally meant editing JSON config files, wrestling with Node paths, and hoping nothing broke after the next Claude Desktop update. Then Anthropic shipped the MCP Bundle format — .mcpb files — which package an entire MCP server into a single installable archive. Think Chrome extension, but for your AI assistant.
This post is a practical walk-through of how to build one. We'll cover the protocol model, what an MCPB actually contains, how to design good tool definitions, how to handle credentials securely, what the install and update lifecycle looks like, and where the format hits its limits.
Before writing any code, it helps to understand what an MCP server actually is. It's a process that speaks a small JSON-RPC protocol over stdio. Claude Desktop launches it as a subprocess, pipes requests to its stdin, and reads responses from its stdout. There is no HTTP server, no port to bind, no domain to register. The transport is just two file descriptors.
That simplicity is the whole point. Local MCP servers are designed for things like exposing a private API to Claude, reading from a local database, automating a workflow specific to one machine, or wrapping an internal service that already exists behind your VPN. They're a poor fit for anything that needs to be shared across users or accessed from claude.ai on the web — for that, you'd build a remote MCP server with a hosted HTTPS endpoint and connect to it as a custom connector.
An MCP server exposes three kinds of capabilities: tools (functions the model can call), resources (read-only data the model can fetch), and prompts (templated instructions the user can invoke). Most servers focus on tools, because tools are what unlock real action. Resources and prompts are valuable but optional.
An .mcpb file is just a zip archive with two things inside: a manifest.json describing the server, and the server code itself. When you install it, Claude Desktop unpacks it into its own extensions directory and runs the server as a subprocess.
The manifest is where the interesting configuration lives. It declares the runtime (Node, Python, or a binary), the entry point, environment variables to inject, and any user-supplied configuration the server needs. A minimal Node-based manifest looks like this:
{
"mcpb_version": "0.1",
"name": "my-extension",
"version": "1.0.0",
"description": "A simple MCP extension",
"author": { "name": "Your Name" },
"server": {
"type": "node",
"entry_point": "server/index.js",
"mcp_config": {
"command": "node",
"args": ["${__dirname}/server/index.js"],
"env": {
"API_KEY": "${user_config.api_key}"
}
}
},
"user_config": {
"api_key": {
"type": "string",
"title": "API Key",
"description": "Your API key for authentication",
"sensitive": true,
"required": true
}
}
}
The ${__dirname} token gets resolved to the unpacked extension's directory at launch. The ${user_config.api_key} token gets resolved to whatever the user entered when installing — and because the field is marked sensitive, that value is stored in the OS keychain (Keychain on macOS, Credential Manager on Windows) rather than in plain text on disk. This is a genuinely important feature. Don't hardcode secrets, don't ask the user to paste them into a config file, and definitely don't ask Claude to remember them between sessions.
The MCPB spec supports Node, Python, and pre-compiled binaries. In practice, Node is the right default for almost everyone, for one specific reason: Claude Desktop ships with its own Node runtime. That means a Node-based extension works out of the box on any user's machine without them installing anything. Python doesn't get this treatment — you have to bundle dependencies yourself, and you can't portably bundle compiled extensions like the ones the MCP Python SDK relies on.
If you have specific reasons to use Python (existing Python codebase, scientific dependencies, easier integration with a particular library), you can — but expect more friction at packaging and install time. For a self-contained tool wrapper, stick with Node and the @modelcontextprotocol/sdk package.
This is the part most tutorials skip, and it's the part that matters most.
Each tool you expose has three parts: a name, a JSON Schema describing its inputs, and a handler function. The schema and the description are not documentation for humans — they're prompts for the model. Claude reads them every time it decides whether to call your tool, what parameters to pass, and what to do with the result. If they're vague, Claude won't know when to call your tool. If they're misleading, Claude will call it incorrectly. If they're too long, you're burning context window for every conversation that loads them.
A few principles that consistently produce good results:
Name tools by intent, not by implementation. list_blog_posts is better than get_articles_db. send_invoice is better than post_invoice_endpoint. The model picks tools based on what they do, so the name should describe the user-facing outcome.
Mark required fields as required, and give optional fields sensible defaults. JSON Schema's required array is read by the model. If you say a field is required, Claude won't call the tool without it. If you don't, Claude may try to omit it and your server has to handle that case. Be deliberate.
Use enums for constrained values. If a parameter can only be "draft", "published", or "archived", declare an enum in the schema. The model respects enums. This prevents Claude from inventing plausible-sounding but invalid values.
Write descriptions that explain when to use the tool, not just what it does. "Creates a new blog post" is fine. "Creates a new blog post. Use this when the user asks to publish, write, or post new content. Do not use for editing existing posts — use update_blog_post instead." is much better, because it disambiguates against neighbouring tools.
Validate inputs server-side anyway. The schema is a hint, not a guarantee. The model can still pass weird values, especially when it's recovering from an earlier error. Validate at the handler boundary and return a structured error if something's wrong.
This is the single biggest mindset shift when building MCP servers. In a normal API, errors are something the calling code handles and possibly logs. In an MCP server, errors flow back to the model, which then explains them to the user and decides whether to retry. That changes what good error handling looks like.
An error message like 500 Internal Server Error is useless. The model has no idea what to do with it. An error message like "Failed to publish post: title contains characters that aren't allowed. Allowed: letters, digits, spaces. Found: ':'" tells the model exactly what went wrong and how to fix it on the next attempt.
Concretely, this means: catch exceptions at the handler level, return errors as structured tool responses with descriptive messages, include the offending field name and value where relevant, and resist the temptation to swallow errors silently. The model handles informative failures gracefully. It struggles with opaque ones.
Installing an .mcpb is supposed to be a double-click. In practice, on Windows at least, the file association doesn't always register cleanly the first time. If double-clicking does nothing, install via Settings → Extensions → Install Extension instead. Same outcome. You can also drag the file directly onto the Settings window.
To fix the association so future installs are one click: right-click the .mcpb file, choose Open With, browse to Claude.exe, and tick "Always use this app". On Mac, use Get Info → Open With → Change All. Once associated, double-click works as advertised.
For updates, the workflow is: bump the version field in manifest.json, repack with mcpb pack ., and install the new file. Claude Desktop detects the existing extension and replaces it. Restart Claude Desktop to make sure the old server process is killed and the new one is launched.
One important detail about updates: tool definitions are cached at the start of each conversation. If you change schemas mid-chat, the existing chat will keep using the old definitions until you start a new conversation. Handler logic changes, on the other hand, take effect immediately because that runs server-side on each call. This catches a lot of people during development — they update a tool description, retry in the same chat, and wonder why the model is still ignoring their changes.
Privately distributed extensions don't auto-update. Only extensions installed from the public directory do. If you're sharing a custom .mcpb with a team, you're responsible for distributing new versions yourself.
MCPBs are excellent for the use case they're designed for: a single user, a single machine, a personal or team-internal tool. There are a few things they're not good at, and it's worth knowing where the cliffs are.
They're machine-local. The same extension on a different machine is a separate install. There's no central registry of "where am I installed", no shared state across machines, no remote configuration. If your tool needs to coordinate across users, it needs to talk to a backend service — the MCP server itself can't be that backend.
They can't be reached from claude.ai. Local MCP servers are a Claude Desktop feature. The web app doesn't connect to them, and neither does Claude on mobile. If you want a tool accessible from anywhere you use Claude, you need a remote MCP server hosted at an HTTPS endpoint and added as a custom connector.
They depend on the protocol version negotiated at install. Claude Desktop and the MCP protocol are both evolving. There have already been cases where a Desktop update changed the negotiated protocol version and existing extensions stopped responding to tool calls until they were updated. This is rare but real, and worth being aware of when you're depending on an extension for production work.
They run with your user privileges. An MCP server is just a process on your machine. It can read your files, hit your network, and make any API call your machine can make. Treat extensions you didn't write yourself the same way you'd treat any third-party software. The bundle format includes the source, so you can — and probably should — read it before installing anything sensitive.
If you're new to building MCP servers, here's a path that works:
Start with a single tool that does one useful thing. Don't try to design a comprehensive API. Get one tool installed, working, and being called by Claude correctly. The protocol mechanics are simple but there are enough small details (manifest paths, env injection, schema validation, restart-to-reload) that getting a single tool through end-to-end is genuinely informative.
Once that works, add more tools incrementally. Watch how Claude uses them. You'll find that some tool descriptions need refining, some inputs need different names, some return shapes are easier or harder for the model to work with. This is the iteration loop, and it's worth doing slowly.
Finally, when the tool feels stable, package it as an .mcpb, version it properly, and treat it like any other piece of software. Bump versions on changes. Keep a changelog. Test on a clean install before sharing. Local-only doesn't mean low-stakes — these tools end up being trusted with real work.
The barrier to wiring Claude into custom systems has dropped dramatically in the last six months. The interesting question isn't whether you can build a useful MCP server anymore — it's which ones are worth building.
For businesses thinking about where this fits, the practical question usually isn't "should we adopt MCP" — it's which workflows would actually benefit from a tool-using AI in the first place. That's a problem of AI consulting more than software engineering. The protocol is straightforward. The harder work is identifying the right operational seams to wire it into.
Thinking about whether a custom MCP server, or a broader AI integration, could quietly remove friction inside your business operations? Get in touch with us to scope where it fits, define safe boundaries, and build something that delivers real value rather than chasing the trend.