b2-mcp-server
Provides tools for interacting with Backblaze B2 Cloud Storage, enabling AI agents to manage buckets, upload and download files, hide/unhide files, delete file versions, report bucket usage against a budget, and list application keys.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@b2-mcp-serverCheck which buckets are over 80% of their budget?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
b2-mcp-server
An MCP server that exposes Backblaze B2 Cloud Storage as tools any MCP-compatible AI client can call. Manage B2 by talking to an assistant -- "which buckets are over 80% of their budget?", "upload this file", "delete that version" -- instead of writing SDK code or clicking through the web console.
Nine tools over stdio: list buckets, list files, upload, download, hide, unhide, delete a version, report bucket usage against a budget, and list application keys.
Built on @backblaze-labs/b2-sdk and the MCP TypeScript SDK.
Why it is shaped this way
The interesting part of an MCP server is not the API calls. It is what happens when a language model is the one calling them.
Local filesystem access denies by default. Upload reads only from
B2_UPLOAD_ROOT, download writes only toB2_DOWNLOAD_ROOT, and both are refused outright when the root is unset. Read and write roots are separate so they can be granted independently. Paths are resolved withrealpathbefore the containment check, so a symlink inside the root cannot point outside it, and containment istarget === root || target.startsWith(root + sep)-- a barestartsWithwould admit the sibling directory/data/uploads-evilfor the root/data/uploads. Rejection messages name the offending path and never the root.The one tool that destroys data cannot be aimed loosely.
b2_delete_file_versiontakes an exactfileIdand refuses to resolve one from a file name, so "delete hello.txt" cannot be satisfied in a single step: the id has to come out of a listing a human can see. A confirm flag would not help, since the same model that calls the tool would set it. Deletion is also refused unlessB2_AUDIT_LOGis configured, and writes an INTENT record before acting and an OUTCOME record after -- on failure too, because a log that can miss events is not a log. WithB2_ARCHIVE_ROOTset, the bytes are copied locally first.The credentials boundary enumerates its fields.
b2_list_keysnames all nine fields it emits rather than spreading the SDK's key object, so a future SDK version that adds a secret-bearing field to a list response cannot leak it by accident. Key creation is deliberately not implemented for the same reason: B2 returns the live secret only fromcreateKey, and that value would land in a model's context window.Partial results announce themselves. Anything that can return less than the whole truth says so in the payload --
truncated/nextFileNameon file listings,truncated/anyTruncated/unfinishedLargeFileson usage,truncatedon key listings. A partial answer presented as a complete one is worse than a refusal, because the caller cannot tell.Downloads are written atomically. Bytes go to
<target>.<pid>.partial, are counted and length-checked, and are renamed onto the target only on a match. The SDK documents that a checksum failure errors the stream after bytes have flowed, so writing straight to the final path would leave a truncated file behind.Errors are returned, never thrown, across the MCP boundary. A throw tears down the stdio session; an error result lets the client read the reason and explain it.
On bucket usage
B2's API exposes no quota or usage endpoint -- caps and alerts live only in the
web console. b2_bucket_usage therefore sums file versions, including old
ones, because B2 bills for those too. Hide markers, folder markers, and start
records are excluded. Parts of unfinished large uploads are billed but not
summable, so they are reported separately as unfinishedLargeFiles and
bytesUsed is an honest floor rather than a total. The budget it is measured
against is project policy defined in code (10 GiB default, the B2 free tier),
not a B2 concept.
Related MCP server: duplicati-mcp
Requirements
Node >= 22.3.0. This is a hard floor, not a preference -- the B2 SDK declares
it in engines. Pinned by .nvmrc.
Setup
npm install
cp .env.example .env # then fill in .env; it is gitignored
npm test # 131 passing
npm run buildCreate an application key in the Backblaze console under Account > Application Keys. Use a regular scoped key, not the master key: the master key carries every capability, cannot be scoped, and cannot be deleted, only regenerated.
Environment
.env.example is committed documentation holding names only. Values go in
.env.
Variable | Required | Purpose |
| yes | Application key id. |
| yes | Application key secret. |
| for uploads | The only directory |
| for downloads | The only directory |
| for deletion | Append-only JSON Lines file, one object per mutation. Deletion is refused when unset. |
| optional | Where a copy of each deleted version is kept before it is destroyed. A manifest proves what existed; this keeps the bytes. |
Capabilities needed per tool: listBuckets/listFiles for the read tools,
writeFiles for upload, readFiles for download, deleteFiles for deletion,
listKeys for b2_list_keys. A Read Only key fails at the B2 API on any write,
by design.
Running
npm start # node dist/server.js (built)
npm run dev # tsx src/server.ts (from source)The server speaks MCP over stdio. Nothing is ever written to stdout except the protocol stream; diagnostics go to stderr.
Wiring it into a client
Add to your MCP client config (Claude Desktop, Claude Code, or any other MCP host):
{
"mcpServers": {
"b2": {
"command": "node",
"args": ["/absolute/path/to/b2-mcp-server/dist/server.js"]
}
}
}Credentials come from the .env file next to the package, so none appear in
client config.
Calling tools by hand
npx @modelcontextprotocol/inspector --cli npm run dev --method tools/list
npx @modelcontextprotocol/inspector --cli npm run dev \
--method tools/call --tool-name b2_bucket_usageEach argument needs its own --tool-arg, for example
--tool-arg bucketName=my-bucket.
Gotcha worth knowing: a relative localPath resolves against the configured
root, not your shell's working directory. With B2_UPLOAD_ROOT=".../uploads",
pass localPath=hello.txt, not localPath=uploads/hello.txt -- the latter
looks for uploads/uploads/hello.txt. The error message shows the candidate as
you gave it and deliberately not the resolved path, because that would print the
root.
Tools
Tool | Read/write | What it does |
| read | Every bucket, with id, name, and type. |
| read | One page of current files in a bucket, optional |
| write | Uploads from |
| write (local) | Downloads to |
| write | Hides a file from listings. Reversible; the data stays in version history. |
| write | Removes the latest hide marker. Reports |
| destructive | Permanently destroys one version. Needs the exact |
| read | Bytes stored per bucket against a budget, flagging buckets over the threshold. Omit |
| read | Application keys with capabilities, bucket restrictions resolved to names, and derived expiry. No secrets. |
Every tool carries a zod input schema with per-field descriptions and MCP
annotations (readOnlyHint, destructiveHint, idempotentHint) set honestly,
so a client can decide what needs confirmation.
Testing
npm test131 tests across 12 files, no network and no credentials required -- modules
take the narrow structural type they actually use (BucketLister, not
B2Client), so a fake satisfies them while tsc still proves the real client
fits.
Several test files are regression coverage for bugs that a green suite would not otherwise catch, and their fixtures are shaped from observed dependency behavior rather than from what the API ought to do:
The B2 SDK throws errors with an empty
.message; the diagnosis lives onname/code/status. Reading.messagealone returned a blank string to the user for every B2-side failure.Node's
process.loadEnvFile()reports an unreadable file asENOENT, notEACCES, which downgrades "your .env has wrong permissions" to "you have no .env".
Where an invariant can be checked against real data instead of a fixture, it
was: usage was validated against a live account by the property that
b2_bucket_usage can never report fewer bytes than b2_list_files sums (it
exceeded it by exactly one 28-byte old version), and the delete path by a
four-way SHA-1 match across the original, the download, the archive copy, and
B2's own checksum. A fixture agrees with whatever the code does; a real corpus
does not.
Project layout
src/
server.ts MCP server, tool registration
config.ts credentials from the environment
env-file.ts .env loading (Node built-in, zero dependencies)
path-fence.ts read and write containment, both directions
atomic-write.ts temp file plus rename
audit-log.ts append-only JSON Lines mutation record
b2/ client, buckets, files, upload, download, delete, usage, keys
tests/ vitest, one file per module
claude-plans/ numbered design docs, written before the codeRoadmap
The full parked list lives in claude-plans/ROADMAP.md. The largest known gap:
b2_list_file_versions. Usage counts every version the account is billed for
while b2_list_files shows only current ones, so the server can report "you are
paying for 91 versions across 90 files" and offer no way to see the difference.
Deleting a current version promotes the next-oldest, which makes discovery
destructive-only today.
License
MIT.
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- Alicense-qualityFmaintenanceEnables seamless integration with Backblaze B2 cloud storage for managing buckets, uploading/downloading files, handling large multipart uploads, and managing application keys through natural language interactions.91MIT
- AlicenseAqualityDmaintenanceMCP server for managing Duplicati backups from an LLM.17MIT
- AlicenseAqualityFmaintenanceMCP server for the Rclone RC API. Gives AI assistants the ability to manage cloud storage remotes, copy/sync files, list directories, and more — all through natural language.564511MIT
- Flicense-qualityDmaintenanceA Model Context Protocol (MCP) server that provides a bridge between MCP-compatible clients and MinIO object storage. It exposes MinIO operations as MCP tools for seamless bucket management and object operations.4
Related MCP Connectors
MCP server exposing the Backtest360 engine API as tools for AI agents.
Cloud-hosted MCP server for durable AI memory
A comprehensive Model Context Protocol (MCP) server that enables AI assistants to interact with yo…
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ffumero2003/b2-mcp-server'
If you have feedback or need assistance with the MCP directory API, please join our Discord server