Enables AI agents to communicate securely using stable addresses and durable mailboxes via encrypted, signed, and sealed message envelopes.
Also known as usebag/bag, @usebag/bag
- Stars
- 7
- Tools
- —
- Downloads, 30 days
- —
- Latest version
- —
- Last commit
- Nov 14, 2026
- License
- Apache-2.0
What is bag MCP?
bag enables AI agents to communicate securely using stable addresses and durable mailboxes via encrypted, signed, and sealed message envelopes.
Summary generated from the project's README
About bag MCP server
Developers can establish secure, asynchronous communication between AI agents using bag, a protocol that provides stable addresses and durable mailboxes. By using Ed25519 key pairs as identities, agents can exchange encrypted, signed, and sealed message envelopes through interchangeable store-and-forward relays. This setup ensures that relays act as dumb storage and cannot access the content of the messages, which are protected via HPKE encryption.
Users can interact with the system through a coding agent or via the command line using tools like send, inbox, and audit verify. The system is designed for resilience, allowing recipients to list multiple relays to maintain connectivity if one goes offline. Messages are stored by relays for up to 7 days, allowing offline agents to retrieve mail once they reconnect. The protocol includes a hash-chained audit log to verify the integrity of every step taken during an exchange.
What you can do with it
- 1Secure agent-to-agent messagingAn AI agent can send a sealed, encrypted message to another agent's stable address without the relay seeing the content.
- 2Verifying communication integrityA developer can use the audit tool to ensure that no part of the message exchange history has been altered or reordered.
- 3Asynchronous message retrievalAn agent can check its mailbox for messages sent while it was offline using the inbox command.
Written from the project's README. Every use case is checked against a quote from it.
How to install bag MCP
Formats verified 10 Nov 2026Add to CursorAdd to GooseInstall bag MCP in Claude Code
$ claude mcp add bag -- npx -y @usebag/bagInstall bag MCP in Cursor
~/.cursor/mcp.json
{
"mcpServers": {
"bag": {
"command": "npx",
"args": [
"-y",
"@usebag/bag"
]
}
}
}Install bag MCP in VS Code
.vscode/mcp.json
{
"servers": {
"bag": {
"type": "stdio",
"command": "npx",
"args": [
"-y",
"@usebag/bag"
]
}
}
}Install bag MCP in VS Code CLI
$ code --add-mcp '{"name":"bag","type":"stdio","command":"npx","args":["-y","@usebag/bag"]}'Install bag MCP in Claude Desktop
claude_desktop_config.json
{
"mcpServers": {
"bag": {
"command": "npx",
"args": [
"-y",
"@usebag/bag"
]
}
}
}Install bag MCP in Codex
$ codex mcp add bag -- npx -y @usebag/bagInstall bag MCP in Codex config.toml
~/.codex/config.toml
[mcp_servers.bag] command = "npx" args = ["-y", "@usebag/bag"]
Install bag MCP in Gemini CLI
$ gemini mcp add -s user bag npx -- -y @usebag/bagInstall bag MCP in Gemini settings.json
~/.gemini/settings.json
{
"mcpServers": {
"bag": {
"command": "npx",
"args": [
"-y",
"@usebag/bag"
]
}
}
}Configuration
AC_HOME
| Name | Description | |
|---|---|---|
| AC_HOME | — |
Automated checks
Ran Nov 14, 2026Full report →Passed our automated checks. That is not a security audit: read the code and give the server the narrowest access it needs. How checks work
- Source repository linked and reachableusebag/bag is public and reachablegithub.com/usebag/bag ↗Pass
- Package names this repository as its source@usebag/bag names usebag/bag as sourcenpmjs.com/package/@usebag/bag ↗Pass
- Build provenance (Sigstore attestation)Sigstore attestation for @usebag/bagnpmjs.com/package/@usebag/bag/v/0.4.1#provenance ↗Pass
- Open source license (OSI)Apache-2.0spdx.org/licenses/Apache-2.0.html ↗Pass
- Commit or release in the last yearLast commit or release 2026-11-12github.com/usebag/bag ↗Pass
- OpenSSF ScorecardNot checked
- Package name not close to a popular packageNo popular package one edit away from @usebag/bagPass
- No critical advisory in the package or its dependenciesNot checked
- No hidden instructions to the model0 tool descriptions and the README read; no override phrases, credential paths or hidden charactersPass
- Destructive tools are labelledNot checked
- Remote endpoint answers the MCP handshakeNot checked
README
View on GitHubbag
A small protocol that gives an agent a stable address and a durable mailbox. Install it with your coding agent; use it from the agent, or from the command line.
Quick start · Usage · How it works · Specification · Install guide · Security
From an agent, you say what you want in plain words, and the agent calls the bag server's tools:
The same exchange by hand, with the command line: alice sends bob one sealed envelope through a relay, bob reads it,
and his audit log is verified from its first line. Here c runs the command line and R holds the relay's URL. The
Quick start runs the same steps from a clean machine, and the output lines are the ones
docs/partner-install.md documents.
$ c send --as alice --to "$(c address bob)" --relay "$R" --body '{"text":"hi"}'
<envelope id>
published <relay> tries=1
$ c inbox --as bob --relay "$R" --once --trust-root "$(c address alice)"
accept <id> <from> <root> chat.v1 {"text":"hi"}
$ c audit verify "$AC_HOME/state/bob/audit.jsonl"
ok <n> lines
Contents: What bag is · Why · Status · Limits · Quick start (with your agent, by hand) · Usage (from an agent, from the command line) · How it works · What is in this repository · Documentation · The checks · Security · Contributing · Licence
What bag is
An identity is an Ed25519 key pair whose address is its public key, and its mailbox is kept by the relays its signed address record lists: you never reach a machine, you reach its mailbox. A message is a signed and sealed, threaded envelope, and relays are dumb, interchangeable store-and-forward servers that enforce only the policy a mailbox's owner signed. An authority chain that anyone can verify offline says who may speak for whom.
Why
- A relay never sees the content. The sender seals the type, thread and body to each recipient with HPKE (RFC 9180: X25519, HKDF-SHA256, AES-256-GCM). A relay sees who writes to whom and when, not what they say.
- A relay cannot forge or change mail. Every envelope and record is signed with Ed25519, and an address is a public key. The recipient checks each signature and the authority chain itself.
- The mailbox's owner decides who may write. A signed accept-from record names the roots the mailbox takes mail from. A relay refuses mail under any other root.
- More than one relay. A recipient lists its relays in its signed address record. The sender publishes the same bytes to up to three of them, and the recipient reads every one and drops duplicates by envelope id. An identity that lists two relays keeps working while one is down.
- Mail waits for an offline recipient. A relay keeps each envelope for up to 7 days. The sender keeps the exact
bytes in its outbox, and a running edge retries each relay until 7 days after the envelope's
ts. - Each step is audited. The edge writes every step it takes to a hash-chained log, accepted or refused, each line
with its decision and reason.
audit verifyrecomputes the chain and names the first line that was edited, removed or reordered. - The spec is the contract. Its test vectors bind three implementations: the TypeScript reference, the Rust crate and relay, and a Python participant written from the spec alone.
Status
[!WARNING] Security preview, review in progress: no outside cryptographic review has signed, the audit package is public, and nothing here says "reviewed" until a reviewer signs.
This is a security preview of version 0 of the wire format. Nothing here is supported or stable yet. Treat it as
something to read, test and attack, not to depend on. What changed in each published cut is in
CHANGELOG.md.
Limits
- Two public relays, each one Machine. The project runs two public relays, each a single Fly.io Machine in one region
(Paris and Amsterdam) with its store in one SQLite file on one volume. Neither copies the other. An identity that
lists both keeps working while one is down: a sender publishes each message to up to three of the relays its
recipient lists, and a recipient reads every relay it lists (
spec/SPEC.md§4.3). When no listed relay takes a message, the sender's outbox keeps the sealed envelope and a running edge retries it until 7 days after the envelope'sts, then marks it failed and keeps the bytes forresend(packages/edge/README.md). - Limits count per client address, not per actor. A relay counts its limits per client address: an IPv4 address,
or on IPv6 a /56 for the rates and quotas and a /64 for the connection cap. The public relays keep the reference
relay's defaults: 79 open WebSocket connections per address; 600 publishes a minute per address, 120 per signing key
and 1,200 per root; 6,000 fetches a minute per address, counted before any signature is checked; 100 MB of stored
envelopes a UTC day per address; 100 new record signers a day per address; and 10,000 envelopes a day per sender
domain. Past a rate or a quota the relay answers 429
rate-limitedwithRetry-After. A client that holds many addresses, such as an IPv6 block larger than a /56, gets a set of limits for each. Beyond them, a relay's shared bounds are the connection limit of the proxy in front of it, a ceiling of 30,000 fetches a minute for all clients together, and its store cap. - The proxy limit is the first ceiling. The proxy in front of each public relay takes at most 3,000 connections at once, for all clients together. Every online agent holds one for its subscription, and each call takes one while it lasts. At that limit no new connection reaches the relay, for anyone.
- Measured capacity. On one such Machine (shared CPU, 512 MB), with the proxy limit and the per-address limits raised for the run and all load from one client machine, the relay held 100 publishes a second in 30-second steps with 1,000 live subscriptions, and took 20 a second for 9 minutes. At 100 a second the publish p99 was 213 ms and the fetch p99 266 ms, measured over the internet. It held 3,966 subscriptions without refusing one; that is a lower bound set by the client's network path. Sustained rates above 20 a second, load from many addresses, and the Machine's CPU and memory under load are not measured (the project's load measurement of 2026-11-01).
- A full store refuses everyone's new mail. The store cap is one number for the whole relay. When the store
reaches it, the relay refuses every new envelope, from any sender, and every record from a signer it does not yet
hold, with 503
store-full, until stored mail expires or the operator raises the cap. A signer it already holds may still update its records, so rotation and revocation go on. The daily byte quota holds what one address can add to 100 MB a day; many addresses together can still fill the store. A second relay adds uptime, not room: a message goes to each relay its recipient lists, so the relays fill together. - One writer per relay. A relay keeps one SQLite connection behind one lock. Every publish, fetch, subscription read, acknowledgement and expiry sweep takes its turn on it, so a relay's throughput is that of one writer, and a flood of any one call slows the others.
- Mail is kept up to 7 days, read or not. A relay keeps each envelope until it expires: 7 days after it arrives by
default, the public relays' setting, or sooner when the recipient's accept-from record sets a shorter
ttl. An acknowledgement moves the recipient's cursor; it does not delete the envelope. Until it expires, the relay holds the sealed bytes and their outer parts (SECURITY.md, "Threat model in one paragraph"). - Domain roots make a relay fetch from hosts others name. An accept-from record may name a
domain:root, and the relay then fetches that domain's record. So anyone can make a relay send HTTPS GET requests to a public host they name. The relay bounds them: HTTPS only, no redirect, at most 8 domains for one publish, at most one fetch of a domain a minute, at most 256 KiB within the time limit (10 seconds by default), and never to an address that is not public. - A backup is only as safe as its relay. Where the project backs up a relay (Litestream, streaming its database to an object-storage bucket), the relay process holds the bucket's key and the bucket keeps no older versions, so a relay that is taken over can delete its own backup. A restore loses the mail the relay acknowledged after its last upload, unless another relay the recipient lists took it.
- Windows is untested. No check or install step here has been run on Windows.
- Claude Code and omp have hosted the MCP server. omp 18.17.0 was checked with
host install omp(docs/host-omp.md); the Codex and opencode entries inpackages/mcp/README.mdare not checked. A host that does not declare MCP elicitation cannot pair: the pairing tools refuse it asno-elicitation. - The spec still changes. A change can alter a verdict or add a rule an edge must follow, as two decisions on relay restores did on 2026-10-31; an edge installed before such a change has to be updated to follow it.
Quick start
With your agent (recommended)
Paste this prompt into your coding agent:
Fetch and follow the instructions to set me up on bag.dev from https://bag.dev/install
Your agent fetches the guide at bag.dev/install, which is written for a coding agent, and
follows it step by step: it runs bag, the npm package @usebag/bag at the version the guide names, with npx,
checks that the package is the published commit, makes your identity, publishes its records at the two public relays,
and registers the bag MCP server in a working folder. After you restart Claude Code
in that folder, you ask it to pair with your peer: the pairing dialogs and the six check digits are yours to answer,
never the agent's. Once you are paired, you ask it to send a hello, and it sends and reads through the server's tools.
The guide is written for Claude Code, the one host that has run the MCP server so far (see Limits); the
prompt itself is also served at bag.dev/prompt.
By hand with the command line
This is the manual path, and the reference for what the agent does. From a clean machine to audit verify: one relay,
two identities and one sealed message, all on your own machine.
1. Get bag. You need Node 24 or later. bag is the npm package @usebag/bag, one bundle with no dependency and no
install script; npx runs it without installing anything globally. Name the version and the program every time:
c() { npx --yes --package=@usebag/bag@0.4.0 bag "$@"; }
c --version # prints: bag 0.4.0 <the commit it was built from>
The package is published from a commit of this repository by a workflow with npm provenance, and
npm view @usebag/bag@0.4.0 gitHead names that commit. To run from source instead, see From source.
2. Get a relay. The relay is Rust and is not in the package: build it from source (cargo build --release --locked -p ac-relay, as From source says) or run its container image (docs/self-host.md, "Run it with
Docker"). The next step runs the binary from a build.
3. Start a relay. In terminal 1, a relay on port 8787 with its own database file (it prints
AC_RELAY_URL=http://127.0.0.1:8787):
target/release/ac-relay --port 8787 --db "${TMPDIR:-/tmp}/ac-cut-relay.sqlite"
4. Make two identities. In terminal 2, two local identities, alice and bob, with their keys in a throwaway home:
R=http://127.0.0.1:8787
export AC_HOME="${TMPDIR:-/tmp}/ac-cut-home"
c health "$R" # prints: ok
c keygen alice && c keygen bob # each prints the address only, never a key
5. Publish bob's records. Bob publishes his address record (with his sealing key) and an accept-from record that
lets alice's root write to him. Each publish prints ok <kind> <signer>.
c record address bob --relay "$R" --sealing-key --out "$AC_HOME/bob.json" && c publish "$R" "$AC_HOME/bob.json"
c record accept-from bob --root "$(c address alice)" --max-bytes 65536 --ttl 604800 --out "$AC_HOME/bob.accept.json" \
&& c publish "$R" "$AC_HOME/bob.accept.json"
6. Send, read and verify. Alice sends one sealed envelope (it prints the envelope id, then
published <relay> tries=1), bob reads it from his inbox (accept <id> <from> <root> <type> <body>), and bob's
audit log is verified from its first line:
c send --as alice --to "$(c address bob)" --relay "$R" --type chat.v1 --body '{"text":"hi"}'
c inbox --as bob --relay "$R" --once --trust-root "$(c address alice)"
c audit verify "$AC_HOME/state/bob/audit.jsonl" # prints: ok <n> lines
Stop the relay with Ctrl-C. The throwaway files are ${TMPDIR:-/tmp}/ac-cut-home and ${TMPDIR:-/tmp}/ac-cut-relay.sqlite.
For the longer walk, from the same clean machine to a first exchange with another installation, a relay of your own
and your records at your own origin, read docs/partner-install.md.
From source
Every byte the package runs can be built and checked here. Clone the repository and check out the commit the
package names (c --version prints it): git clone https://github.com/usebag/bag.git bag, cd bag, git checkout --detach <commit>.
If you received this copy as one file, a git bundle, clone the file instead: git clone <file>.bundle bag, cd bag,
then check it with git bundle verify ../<file>.bundle, which must print ../<file>.bundle is okay.
You need Node 24 or later, rustup (the toolchain version is pinned in rust-toolchain.toml, and rustup installs it
on the first cargo call), and bash. Run everything from the root of this directory:
npm ci
cargo build --release --locked -p ac-relay
c() { node packages/cli/src/main.ts "$@"; }
c help # prints every command
From source, the command line is node packages/cli/src/main.ts, the MCP server node packages/mcp/src/main.ts and
the A2A bridge node packages/a2a/src/main.ts. npm run build -w packages/bag builds the package's bundle from the
checkout into packages/bag/dist/; node packages/bag/dist/bag.js --version then prints the version and this
checkout's commit, and npm pack -w packages/bag makes the package file.
Usage
The command line and the MCP server are two ways to be one identity. Run one receiver per identity: of an MCP server
and inbox --watch on the same identity, whichever starts second is refused as busy.
From an agent
This is the recommended way to use bag. Your agent uses it through the bag MCP server: you say what you want in plain words, and the agent calls the server's tools. The setup prompt in the Quick start installs the server and registers it for you.
packages/mcp is one MCP server over stdio. It gives an agent in an MCP host an address and a mailbox, as ten tools:
send, receipts, inbox, wait, ask, handoff, status, lookup, pair_invite and pair_accept, and an
eleventh, delegate, only when started with --allow-delegate. One server holds up to 64 identities. host install
writes the registration, and nothing it writes holds a key. Run from the package, it first copies bag to the
package folder of the bag home and writes that path, so the registration does not depend on npm's cache:
c host install claude-code --as alice --relay "$R" --project /path/to/your/project
It writes the server into the project's .mcp.json, and a SessionStart and a Stop hook into its
.claude/settings.json, which let a waiting session see new mail. packages/mcp/README.md,
"Install", gives the same by hand and for other hosts.
| say to your agent | the tool it calls |
|---|---|
| "Pair with my peer, and call them ben." | pair_invite or pair_accept, with dialogs only you answer |
| "Send ben a hello." | send, to the petname you gave |
| "Ask ben what 6 x 7 is, and wait for the answer." | ask, which waits up to 300 seconds a call for the answer |
| "Wait for a message." | wait, up to 300 seconds a call, then again with its resume token |
| "What is in my bag inbox?" | inbox: the accepted messages, oldest first |
| "Show the bag status." | status: the address, relays, trust roots, counts and subscriptions |
Claude Code plugin
The plugin is not in this release. The MCP server it wraps is: packages/mcp gives an agent in Claude Code, or in any
MCP host that runs a server over stdio, an address and a mailbox. c host install claude-code --as <name> --relay <relay URL> --project <project dir> writes the registration for a project;
docs/partner-install.md, section 5c, walks through it, and packages/mcp/README.md, "Install", gives the same by hand
(claude mcp add) and for other hosts.
From the command line
The package's command line is the bin bag; the guides wrap it in a shell function, c:
c() { npx --yes --package=@usebag/bag@0.4.0 bag "$@"; }
c help # prints every command
From source it is node packages/cli/src/main.ts (From source).
| to | commands |
|---|---|
| make and list identities | keygen, address, keys list, keys rotate |
| sign and publish records, and keep them alive | record address, record accept-from, publish, record keep |
| send and receive | send, resend, inbox --once, inbox --watch |
| pair with another person's identity | peer invite, peer accept, unpair |
| name peers and find their records | name add, lookup |
| put an agent on your organisation's domain | onboard, delegate |
| connect an agent host | host install claude-code, host install codex, host install omp |
| check | health, status, audit verify |
Errors exit 1 with bag: <code>: <detail> on stderr. packages/cli/README.md documents
the commands and their flags, and Appendix A of spec/SPEC.md lists every code.
Public relays
Two public relays run: https://relay1.bag.dev, in Paris, and https://relay2.bag.dev, in Amsterdam. Either can
stand in for a relay of your own as --relay, and c health https://relay1.bag.dev checks one. Each is a single
machine with the limits listed under Limits.
Pairing with another installation
Two people on two machines pair their identities with a one-use code and six check digits, through a rendezvous relay.
From an agent, ask it to pair with your peer and say what to call them. It calls pair_invite or pair_accept, and
the host opens dialogs that only you answer, for the peer's address, the pairing code and the six check digits; the
agent never sees the digits. A host that cannot show those dialogs cannot pair (see Limits).
From the command line, one side runs peer invite, which prints a pairing code. The other side runs peer accept
with that code and reads the six digits it shows to the inviter, by another way than the code. The inviter types them.
Then each side trusts the other, and either can send to the other by the petname it gave. Here R is a relay both
machines can reach:
c peer invite --as me --relay "$R" --name ben # prints: pairing code <code>
c peer accept --as me --relay "$R" --name ana '<pairing code>' # on the other machine
c send --as me --to ben --relay "$R" --type chat.v1 --body '{"text":"hello"}'
When you already know the peer's address, add --for <address> to bind the invitation to it.
docs/partner-install.md, section 5b, walks through the whole exchange.
The A2A binding
packages/a2a lets an unmodified A2A 1.0 client complete a task with a bag agent through the caller's own local
bridge, and every relay between them carries ciphertext only. packages/a2a/README.md
describes the bridge and the message type.
A participant in Python
examples/python-join is a participant written in Python from the spec alone, sharing no code with the reference.
Its check script sends and receives through the reference relay, to and from the reference CLI
(examples/python-join/README.md).
bag MCP questions
How do I add bag MCP to Claude Code?
Run claude mcp add bag -- npx -y @usebag/bag in your terminal. Add --scope user to make it available in every project.
How do I add bag MCP to Cursor?
Open ~/.cursor/mcp.json and add the server under mcpServers, using the Cursor config shown in the install section above.
Is the protocol currently stable for production use?
No, it is currently in a security preview stage (version 0) and should be treated as something to test and attack rather than something to depend on.