Solz.app Documentation

Solz MCP and API (under development)

Connect an AI agent or your own app to Solz on www.solz.app. Build and simulate Solana token actions, then sign them with your own wallet.

The Solz MCP server and API are under development, so tools, limits and these instructions can change. Start with "network": "devnet", because every tool defaults to mainnet, where a signed transaction spends real SOL.

What can the Solz MCP server do?

It gives an AI agent 22 tools for creating tokens, launching, managing authorities and liquidity, and running bundled trades, at the same prices as the site. A tool builds and simulates the transactions, then a wallet you control signs them. Your private key never goes through MCP, and Solz never sees it.

Prerequisites

  • A Solana wallet to create your API key with. Phantom, Solflare and Backpack can all sign the message.
  • An MCP client, or anything that can send an HTTP request.
  • SOL on the network you're using, for the fees and network costs of what you sign.

Steps to connect

  1. Create an API key. Go to www.solz.app/agent, connect your wallet, click Create API key and sign the message. Copy the key straight away, because it won't be shown again. Creating a new key replaces the old one.
  2. Add Solz to your client. Use the link or command for your client below, with your key in place of YOUR_API_KEY.
  3. Ask your agent to list the tools. You should see all 22. If the key is wrong, you'll get an unauthorized error.

Add Solz to your client

Cursor and VS Code install from a link. Each link fills in the address and a placeholder, so you then replace YOUR_API_KEY with your key in the server's settings. Other clients take a command or a short settings entry.

ClientHow to add it
CursorAdd Solz to Cursor
VS CodeAdd Solz to VS Code
Claude and Claude DesktopOpen Claude connectors and follow the steps below
Claude CodeRun the command below
CodexAdd the settings below
Gemini CLIRun the command below
Other MCP clientsUse the settings below

Claude and Claude Desktop: on the connectors page, click + Add, then Add custom connector. Enter https://www.solz.app/api/mcp, choose No sign in, and add a request header named Authorization with the value Bearer YOUR_API_KEY. On Team and Enterprise plans, an Owner adds connectors in the organization settings.

Claude Code: run this, then type /mcp in Claude Code to check the connection:

claude mcp add --transport http solz https://www.solz.app/api/mcp --header "Authorization: Bearer YOUR_API_KEY"

Codex: add this to ~/.codex/config.toml, and set SOLZ_API_KEY to your key in your environment:

[mcp_servers.solz]
url = "https://www.solz.app/api/mcp"
bearer_token_env_var = "SOLZ_API_KEY"

Gemini CLI: run this:

gemini mcp add --transport http --header "Authorization: Bearer YOUR_API_KEY" solz https://www.solz.app/api/mcp

Other MCP clients: most take these settings, though the exact names can differ, so check your client's own guide:

{
  "mcpServers": {
    "solz": {
      "url": "https://www.solz.app/api/mcp",
      "headers": { "Authorization": "Bearer YOUR_API_KEY" }
    }
  }
}

ChatGPT's custom connectors don't take an API key, so they can't connect to this server yet. Use Codex instead.

Keep your key out of shared files and chats. It lets someone build requests and use your rate limits, but it can't sign anything or move funds.

Using REST instead

Each tool is also a POST endpoint, and takes the same inputs. The list of tools and their inputs, as JSON Schema, doesn't need a key.

GET  https://www.solz.app/api/v1/tools

POST https://www.solz.app/api/v1/tools/<tool>
Authorization: Bearer YOUR_API_KEY
Content-Type: application/json

How does a tool run from start to finish?

A tool builds the transactions, simulates them, and returns them unsigned with an approval link, the simulated cost and the service fee. Nothing happens on Solana at this point. A wallet then signs, either through the approval link or in your own code, and you follow the result with get_status.

Here's a full run that creates Example Token on mainnet. The response is real: it was built and simulated on November 2, 2026, and nothing was signed or sent. Addresses, IDs and the transaction are placeholders. To practise, set network to devnet, where the costs can differ.

1. Check the fee. Call estimate_cost. This example hosts its own metadata, which costs less:

{ "action": "create_token", "self_hosted_metadata": true }
{ "action": "create_token", "service_fee_sol": 0.03 }

2. Build it. Call create_token. Host your image and metadata first with upload_metadata, or use your own URL:

{
  "network": "mainnet",
  "wallet": "YOUR_WALLET_ADDRESS",
  "name": "Example Token",
  "symbol": "EXMPL",
  "metadata_uri": "https://example.com/exmpl.json",
  "supply": 1000000000,
  "max_total_sol": 0.1
}

3. Read the response. Check summary, risk, service_fee_sol and simulation.total_cost_sol before anyone signs. Here, the total of 0.04637994 SOL includes the 0.03 SOL service fee:

{
  "request_id": "EXAMPLE_REQUEST_ID",
  "wallet": "YOUR_WALLET_ADDRESS",
  "network": "mainnet",
  "summary": [
    "Create the spl token \"Example Token\" (EXMPL)",
    "Mint 1000000000 tokens to YOUR_WALLET_ADDRESS"
  ],
  "risk": "normal",
  "service_fee_sol": 0.03,
  "simulation": { "ok": true, "total_cost_sol": 0.04637994 },
  "transactions": [
    {
      "transaction": "BASE64_TRANSACTION",
      "needs_signature_from": ["YOUR_WALLET_ADDRESS"]
    }
  ],
  "last_valid_block_height": 442482794,
  "outputs": { "mint": "NEW_TOKEN_ADDRESS" },
  "approval_url": "https://www.solz.app/sign/EXAMPLE_REQUEST_ID",
  "approval_expires_at": "2026-11-02T10:57:28.215Z"
}

4. Sign it. Choose one way:

  • Approval link: open approval_url in a browser. Solz checks the cost again, and you approve in your wallet. It sends the transaction for you, so you don't call submit.
  • Your own code: sign each entry's transaction with the wallets in its needs_signature_from, then call submit with the same request_id and the signed transactions as base64 strings. There are full examples below:
{
  "request_id": "EXAMPLE_REQUEST_ID",
  "signed_transactions": ["BASE64_SIGNED_TRANSACTION"]
}

5. Follow the result. Call get_status with request_id:

statusWhat it meansWhat to do
waiting_for_signatureNobody has signed yetApprove or submit it
expiredThe approval window passed with no signatureRun the tool again for a new request
sendingIt's been sent, and hasn't landed yetCheck again shortly
confirmedEverything landedUse the signatures and outputs, such as the new token address
partialSome transactions landed, and some didn'tCheck each signature's status before you retry, so you don't repeat what landed
failedNothing landedRead send_error if it's there, then run the tool again

How do I sign without sharing a private key?

Approve in your own browser through the approval link, or sign in your own code and call submit. Private keys never go through MCP or to Solz. A link lasts 30 minutes, and it shows what will happen, the wallet, the network, the service fee and the total cost before you approve.

An approval link can combine different kinds of wallet in one request:

  • X Wallets in that browser sign when you click Approve, with no popup.
  • Wallet apps each approve in turn. Solz says Signed so far. Now switch to that wallet and approve.
  • The wrong wallet gets a message naming the wallet the request is for.

Some actions ask for more before you can approve:

  • Actions that can't be undone, such as revoking an authority, need a tick beside I understand this can't be undone.
  • Giving control to another wallet needs you to type CONFIRM.
  • A link someone else created shows a warning naming the wallet that created it. Approve only if you trust whoever sent it.

If the simulation would fail, or the cost goes over your limit, the page won't let you approve. If the cost has changed, it shows the new amount and asks you to approve again.

How do I sign in my own code?

Use any Solana library to sign each entry's transaction with the wallets in its needs_signature_from, then call submit. Keep any signature already on the transaction, such as one from a new token's address. These examples create a token on devnet, and read your keys from the environment.

Set these in your environment first. Never put them in your code, or paste them into an AI chat:

export SOLZ_API_KEY="YOUR_API_KEY"
export WALLET_PRIVATE_KEY="YOUR_BASE58_PRIVATE_KEY"

For a bundle, add every wallet that has to sign to wallets. Each example stops if the simulation fails, or if a transaction needs a wallet it has no key for. A devnet wallet needs devnet SOL for the transaction to land.

JavaScript

Use version 1 of @solana/web3.js. Later versions have a different API. Save this as solz.mjs, and run it with Node.js 20 or later:

npm install @solana/web3.js@1 bs58
node solz.mjs
import { Keypair, VersionedTransaction } from "@solana/web3.js";
import bs58 from "bs58";

const API = "https://www.solz.app/api/v1/tools";
const wallets = [Keypair.fromSecretKey(bs58.decode(process.env.WALLET_PRIVATE_KEY))];

async function callTool(name, input) {
  const response = await fetch(`${API}/${name}`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.SOLZ_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify(input),
  });
  const body = await response.json();
  if (!response.ok) throw new Error(`${body.error.code}: ${body.error.message}`);
  return body;
}

function sign({ transaction, needs_signature_from }) {
  const tx = VersionedTransaction.deserialize(Buffer.from(transaction, "base64"));
  const signers = needs_signature_from.map((address) => {
    const signer = wallets.find((w) => w.publicKey.toBase58() === address);
    if (!signer) throw new Error(`No key for ${address}`);
    return signer;
  });
  tx.sign(signers);
  return Buffer.from(tx.serialize()).toString("base64");
}

const built = await callTool("create_token", {
  network: "devnet",
  wallet: wallets[0].publicKey.toBase58(),
  name: "Example Token",
  symbol: "EXMPL",
  metadata_uri: "https://example.com/exmpl.json",
  supply: 1000000000,
  max_total_sol: 0.1,
});
console.log(built.summary, built.service_fee_sol, built.simulation);
if (!built.simulation.ok) throw new Error(built.simulation.error);

let result = await callTool("submit", {
  request_id: built.request_id,
  signed_transactions: built.transactions.map(sign),
});
while (result.status === "sending") {
  await new Promise((resolve) => setTimeout(resolve, 3000));
  result = await callTool("get_status", { request_id: built.request_id });
}
console.log(result.status, result.outputs, result.signatures);

Python

Save this as solz.py:

pip install solders requests
python solz.py
import base64
import os
import time

import requests
from solders.keypair import Keypair
from solders.message import to_bytes_versioned
from solders.transaction import VersionedTransaction

API = "https://www.solz.app/api/v1/tools"
wallets = [Keypair.from_base58_string(os.environ["WALLET_PRIVATE_KEY"])]


def call_tool(name, payload):
    response = requests.post(
        f"{API}/{name}",
        headers={"Authorization": f"Bearer {os.environ['SOLZ_API_KEY']}"},
        json=payload,
        timeout=120,
    )
    body = response.json()
    if not response.ok:
        raise RuntimeError(f"{body['error']['code']}: {body['error']['message']}")
    return body


def sign(entry):
    tx = VersionedTransaction.from_bytes(base64.b64decode(entry["transaction"]))
    signed_message = to_bytes_versioned(tx.message)
    signatures = list(tx.signatures)
    keys = list(tx.message.account_keys)
    for address in entry["needs_signature_from"]:
        signer = next((w for w in wallets if str(w.pubkey()) == address), None)
        if signer is None:
            raise RuntimeError(f"No key for {address}")
        index = next(i for i, key in enumerate(keys) if str(key) == address)
        signatures[index] = signer.sign_message(signed_message)
    signed = VersionedTransaction.populate(tx.message, signatures)
    return base64.b64encode(bytes(signed)).decode()


built = call_tool("create_token", {
    "network": "devnet",
    "wallet": str(wallets[0].pubkey()),
    "name": "Example Token",
    "symbol": "EXMPL",
    "metadata_uri": "https://example.com/exmpl.json",
    "supply": 1000000000,
    "max_total_sol": 0.1,
})
print(built["summary"], built["service_fee_sol"], built["simulation"])
if not built["simulation"]["ok"]:
    raise RuntimeError(built["simulation"]["error"])

result = call_tool("submit", {
    "request_id": built["request_id"],
    "signed_transactions": [sign(entry) for entry in built["transactions"]],
})
while result["status"] == "sending":
    time.sleep(3)
    result = call_tool("get_status", {"request_id": built["request_id"]})
print(result["status"], result.get("outputs"), result["signatures"])

Rust

Add these to your Cargo.toml, put the code in src/main.rs, and run cargo run:

[dependencies]
base64 = "0.22"
bincode = "1"
serde_json = "1"
solana-keypair = "4"
solana-signer = "4"
solana-transaction = { version = "6", features = ["serde"] }
ureq = { version = "2", features = ["json"] }
use std::{env, error::Error, thread, time::Duration};

use base64::{engine::general_purpose::STANDARD, Engine};
use serde_json::{json, Value};
use solana_keypair::Keypair;
use solana_signer::Signer;
use solana_transaction::versioned::VersionedTransaction;

const API: &str = "https://www.solz.app/api/v1/tools";

fn call_tool(name: &str, input: Value) -> Result<Value, Box<dyn Error>> {
    let key = env::var("SOLZ_API_KEY")?;
    let response = ureq::post(&format!("{API}/{name}"))
        .set("Authorization", &format!("Bearer {key}"))
        .send_json(input);
    match response {
        Ok(ok) => Ok(ok.into_json()?),
        Err(ureq::Error::Status(_, failed)) => {
            let body: Value = failed.into_json()?;
            let text = |field: &str| body["error"][field].as_str().unwrap_or("").to_string();
            Err(format!("{}: {}", text("code"), text("message")).into())
        }
        Err(error) => Err(error.into()),
    }
}

fn sign(entry: &Value, wallets: &[Keypair]) -> Result<String, Box<dyn Error>> {
    let bytes = STANDARD.decode(entry["transaction"].as_str().ok_or("no transaction")?)?;
    let mut tx: VersionedTransaction = bincode::deserialize(&bytes)?;
    let message = bincode::serialize(&tx.message)?;
    let keys = tx.message.static_account_keys().to_vec();
    for address in entry["needs_signature_from"].as_array().ok_or("no signers")? {
        let address = address.as_str().ok_or("bad signer")?;
        let signer = wallets
            .iter()
            .find(|wallet| wallet.pubkey().to_string() == address)
            .ok_or(format!("No key for {address}"))?;
        let index = keys
            .iter()
            .position(|key| key.to_string() == address)
            .ok_or("signer not in transaction")?;
        tx.signatures[index] = signer.sign_message(&message);
    }
    Ok(STANDARD.encode(bincode::serialize(&tx)?))
}

fn main() -> Result<(), Box<dyn Error>> {
    let wallets = vec![Keypair::from_base58_string(&env::var("WALLET_PRIVATE_KEY")?)];

    let built = call_tool(
        "create_token",
        json!({
            "network": "devnet",
            "wallet": wallets[0].pubkey().to_string(),
            "name": "Example Token",
            "symbol": "EXMPL",
            "metadata_uri": "https://example.com/exmpl.json",
            "supply": 1000000000,
            "max_total_sol": 0.1
        }),
    )?;
    println!("{} {} {}", built["summary"], built["service_fee_sol"], built["simulation"]);
    if built["simulation"]["ok"] != true {
        return Err(built["simulation"]["error"].as_str().unwrap_or("Simulation failed").into());
    }

    let signed = built["transactions"]
        .as_array()
        .ok_or("no transactions")?
        .iter()
        .map(|entry| sign(entry, &wallets))
        .collect::<Result<Vec<_>, _>>()?;
    let request_id = built["request_id"].clone();
    let mut result = call_tool(
        "submit",
        json!({ "request_id": request_id, "signed_transactions": signed }),
    )?;
    while result["status"] == "sending" {
        thread::sleep(Duration::from_secs(3));
        result = call_tool("get_status", json!({ "request_id": request_id }))?;
    }
    println!("{} {} {}", result["status"], result["outputs"], result["signatures"]);
    Ok(())
}

Code that signs by itself can spend real SOL with nobody checking. On mainnet, check summary and simulation.total_cost_sol before you sign, and only sign requests you created yourself.

How do I teach an AI agent to use Solz?

Copy the instructions below into your agent. It then sets Solz up for you, shows you how to save your keys securely, shows you the cost, and waits for your approval before anything is signed. You enter your keys yourself in your own terminal, so they never go into the AI chat.

  • Claude Code: save them as .claude/skills/solz/SKILL.md in your project.
  • Codex, Cursor and others: paste them into AGENTS.md, or your client's rules or instructions, without the part between the --- lines.
---
name: solz
description: Create, launch and manage Solana tokens with the Solz MCP server or API. Use when the user asks to create a token, launch on pump.fun or LaunchLab, manage liquidity or authorities, or run bundled trades with Solz.
---

# Solz

Solz builds and simulates Solana transactions. Nothing happens on Solana until a wallet signs them.

Use the `solz` MCP tools if they're connected. Otherwise call `POST https://www.solz.app/api/v1/tools/<tool>` with the header `Authorization: Bearer $SOLZ_API_KEY`. `GET https://www.solz.app/api/v1/tools` lists every tool and its inputs.

Guide and code examples: https://www.solz.app/docs/developers/mcp.mdx

## Set up

Do this when the `solz` tools aren't connected.

### 1. Check for the API key

Run this. It only says whether the key is set, and never shows it:

```bash
[ -n "$SOLZ_API_KEY" ] && echo set || echo missing
```

If it's missing, tell the user to create a key at https://www.solz.app/agent and save it with the commands below in their own terminal. Then wait for them to tell you it's done. Never ask them to paste a key into this chat. If they do, tell them it's exposed and to create a new key, which replaces the old one.

macOS, which keeps it in the Keychain and asks for the key:

```bash
security add-generic-password -U -a "$USER" -s SOLZ_API_KEY -w
echo 'export SOLZ_API_KEY="$(security find-generic-password -a "$USER" -s SOLZ_API_KEY -w)"' >> ~/.zshrc
```

Linux, which keeps it in a file only their user can read:

```bash
mkdir -p ~/.config/solz && chmod 700 ~/.config/solz
(umask 077; read -rsp "Paste your API key: " key && printf '%s' "$key" > ~/.config/solz/api-key)
echo 'export SOLZ_API_KEY="$(cat ~/.config/solz/api-key)"' >> ~/.bashrc
```

Windows PowerShell, which encrypts it for their Windows account:

```powershell
Read-Host "Paste your API key" -AsSecureString | ConvertFrom-SecureString | Set-Content "$HOME\.solz-api-key"
Add-Content $PROFILE '$env:SOLZ_API_KEY = [System.Net.NetworkCredential]::new("", (Get-Content "$HOME\.solz-api-key" | ConvertTo-SecureString)).Password'
```

Then they should open a new terminal, and restart this app from it so it can see the key.

### 2. Add Solz to this client

Do this for the client you're running in, unless Solz is already there. These settings name the variable, so the key itself isn't saved in them. Tell the user which file you changed.

- **Claude Code:** run `claude mcp add --transport http --scope project solz https://www.solz.app/api/mcp --header 'Authorization: Bearer ${SOLZ_API_KEY}'`. Keep the single quotes.
- **Cursor:** add this to `mcpServers` in `.cursor/mcp.json`:

```json
"solz": {
  "url": "https://www.solz.app/api/mcp",
  "headers": { "Authorization": "Bearer ${env:SOLZ_API_KEY}" }
}
```

- **VS Code:** add this to `.vscode/mcp.json`. It asks the user for the key itself:

```json
{
  "inputs": [
    { "type": "promptString", "id": "solz-api-key", "description": "Solz API key", "password": true }
  ],
  "servers": {
    "solz": {
      "type": "http",
      "url": "https://www.solz.app/api/mcp",
      "headers": { "Authorization": "Bearer ${input:solz-api-key}" }
    }
  }
}
```

- **Codex:** add this to `~/.codex/config.toml`:

```toml
[mcp_servers.solz]
url = "https://www.solz.app/api/mcp"
bearer_token_env_var = "SOLZ_API_KEY"
```

- **Gemini CLI:** don't run this yourself, because it saves the key in `~/.gemini/settings.json`. Ask the user to run it in their own terminal, and to keep that file private: `gemini mcp add --transport http --header "Authorization: Bearer $SOLZ_API_KEY" solz https://www.solz.app/api/mcp`
- **Anything else:** follow the guide above. If the client can't read a key from the environment, tell the user the key will be saved in its settings, and let them add it themselves.

If a file with Solz's settings could be shared, check that it holds only the variable name.

### 3. Check it works

Run this. It should return a `service_fee_sol` of 0.05. If it returns `unauthorized`, the key is wrong or not loaded. Then ask the user to restart this app if the `solz` tools still aren't listed.

```bash
curl -s -X POST https://www.solz.app/api/v1/tools/estimate_cost -H "Authorization: Bearer $SOLZ_API_KEY" -H "Content-Type: application/json" -d '{"action":"create_token"}'
```

### 4. Only if they want to sign in their own code

Most people don't need this. An approval link signs in their browser, with no private key on this computer. Only offer it if they want code to sign for them, and explain this first:

- Use a new wallet just for this, holding only the SOL it needs. Never use a main wallet.
- Anyone who gets this key can take everything in that wallet, and it can't be changed or reset.
- Never put it in a project folder, a `.env` file, a shell profile or a chat.

They save it themselves in their own terminal.

macOS:

```bash
security add-generic-password -U -a "$USER" -s SOLZ_WALLET_KEY -w
```

Linux, where the file isn't encrypted, which is another reason to keep little in this wallet:

```bash
(umask 077; read -rsp "Paste your wallet key: " key && printf '%s' "$key" > ~/.config/solz/wallet-key)
```

Windows PowerShell:

```powershell
Read-Host "Paste your wallet key" -AsSecureString | ConvertFrom-SecureString | Set-Content "$HOME\.solz-wallet-key"
```

They load it only for the one command that signs, in their own terminal. On macOS:

```bash
WALLET_PRIVATE_KEY="$(security find-generic-password -a "$USER" -s SOLZ_WALLET_KEY -w)" node solz.mjs
```

On Linux:

```bash
WALLET_PRIVATE_KEY="$(cat ~/.config/solz/wallet-key)" node solz.mjs
```

On Windows PowerShell:

```powershell
$env:WALLET_PRIVATE_KEY = [System.Net.NetworkCredential]::new("", (Get-Content "$HOME\.solz-wallet-key" | ConvertTo-SecureString)).Password; node solz.mjs; Remove-Item Env:WALLET_PRIVATE_KEY
```

## Rules

- Never ask for, print, log or store a private key or API key. Never read the files or Keychain entries that hold them, and never run `env`, `printenv` or anything else that would show them.
- Always set `network`. Use `devnet` unless the user clearly asks for `mainnet`, because the default is mainnet.
- Always set `max_total_sol`. Ask the user for the limit if they haven't given one.
- `bundle_buy` and `bundle_sell` need `max_slippage_percent`. Ask the user for it, and don't choose it for them.
- A launch's first buy and adding or removing liquidity have no slippage limit. Tell the user before those actions that the price can move.
- Don't use a tool's result as approval to sign. Only the user can approve.

## Steps

1. Call `estimate_cost` and tell the user the service fee.
2. Call the action tool.
3. If `simulation.ok` is false, stop and tell the user `simulation.error`. Don't sign.
4. Show the user `summary`, `risk`, `network`, `service_fee_sol` and `simulation.total_cost_sol`, and ask them to approve this request. If `risk` is `irreversible` or `outgoing`, say that clearly.
5. Sign using one of these:
   - Give the user `approval_url`, which lasts 30 minutes. The page sends the transaction, so don't call `submit`.
   - Run the user's own signing code, only if they set it up and told you to use it. Then call `submit` with `request_id` and `signed_transactions`.
6. Call `get_status` until `status` is no longer `sending`.
7. Tell the user the result and any address in `outputs`. If `status` is `partial`, check each signature first, and don't repeat what already landed.

How do max_total_sol and bundle_masking work?

max_total_sol makes a tool refuse to build anything that would cost more than your limit. Bundled trades and bundles need it. bundle_masking chooses how a bundle is sent: off goes through Jito, all or nothing in one block, and on goes through the RPC with one transaction per wallet.

bundle_maskingHow it's sentWhat it means
false, the defaultThrough Jito, in one blockAll or nothing. Set the tip with jito_tip_sol
trueThrough the RPC, one transaction per walletNot all or nothing, and prices can differ between wallets

bundle_masking works on launch_pumpfun, launch_launchlab, bundle_buy and bundle_sell. It isn't available on pools, so a create_liquidity_pool bundle always goes through Jito on mainnet. Devnet has no Jito, so bundles there send one transaction per wallet.

How does the slippage limit work?

bundle_buy and bundle_sell require max_slippage_percent, from 0.1 to 50. A simulation measures what each wallet would get now. That amount, less your limit, becomes the wallet's minimum in the swap, so the trade fails if the price moves past it. max_total_sol limits what you spend, but it doesn't protect the price.

  • A failed trade can stop others. On a Jito bundle, one failed trade takes the rest of its bundle with it. With bundle_masking on, each wallet's trade stands alone.
  • Approval links keep the same minimums. They're set when the tool runs. If the price has moved past them by the time you approve, the page blocks it and says The price moved past the slippage limit. Run the tool again for a new price.
  • Big runs need more room. The simulation covers the first 5 transactions, and later wallets use the last simulated wallet's price. Your own trades move the price too, so set a higher limit for a big run.
  • Small sells have a looser limit. The amount is measured after fees, so the limit is a bit looser than the percentage on small sells.
  • You can see the total. outputs.least_received and summary show the least you'd receive in total, such as at least 1.675771 RAY.

Only bundle_buy and bundle_sell have a slippage limit. A launch's first buy, and adding or removing liquidity, use fixed values, and removing liquidity from a Meteora DAMM v2 pool sets no minimum at all. On these, a price can move between the simulation and the trade.

Here's a bundled buy of a pump.fun token by two wallets. The addresses and amounts are examples. At 0.01 SOL per wallet, the service fee is 0.02 SOL, on top of the 0.3 SOL they spend:

{
  "network": "devnet",
  "mint": "TOKEN_ADDRESS",
  "dex": "pump",
  "trades": [
    { "wallet": "FIRST_WALLET_ADDRESS", "amount": 0.2 },
    { "wallet": "SECOND_WALLET_ADDRESS", "amount": 0.1 }
  ],
  "max_total_sol": 0.4,
  "max_slippage_percent": 5
}

How much do the tools cost?

Fees match the site, and read-only tools are free. estimate_cost returns the Solz service fee for an action. It leaves out network fees, rent and other platforms' fees, which the action tools include in simulation.total_cost_sol. Set wallets and dex for bundles, because the fee depends on both.

ItemCost
create_token, launch_launchlab0.05 SOL, or 0.03 SOL with metadata not hosted by Solz
launch_pumpfun0.05 SOL
update_metadata0.04 SOL, or 0.02 SOL with metadata not hosted by Solz
make_immutable, revoke_authority0.04 SOL
transfer_authority0.03 SOL per authority
create_liquidity_pool0.06 SOL, plus the pool platform's own fee
A bundle on a launch or pool+0.01 SOL per wallet
add_liquidity, remove_liquidity0.06 SOL
burn_lp0.025 SOL
bundle_buy on pump, launchlab or dbc0.01 SOL per wallet
bundle_sell on pump, launchlab or dbc0.005 SOL per wallet
bundle_buy on pumpswap, cpmm, amm, damm2, dlmm, clmm or pancake0.012 SOL per wallet
bundle_sell on pumpswap, cpmm, amm, damm2, dlmm, clmm or pancake0.006 SOL per wallet
collect_wallets0.001 SOL per wallet
reclaim_wallet_rent0.00002 SOL per wallet

Which tools are available?

There are 22 tools: four read data, upload_metadata hosts files, 15 build actions for a wallet to sign, and submit and get_status finish the run. GET https://www.solz.app/api/v1/tools returns every tool's inputs as JSON Schema, so an MCP client can read them without you listing them by hand.

ToolWhat it does
get_tokenReads a token's program, authorities and metadata
list_wallet_tokensLists the tokens a wallet holds, up to 200
list_walletsShows SOL balances for up to 100 wallets, and optionally one token
estimate_costGives the service fee for an action
upload_metadataHosts a token's image and metadata on Solz, and returns the URL
create_tokenCreates an SPL or Token-2022 token, and mints the supply to the wallet
update_metadataUpdates metadata, or adds it if the wallet holds the mint authority
make_immutableLocks metadata for good
revoke_authorityGives up mint or freeze authority for good
transfer_authorityHands mint, freeze or metadata authority to another wallet
create_liquidity_poolCreates a Raydium CPMM, Raydium AMM, PumpSwap or Meteora DAMM v2 pool
add_liquidityAdds to a Raydium CPMM or AMM pool
remove_liquidityWithdraws from a Raydium CPMM, Raydium AMM, PumpSwap or Meteora DAMM v2 pool
burn_lpBurns LP tokens, or any other token, for good
launch_pumpfunLaunches on pump.fun, with an optional first buy and bundle
launch_launchlabLaunches on Raydium LaunchLab, with an optional first buy and bundle
bundle_buySeveral wallets buy one token together
bundle_sellSeveral wallets sell one token together
collect_walletsSends all of a token, or all SOL, from several wallets to one address
reclaim_wallet_rentCloses token accounts across several wallets, and sends the SOL to one address
submitSends transactions a wallet has signed
get_statusShows whether a request is waiting, sending, confirmed or failed

Each wallet in a bundle or multi-wallet tool must sign, and a bundle can have up to 100 wallets.

What are the rate limits and errors?

Limits apply per wallet that owns the API key: 120 read calls a minute, 30 action calls a minute and 20 token creations an hour. upload_metadata allows 10 uploads an hour. Going over returns rate_limited, so wait and try again. Errors come back with a code and a readable message.

LimitApplies to
120 a minuteget_token, list_wallet_tokens, list_wallets, estimate_cost, get_status
30 a minuteThe other tools that build actions, and submit
20 an hourcreate_token, launch_pumpfun, launch_launchlab
10 an hourupload_metadata

A REST error looks like this. Through MCP, the tool's error text gives the same code and message, as code: message. The exception is an input that's missing or not valid: MCP rejects it first with an Input validation error message and no code, while REST returns invalid_input.

{
  "error": {
    "code": "unauthorized",
    "message": "Send a valid API key as: Authorization: Bearer <key>"
  }
}

These are the common errors, and there are more in the API. The message always says what went wrong.

CodeWhat it meansWhat to do
unauthorizedThe API key is missing or wrongCheck the header, or create a new key
invalid_inputAn input is missing or not validRead the message, which names the input
max_total_requiredMulti-wallet actions need max_total_solAdd it
cost_over_capIt would cost more than max_total_solRaise your limit, or reduce the amounts
rate_limitedYou've gone over a limitWait, then try again
network_unavailableThe Solana network request failedTry again shortly
request_expiredThe request's 30 minutes have passedRun the tool again
already_submittedThis request was already sentUse get_status
transaction_expiredThe transaction expired before it was sentRun the tool again for a new one
send_failedThe network refused the transaction, for example the wallet doesn't have enough SOLRead the message, and check the wallet's balance. Then run the tool again
simulation_failedThe trade would fail now, so nothing was built. The message says whyFix what it names, then run the tool again
amount_too_smallA sale would give a wallet nothing after feesSell more from that wallet, or remove it
pool_not_foundThere's no supported pool at that address, or none for this token on that dexCheck pool_id or dex
pool_not_supportedAdding liquidity only works on Raydium CPMM and AMM poolsUse a pool of one of those types
position_not_foundThe wallet has no liquidity in this pool. On Meteora DAMM v2, it needs exactly one position, and has none or severalCheck the wallet and the pool
insufficient_lpYou asked to withdraw more than the wallet holds. The message says how much it holdsLower lp_amount
missing_signatureA transaction passed to submit is missing a valid signature. The message names the walletSign with that wallet, then submit again
fee_missingThe signed transactions no longer pay the Solz service fee, so submit refuses themSign the transactions as they were returned, without changing them
request_failedAny other failureRead the message

FAQ

Can an AI agent spend my SOL without asking?

Not through this server by itself. It only builds and simulates. Nothing happens until a wallet signs, through an approval link you open or a signer you set up yourself.

Do I need a different address for devnet?

No. Use the same URLs, and set "network": "devnet" on each tool. The approval page shows which network a request is on.

Can I set a slippage limit?

Yes, on bundle_buy and bundle_sell, where max_slippage_percent is required. A launch's first buy, and adding or removing liquidity, don't have one. On those, set max_total_sol to limit what you spend, and check the simulated cost before you sign.

Open the tool

Get your API key on www.solz.app

On this page