Skip to main content
Glama
README.md
<div align="center">
  <img src="docs/icon-animated.svg" alt="BagOS Icon" width="144">
  <h1>BagOS ๐Ÿšฆ</h1>
  <p><em>An MCP server that lets an AI assistant trade on Solana โ€” and signs nothing until you say so.</em></p>

  [![npm](https://img.shields.io/npm/v/bagos-mcp-server?style=for-the-badge&color=CB3837&logo=npm&logoColor=white&label=npm)](https://www.npmjs.com/package/bagos-mcp-server)
  [![CI/CD](https://github.com/edycutjong/BagOS/actions/workflows/ci.yml/badge.svg)](https://github.com/edycutjong/BagOS/actions/workflows/ci.yml)
  [![MCP Registry](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fregistry.modelcontextprotocol.io%2Fv0%2Fservers%3Fsearch%3Dbagos%26version%3Dlatest&query=%24.servers%5B0%5D.server.version&prefix=v&label=MCP%20Registry&color=000000&style=for-the-badge)](https://registry.modelcontextprotocol.io/?q=bagos)
  [![License](https://img.shields.io/badge/License-MIT-yellow)](LICENSE)
</div>

## ๐Ÿš€ Install

```bash
npx bagos-mcp-server
```

**Claude Desktop** โ€” `~/Library/Application Support/Claude/claude_desktop_config.json`
(macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):

```json
{
  "mcpServers": {
    "bagos": {
      "command": "npx",
      "args": ["-y", "bagos-mcp-server"],
      "env": {
        "BAGS_API_KEY": "your-key-here"
      }
    }
  }
}
```

**Claude Code**:

```bash
claude mcp add bagos --env BAGS_API_KEY=your-key-here -- npx -y bagos-mcp-server
```

Restart the client, then ask it: *"show me the top Bags creators"*. The server prints a
configuration report to stderr on startup; if something is missing it tells you which
variable and why.

An API key alone gives you 8 of the 11 read tools. Three of them โ€” `bags_heartbeat`,
`bags_get_claimable_fees` and `bags_authenticate` โ€” report on *your* wallet, so they also
need `BAGS_KEYPAIR_PATH` and will error without it. Writes stay off until you configure
them ([Getting Started](#-getting-started)).

## ๐Ÿ”’ Why this is safe to hand an assistant

- **Nothing signs on the first call.** A write tool answers with a preview and a
  single-use token fingerprinted to those exact arguments; nothing reaches the chain
  until you call again with it.
- **Hard SOL caps.** 0.1 per transaction and 1 per session by default, refused before
  the Bags SDK is called.
- **Devnet by default.** Writes are mainnet-only, so an unconfigured install cannot
  spend real money.

Full threat model, disclosure policy and the limits of each control:
**[.github/SECURITY.md](.github/SECURITY.md)**.

<details>
<summary><strong>โš ๏ธ If you used 1.x โ€” v2.0.0 corrected a serious defect</strong></summary>

<br>

> **v2.0.0 corrects a serious defect.** In 1.x the write tools built
> transactions, discarded them, and reported success โ€” nothing was ever signed
> or submitted. If you used 1.x and believed a trade or claim executed, it did
> not. See [CHANGELOG.md](CHANGELOG.md).
>
> **2.x is live on npm** and is what `npx bagos-mcp-server` installs โ€” see the
> [latest release](https://github.com/edycutjong/BagOS/releases/latest). Every release ships with
> [npm provenance](https://www.npmjs.com/package/bagos-mcp-server#provenance) โ€”
> the tarball is cryptographically attested to this repository and the commit that built it.
> 1.x is deprecated on npm. If you are still on it, upgrade.

</details>

<div align="center">
  <img src="docs/readme-hero-animated.svg"
       alt="BagOS โ€” gates every AI-initiated Solana spend: the amber-held swap turns green only when its confirmed signature lands on chain"
       width="100%">
</div>

---

## ๐Ÿ“ฆ Where it's listed

  [![npm](https://img.shields.io/npm/v/bagos-mcp-server?style=for-the-badge&color=CB3837&logo=npm&logoColor=white&label=npm)](https://www.npmjs.com/package/bagos-mcp-server)
  [![MCP Registry](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fregistry.modelcontextprotocol.io%2Fv0%2Fservers%3Fsearch%3Dbagos%26version%3Dlatest&query=%24.servers%5B0%5D.server.version&prefix=v&label=MCP%20Registry&color=000000&style=for-the-badge)](https://registry.modelcontextprotocol.io/?q=bagos)
  [![Smithery](https://img.shields.io/badge/Smithery-edycutjong%2Fbagos-A855F7?style=for-the-badge)](https://smithery.ai/servers/edycutjong/bagos)
  [![GitHub Packages](https://img.shields.io/badge/GitHub-Packages-181717?style=for-the-badge&logo=github&logoColor=white)](https://github.com/edycutjong/BagOS/pkgs/npm/bagos-mcp-server)

  [![Live Site](https://img.shields.io/badge/๐ŸŒ_bagos.edycu-.dev-0ea5e9?style=for-the-badge)](https://bagos.edycu.dev)
  [![Pitch Deck](https://img.shields.io/badge/๐ŸŽค_Pitch-Deck-f59e0b?style=for-the-badge)](https://bagos.edycu.dev/deck/)
  [![Run Receipts](https://img.shields.io/badge/๐Ÿ“Š_Run-Receipts-06b6d4?style=for-the-badge)](DEMO.md)
  [![Security Policy](https://img.shields.io/badge/๐Ÿ”_Security-Policy-ef4444?style=for-the-badge)](.github/SECURITY.md)
  [![Changelog](https://img.shields.io/badge/๐Ÿ“‹_Change-log-8b5cf6?style=for-the-badge)](CHANGELOG.md)

  ![MCP](https://img.shields.io/badge/MCP-stdio_+_HTTP-000000?style=flat)
  ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat&logo=typescript&logoColor=white)
  ![Solana](https://img.shields.io/badge/Solana-9945FF?style=flat&logo=solana&logoColor=white)
  ![Jest](https://img.shields.io/badge/Jest-100%25_coverage-C21325?style=flat&logo=jest&logoColor=white)
  [![Publish](https://github.com/edycutjong/BagOS/actions/workflows/publish.yml/badge.svg)](https://github.com/edycutjong/BagOS/actions/workflows/publish.yml)
  [![CodeQL](https://github.com/edycutjong/BagOS/actions/workflows/codeql.yml/badge.svg)](https://github.com/edycutjong/BagOS/actions/workflows/codeql.yml)
  [![Release](https://img.shields.io/github/v/release/edycutjong/BagOS?color=8b5cf6&logo=github&label=release)](https://github.com/edycutjong/BagOS/releases/latest)

---

## ๐Ÿ’ก The Problem & Solution

### The Problem

An MCP server that can move money gives an AI assistant a signing key. The assistant
decides, and the transaction is already on chain by the time a human reads about it.
Nothing in the protocol makes the model pause, and nothing bounds what a single
misunderstood instruction can spend.

### The Solution

[Bags](https://bags.fm) is a Solana token launchpad whose defining feature is
**creator fee sharing**: a token's trading fees are split on-chain between the people who
launched and promote it, claimable at any time. That makes "who earns from this token,
how much is claimable, and claim it" a real workflow โ€” and the one BagOS automates.

BagOS lets an AI assistant read Bags/Solana token data and โ€” with explicit
confirmation โ€” execute swaps and claim creator fees from your wallet. Writes are off
unless you configure them, they are mainnet-only, and the first call to a write tool
signs nothing: it returns a preview and a single-use token that only authorizes the
exact arguments it was issued for.

---

## ๐Ÿ—๏ธ Architecture & Tech Stack

<img src="docs/architecture.svg"
     alt="BagOS architecture: an MCP client speaks stdio or Streamable HTTP to the BagOS MCP server, which exposes 11 read tools, 1 gated tool and 2 write tools. Every write passes token gate, spend caps, confirmation, simulate, sign, send and confirm before reaching the Bags SDK and Solana."
     width="100%">

Every write goes through this:

```
token gate โ†’ spend caps โ†’ confirmation โ†’ simulate โ†’ sign โ†’ send โ†’ confirm
```

| Tool | Type | What it does |
|---|---|---|
| `bags_heartbeat` | read | Server status and wallet reachability |
| `bags_get_token_analytics` | read | Lifetime fee data for a token mint |
| `bags_get_creators` | read | Top token creators by lifetime fees |
| `bags_get_trade_quote` | read | Price quote for a swap. Does not trade. |
| `bags_get_claimable_fees` | read | Fees currently claimable by your wallet |
| `bags_get_partner_stats` | read | Partner config claim statistics |
| `bags_get_token_claim_stats` | read | Per-creator claim totals โ€” the royalty roster with amounts claimed |
| `bags_get_token_claim_events` | read | The claim audit trail for a token, paginated |
| `bags_get_token_creators` | read | Who shares a token's fees, and in what proportion |
| `bags_resolve_launch_wallet` | read | Social handle (twitter/tiktok/kick/github) โ†’ fee-share wallet |
| `bags_authenticate` | read | Verify wallet ownership via Ed25519 signature |
| `bags_prepare_token_metadata` | gated | Creates token info + metadata. **Does not launch a token.** |
| `bags_execute_trade` | **write** | Swap tokens. Signs and submits. |
| `bags_claim_fees` | **write** | Claim creator/LP fees. Signs and submits. |

`bags_prepare_token_metadata` reserves a mint and uploads metadata. Completing a
launch also requires a Meteora fee-share config, whose fee-claimer split has to
be your decision โ€” so this server does not implement that step rather than
guessing at it. Finish the launch at [bags.fm](https://bags.fm).

---

## ๐Ÿ” Write Tools & Spend Controls

Writes are off unless you configure them, and they are mainnet-only.

**Bags has no devnet deployment.** Its API endpoint and its Meteora/fee-share
program IDs are all mainnet. This server nonetheless defaults to **devnet**, so
an unconfigured install cannot spend real money. Calling a write tool on devnet
returns an explanation, not a cryptic program error.

The first call to a write tool **signs nothing**. It returns a preview and a
single-use token:

```
โš ๏ธ  CONFIRMATION REQUIRED โ€” nothing has been signed or sent.

Action:  Swap 0.05 of So1111โ€ฆ1112
         for       EkJuyYโ€ฆdBAGS
         expect    4823917722 (min 4679199990)
         slippage  3%
         network   ๐Ÿ”ด MAINNET โ€” real funds

Spend:   0.05 SOL
Caps:    0.1 SOL/tx ยท 0/1 SOL used this session

To execute, call bags_execute_trade again with the identical arguments plus:
  confirm: "kR3nT9xQm2vP"
```

The token is a fingerprint of the tool name plus the exact arguments, so one
issued for a 0.01 SOL swap cannot authorize a 10 SOL one. It expires in five
minutes and is consumed on every outcome, so it cannot be replayed.

On success you get a real signature and explorer link โ€” never a success message
for a transaction that did not land.

Set `BAGS_ALLOW_UNCONFIRMED=true` to skip the preview. Spend caps still apply.

**The caps only bind on SOL.** A swap whose input is some other token cannot be
valued in SOL, so no cap can limit it. Those swaps are refused by default; set
`BAGS_ALLOW_UNCAPPED_TOKEN_SWAPS=true` to permit them, and the preview will say
plainly that the trade is uncapped.

---

## โ›“๏ธ Live Deployment

A real transaction, landed through the production write path and re-fetched from the
chain rather than trusted from a return value:

| | |
|---|---|
| **Signature** | [`2kvu25xWโ€ฆU5Dm`](https://explorer.solana.com/tx/2kvu25xWAjqCB3wuNzwMRcN2RMqqfYN6TeJjnA888YtCqNJi9EU9CHSxynkq5QdM499e6yKbXYAwXUbzDKY9U5Dm?cluster=devnet) |
| **Cluster** | devnet |
| **Slot** | 484219564 |
| **Status** | `err: null` |
| **Captured** | 2026-08-16 |

Reproduce it yourself with `npm run proof:devnet` โ€” it funds a throwaway keypair from the
faucet, pushes a transfer through the same simulate โ†’ sign โ†’ send โ†’ confirm path the write
tools use, then re-fetches the signature from the chain. A function returning `success` is
a claim; a signature you can open on an explorer is evidence. Full method in
[DEMO.md](DEMO.md).

---

## ๐Ÿ“Š Engineering Rigor

418 tests. The bypass tests around the spend caps and the confirmation step are
load-bearing; treat a change there as a security change. They were checked by hand
against deliberate mutations: deleting the cap guard, the confirmation check, the
decimals lookup, or the spend recorder each makes the suite fail. That was a manual
exercise, not an automated mutation-testing stage โ€” there is no mutation config in
this repo to re-run.

| Layer | Status | Details |
|---|---|---|
| **Real default path** | โœ… | No kill-switch flag in any documented command. `USE_MOCK_DATA` defaults **off**; when on, it affects only the `bags_get_claimable_fees` tool, stamping `โš ๏ธ [MOCK DATA ENABLED]` on that tool's own response. The other 13 tools ignore it. Live-run receipts in [DEMO.md](DEMO.md) |
| Code quality | โœ… | ESLint + `tsc --noEmit`, both clean |
| Unit testing | โœ… | Jest, 418 tests / 19 suites, **100%** statements ยท branches ยท functions ยท lines, enforced |
| High-signal tests | โœ… | Mutation-checked cap/confirmation bypass tests ยท a leak-channel regression test (the API key used to be echoed into tool output) ยท network-mismatch refusal |
| Security | โœ… | CodeQL SAST ยท Dependabot SCA ยท gitleaks over full history (`fetch-depth: 0`) ยท secret scanning + push protection on ยท `npm audit` in CI as a **ratchet** โ€” see below |
| Dependency debt | โš ๏ธ | **6 advisories, 0 critical** โ€” down from 90. Everything patchable was cleared with version-scoped `overrides` (see [`package.json`](package.json)). The 6 that remain are **one** root cause, `bigint-buffer` [GHSA-3gc7-fjrx-p6mg](https://github.com/advisories/GHSA-3gc7-fjrx-p6mg), counted once at each level of the chain it travels up to `@bagsfm/bags-sdk`. No patched `bigint-buffer` exists โ€” 1.1.5 is the installed version, the latest version, and vulnerable. CI blocks any critical and any increase over [`.audit-baseline.json`](.audit-baseline.json). **Note:** npm honours `overrides` only in a root project, so these protect this repo and CI, not consumers of the published package. |
| CI | โœ… | 4 stages (Quality โ†’ Security โˆฅ Test โ†’ Build) with `cancel-in-progress` concurrency; Node 22 + 24 matrix; packaged-artifact and entrypoint checks |
| CD | โœ… | Release โ†’ tarball audit โ†’ `npm publish --provenance` โ†’ deprecate the superseded version. A second workflow submits `server.json` to the MCP registry via OIDC. Both gated on the full CI suite. 1.0.0 is deprecated on npm with a pointer to the defect it carried. |
| On-chain proof | โœ… | `npm run proof:devnet` lands a real transaction through the production write path and re-fetches it from the chain. Captured 2026-08-16: [`2kvu25xWโ€ฆU5Dm`](https://explorer.solana.com/tx/2kvu25xWAjqCB3wuNzwMRcN2RMqqfYN6TeJjnA888YtCqNJi9EU9CHSxynkq5QdM499e6yKbXYAwXUbzDKY9U5Dm?cluster=devnet), slot 484219564, `err: null`. Anyone can re-verify it โ€” see [DEMO.md](DEMO.md) |
| Community standards | โœ… | Code of Conduct ยท Contributing ยท Security policy ยท issue + PR templates |

E2E browser tests and Lighthouse budgets are deliberately absent: this is a stdio/HTTP MCP
server with no web UI, so both would measure nothing. The nearest end-to-end coverage is
`npm run demo`, which drives five read tools โ€” `bags_heartbeat`, `bags_get_creators`,
`bags_get_trade_quote`, `bags_get_partner_stats` and `bags_authenticate` โ€” over real MCP
JSON-RPC against the live API. The remaining read tools are covered by unit tests only.

---

## ๐Ÿš€ Getting Started

### Prerequisites

You need a Bags API key from [dev.bags.fm](https://dev.bags.fm). That alone
enables the read-only tools. For trading and fee claims you also need a Solana
keypair file and the gating token โ€” see
[Write Tools & Spend Controls](#-write-tools--spend-controls).

### Enabling writes

Writes stay off until all of these are set:

```json
{
  "mcpServers": {
    "bagos": {
      "command": "npx",
      "args": ["-y", "bagos-mcp-server"],
      "env": {
        "BAGS_API_KEY": "your-key-here",
        "BAGS_NETWORK": "mainnet",
        "BAGS_KEYPAIR_PATH": "~/.config/bags/keypair.json",
        "BOS_TOKEN_MINT": "Feqmy64uNvK198MAWFC5ujRnzif6kM9wKonTX2t3BAGS",
        "BAGS_MAX_SOL_PER_TX": "0.1",
        "BAGS_MAX_SOL_PER_SESSION": "1.0"
      }
    }
  }
}
```

> โš ๏ธ **`BOS_TOKEN_MINT` currently does two jobs.** It is the token the gate requires you
> to **hold** ($BOS, above), *and* it is the default **output** mint for a swap that does
> not name one (`ExecuteTrade.ts`). Those want opposite properties โ€” a gate wants a token
> you hold, a swap target wants a token with liquidity, and $BOS has almost none. Always
> pass `outputMint` explicitly on `bags_execute_trade` rather than relying on the default.
> Splitting these into two variables is tracked as a known issue.

### Configuration

| Variable | Required | Default | Notes |
|---|---|---|---|
| `BAGS_API_KEY` | yes | โ€” | From [dev.bags.fm](https://dev.bags.fm) |
| `BAGS_NETWORK` | no | `devnet` | `devnet` or `mainnet`. Writes need mainnet. |
| `SOLANA_RPC_URL` | no | public cluster RPC | Must agree with `BAGS_NETWORK` or the server refuses to start |
| `BAGS_KEYPAIR_PATH` | writes only | `~/.config/bags/keypair.json` | JSON byte-array keypair file |
| `BOS_TOKEN_MINT` | writes only | โ€” | Gating token mint |
| `BOS_REQUIRED_BALANCE` | no | `10000` | Minimum gating-token balance. `0` disables the gate (any balance passes); a non-numeric value is refused at startup rather than silently defaulting. |
| `BAGS_MAX_SOL_PER_TX` | no | `0.1` | Per-transaction spend cap |
| `BAGS_MAX_SOL_PER_SESSION` | no | `1.0` | Per-process spend cap |
| `BAGS_ALLOW_UNCONFIRMED` | no | `false` | Skip the confirmation step |
| `BAGS_ALLOW_UNCAPPED_TOKEN_SWAPS` | no | `false` | Permit swaps whose input is not SOL. The caps are SOL-denominated and **cannot limit these**. |
| `HELIUS_RPC_URL` | no | โ€” | Alias for `SOLANA_RPC_URL`, read only if that is unset |
| `USE_MOCK_DATA` | no | `false` | `true` makes `bags_get_claimable_fees` return **fabricated** balances, stamped as such. No other tool is affected. |
| `BAGS_API_URL` | no | `https://public-api-v2.bags.fm/api/v1` | Override the Bags API base URL used by `bags_authenticate`. Must be `https` on `bags.fm` or a subdomain, because the tool signs what this endpoint sends. |
| `BAGS_ALLOW_CUSTOM_API_URL` | no | `false` | `true` lifts the `bags.fm` restriction on `BAGS_API_URL`. Only for a server you control. |
| `BAGS_ENV_FILE` | no | โ€” | Absolute path of an env file to load. A `.env` in the working directory is **never** loaded on its own, because an MCP client's working directory is whatever project you have open. |
| `PORT` | no | `3050` | HTTP listener port. Only read when started with `--http`. |

---

## ๐Ÿงช Testing & CI

```bash
npm ci
npm run ci            # lint + typecheck + tests with coverage
npm run dev           # stdio server with watch
npm run inspector     # MCP Inspector against the built server
npm run proof:devnet  # land a real devnet transaction through the write path
```

`proof:devnet` uses a persisted throwaway keypair (`.proof/`, gitignored), funds
it from the devnet faucet when needed, and pushes a transfer through the same
simulate/sign/send/confirm path the write tools use โ€” then re-fetches the
signature from the chain instead of trusting the function's return value. That
last step is the whole point: a function returning `success` is a claim, and a
signature you can open on an explorer is evidence.

---

## ๐Ÿ“ฝ๏ธ Demo Materials

- **[DEMO.md](DEMO.md)** โ€” receipts from a real run against the live Bags mainnet API:
  7 scenarios, 8 steps, per-step latency, plus the network-mismatch guard and the token
  gate caught refusing a write.
- **[docs/examples.md](docs/examples.md)** โ€” prompts you can type at your assistant and
  what each should do.
- **[docs/KNOWN_ISSUES.md](docs/KNOWN_ISSUES.md)** โ€” what is currently broken and why,
  including what has already been ruled out. Open advisories are explained there rather
  than left for you to discover.

---

## ๐Ÿ›ก๏ธ Security

Read [SECURITY.md](.github/SECURITY.md) before pointing a funded wallet at this.

Summary: your private key is read from disk, used to sign, and never logged,
never sent anywhere, and never placed in an error message. Tool errors return
a message only โ€” no stack traces โ€” with key-shaped strings redacted. The startup
report strips credentials from the RPC URL. If the RPC endpoint's cluster
disagrees with `BAGS_NETWORK`, the server refuses to start rather than sign
mainnet transactions under a devnet banner.

The same rule now covers the **Bags API key**: `bags_authenticate` writes it to
`~/.config/bags/credentials.json` and echoes only a four-character tail. It used
to print the key in full, which published a live credential into the assistant's
context and every transcript downstream of it. If you ran `bags_authenticate` on
a version before this change, rotate that key at [dev.bags.fm](https://dev.bags.fm).

### Known limits of these controls

Two are worth stating here rather than leaving in SECURITY.md:

- **HTTP mode has no authentication.** Started with `--http`, the server listens on
  `0.0.0.0` with permissive CORS and no auth, so any caller that can reach the port can
  invoke the write tools โ€” sharing one spend counter. **Do not run HTTP mode on a funded
  wallet.** stdio is the default and the only transport this project recommends; it is
  also why the Smithery listing is stdio-only rather than hosted.
- **The session cap counts a trade whose outcome is unknown.** If confirmation times out
  after sending, the spend is counted (it may still land) and the error carries the
  signature. Restart the server to reset the counter once you have checked it on an explorer.

Report vulnerabilities via
[GitHub security advisories](https://github.com/edycutjong/BagOS/security/advisories/new).

---

## ๐Ÿ“„ License

MIT โ€” see [LICENSE](LICENSE).

TDQS

A3.9/5.0

Scored across 14 tools

Disambiguation4/5

Each tool targets a distinct operation or resource, and the write vs read-only distinction is clear from descriptions. Minor overlap remains between get_token_analytics and get_token_claim_stats (both expose claim stats) and between get_creators and get_token_creators.

Naming Consistency4/5

All tools share the bags_ prefix and mostly follow a verb_object pattern (get_, execute_, claim_, prepare_). Minor deviations include bags_heartbeat, which is a noun, and bags_authenticate, which is a bare verb.

Tool Count5/5

14 tools is well within the ideal range and covers authentication, trading, fee claiming, analytics, and token metadata preparation without feeling bloated. Each tool serves a clear purpose in the overall workflow.

Completeness4/5

The surface covers the main read-only analytics, trade quoting/execution, fee claims, and metadata preparation workflows. The most notable gap is that a prepared token launch cannot be executed through the server, but the core trade and fee lifecycle is otherwise well covered.

Maintenance

ActivityMaintained
ResponsivenessSlow