Metadata-Version: 2.5
Name: payaion-mcp
Version: 1.2.2
Summary: MCP server for Payaion — file uploads, storage folders, marketplace listings, and paid downloads for AI agents on Base mainnet (USDC).
Project-URL: Homepage, https://payaion.com
Project-URL: Documentation, https://payaion.com/docs
License-Expression: MIT
License-File: LICENSE
Keywords: ai-agents,base,claude,cursor,file-transfer,file-upload,marketplace,mcp,mcp-server,payaion,payments,usdc,x402
Classifier: Development Status :: 4 - Beta
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.11
Requires-Dist: httpx>=0.27.0
Requires-Dist: mcp<3,>=2
Requires-Dist: pydantic>=2
Description-Content-Type: text/markdown

<!-- mcp-name: io.github.defdone/payaion -->

# payaion-mcp

MCP server for [Payaion](https://payaion.com) — file uploads, marketplace listings, and paid downloads for AI agents on **Base mainnet** (USDC).

## Quick Start

No API key needed to start. Add the server and the transfer tools work
immediately — uploads run as a guest: 100 MB per file, 24-hour link.

```json
{
  "mcpServers": {
    "payaion": {
      "command": "uvx",
      "args": ["payaion-mcp"]
    }
  }
}
```

Restart your MCP client (Cursor, Claude Desktop, …) and ask it to transfer a file.

### Adding a key

A key raises the limits (500 MB per file, 2 GB stored, 28-day links) and is
required to list on the marketplace or buy. Selling a priced link works without
one (pass `payoutAddress`, see below). Two ways to get one:

- **From the dashboard** — connect a wallet at
  [payaion.com/dashboard](https://payaion.com/dashboard).
- **From the agent itself** — sign a message with a wallet it holds locally, no
  browser involved. Three requests, documented at
  [payaion.com/docs/agent-flow](https://payaion.com/docs/agent-flow).

```json
{
  "mcpServers": {
    "payaion": {
      "command": "uvx",
      "args": ["payaion-mcp"],
      "env": { "PAYAION_API_KEY": "pya_…" }
    }
  }
}
```

Keys minted by signature carry upload scopes only. That is enough to price your
own uploads: any key with `upload:create` can set a price, and the money always
goes to the account's payout wallet, never to an address the agent picks.
Listing on the marketplace needs a key with the `marketplace:list` scope. No API
key can read or change the payout address: set it under Earnings and use a cold
wallet.

### Hosted (remote) server

Payaion runs a hosted server at `https://mcp.payaion.com/mcp` over Streamable
HTTP, for clients on `2026-07-28` and on older revisions. It requires an API key:
send `Authorization: Bearer <your pya_ API key>` with every request. There is no
keyless mode over HTTP and no OAuth yet. To start without a key, run the package
over stdio as above.

For clients that accept a URL plus headers:

```json
{
  "mcpServers": {
    "payaion": {
      "url": "https://mcp.payaion.com/mcp",
      "headers": { "Authorization": "Bearer <your pya_ API key>" }
    }
  }
}
```

## Install

```bash
pip install payaion-mcp
```

```bash
uvx payaion-mcp --help
```

## Available Tools

| Tool                       | Description                                                                      |
| -------------------------- | -------------------------------------------------------------------------------- |
| `transfer`                 | One-shot file transfer with optional pricing & marketplace listing (recommended) |
| `upload_file`              | Upload a local file or base64 content (returns upload ID)                        |
| `upload_from_url`          | Upload a file from a public URL                                                  |
| `get_upload_status`        | Check the processing status of an upload                                         |
| `get_download_url`         | Get a fresh shareable download URL for a completed upload                        |
| `list_on_marketplace`      | List an uploaded file on the public Payaion marketplace                          |
| `delist_from_marketplace`  | Take your listing down; the file and existing buyers' access stay                |
| `browse_marketplace`       | Search and browse active marketplace listings                                    |
| `get_payment_requirements` | Fetch price and payment terms for a listing before buying                        |
| `purchase_asset`           | Finalize the purchase of a paid listing (real USDC)                              |
| `list_storage`             | List folders (with paths and file counts), the files in one, and storage used   |
| `create_folder`            | Create a folder, optionally nested inside another (up to 8 levels)              |
| `update_folder`            | Rename a folder, move it under a different parent, or both                      |
| `delete_folder`            | Delete a folder and its subfolders — the files inside move back to the root     |
| `move_file`                | File an upload into a folder, or back to the storage root                       |

`transfer`, `upload_file`, `upload_from_url`, `get_upload_status` and
`browse_marketplace` work without a key. `list_on_marketplace`,
`delist_from_marketplace`, `get_payment_requirements` and `purchase_asset`
require one, and so do the five folder tools, since a keyless caller has no
storage to organise.

`get_download_url` needs a key: proving you own an upload requires an identity, and
a keyless caller has none. `get_upload_status` still works without one. This costs
a keyless agent nothing: the share link belongs to the file, not the caller, so
keep the `downloadUrl` from the upload response and nothing is lost.

### Storage folders

Folders group what an account already holds. They are metadata only: moving or
deleting one never touches a blob, never deletes a file, and never changes a
share link, a price or an expiry. Deleting a folder drops its subfolders and
returns the files inside to the storage root — deleting a file stays a separate,
explicit action. Names are unique among siblings, nesting stops at 8 levels, and
folders count against nothing: the plan quota measures uploaded bytes.

Reading needs the `upload:status` scope, every write needs `upload:create`. A
keyless (guest) caller has no storage, so these five tools require a key.

### Upload Methods

`transfer` takes exactly one of three inputs; `upload_file` takes `filePath` or
`content`, and `upload_from_url` takes `url`:

- **`filePath`**: Local file path (most efficient, zero tokens)
- **`url`**: Public URL for the server to fetch
- **`content`**: Base64-encoded file content (fallback)

### Pricing & Marketplace

```yaml
pricePerDownload: 0.50  # USD, charged in USDC on Base
payoutAddress: "0x..."  # only without an API key — see below
```

With an API key, earnings go to the payout wallet set in your dashboard and
`payoutAddress` is ignored. Without a key, it is the only way to get paid: pass
the wallet the 95% creator share should land in. Payments are final, so a
mistyped address is unrecoverable, and an exchange deposit address usually will
not credit a Base transfer arriving from a contract — use a wallet you control.

To list on the marketplace, use `transfer` with listing metadata:

```yaml
listingTitle: "Dataset Q1 2026"        # 3–120 chars
listingDescription: "Cleaned Q1 2026 sales data — deduplicated, currency-normalised, with a column dictionary."
listingCategory: "datasets"            # reports | datasets | code | media | models | prompts | other
listingTags: ["sales", "q1"]           # max 8, alphanumeric + hyphens
```

`listingDescription` must be 40–500 characters — shorter descriptions are rejected.
With a key, a price above 0 requires a wallet connected to your account.

When a listing was requested, `transfer` returns `url`, the `/m/<hash>` market
page. If the daily listing limit is reached it returns `listingSkipped`
(`code: listing_limit_reached`, with `retryAfterSec`) instead: the file is still
uploaded, just not listed, and you can list it later.

### Buying

If `purchase_asset` reports that settlement timed out, retry with the same
`paymentPayload`: it cannot be charged twice. Never sign a new payment for a
retry. If the response says the payment settled but the receipt failed, do not
pay again; email contact@payaion.com with the transaction hash.

## Environment Variables

| Variable               | Required | Default                       | Description                                      |
| ---------------------- | -------- | ----------------------------- | ------------------------------------------------ |
| `PAYAION_API_KEY`      | No       | unset                         | Raises limits; needed to list on the marketplace or buy. Without it you upload as a guest |
| `PAYAION_API_BASE_URL` | No       | `https://api.payaion.com` | Payaion API endpoint, leave unset in normal use |

## Limits

Enforced per request against the account's current plan, so an expired Pro is back
on Basic limits immediately.

| | Guest (no key) | Basic | Pro |
| --- | --- | --- | --- |
| Per file | 100 MB | 500 MB | 1 GB |
| Total storage | — (per-file only) | 2 GB | 20 GB |
| Link lifetime | 24 hours (14 days when priced) | 28 days | while subscribed |
| Live files | — | 200 | 2,000 |
| Marketplace / day | cannot list or buy (5 priced uploads per IP) | 5 list · 10 buy | 50 list · 100 buy |

Uploads are capped at 10/min per key (15 per account, 30 per IP) and 500/day per
account (800 per IP), with 2 in flight per key and 3 per account. Exceeding a size
or storage limit is a final answer: retrying or splitting the file will not get
around it.

## CLI

```bash
payaion-mcp --help
payaion-mcp --version
```

## Transport

This package runs over stdio, same as the Node package: the client starts the
server as a child process. For remote clients, Payaion operates the hosted
server at `https://mcp.payaion.com/mcp`, which requires an API key (see
[Hosted (remote) server](#hosted-remote-server)).

## Protocol

Speaks both MCP eras from the same server, so no client has to move first:

| Client speaks | What happens |
| --- | --- |
| `2026-07-28` (stateless, no handshake) | Served natively — no `initialize`, no session id |
| `2025-11-25` and earlier | Served through the `initialize` handshake as before |

This matters because the eras do not degrade into each other: a client that only
speaks the new revision cannot fall back, and a client that only speaks the old
one cannot fall forward. Serving both is the only arrangement where nobody
breaks. Session ids, `GET`/`DELETE` on the endpoint, and SSE stream resumption
are gone with the new revision — the HTTP endpoint answers `405` for them.

## Also Available

- **Node.js:** `npx -y @payaion/mcp` ([npm](https://www.npmjs.com/package/@payaion/mcp))
- **Docs:** [payaion.com/docs](https://payaion.com/docs)

## License

MIT
