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
- 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.
- Add Solz to your client. Use the link or command for your client below, with your key in place of
YOUR_API_KEY. - Ask your agent to list the tools. You should see all 22. If the key is wrong, you'll get an
unauthorizederror.
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.
| Client | How to add it |
|---|---|
| Cursor | Add Solz to Cursor |
| VS Code | Add Solz to VS Code |
| Claude and Claude Desktop | Open Claude connectors and follow the steps below |
| Claude Code | Run the command below |
| Codex | Add the settings below |
| Gemini CLI | Run the command below |
| Other MCP clients | Use 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/mcpOther 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/jsonHow 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_urlin a browser. Solz checks the cost again, and you approve in your wallet. It sends the transaction for you, so you don't callsubmit. - Your own code: sign each entry's
transactionwith the wallets in itsneeds_signature_from, then callsubmitwith the samerequest_idand 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:
status | What it means | What to do |
|---|---|---|
waiting_for_signature | Nobody has signed yet | Approve or submit it |
expired | The approval window passed with no signature | Run the tool again for a new request |
sending | It's been sent, and hasn't landed yet | Check again shortly |
confirmed | Everything landed | Use the signatures and outputs, such as the new token address |
partial | Some transactions landed, and some didn't | Check each signature's status before you retry, so you don't repeat what landed |
failed | Nothing landed | Read 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.mjsimport { 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.pyimport 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.mdin 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_masking | How it's sent | What it means |
|---|---|---|
false, the default | Through Jito, in one block | All or nothing. Set the tip with jito_tip_sol |
true | Through the RPC, one transaction per wallet | Not 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_maskingon, 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_receivedandsummaryshow 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.
| Item | Cost |
|---|---|
create_token, launch_launchlab | 0.05 SOL, or 0.03 SOL with metadata not hosted by Solz |
launch_pumpfun | 0.05 SOL |
update_metadata | 0.04 SOL, or 0.02 SOL with metadata not hosted by Solz |
make_immutable, revoke_authority | 0.04 SOL |
transfer_authority | 0.03 SOL per authority |
create_liquidity_pool | 0.06 SOL, plus the pool platform's own fee |
| A bundle on a launch or pool | +0.01 SOL per wallet |
add_liquidity, remove_liquidity | 0.06 SOL |
burn_lp | 0.025 SOL |
bundle_buy on pump, launchlab or dbc | 0.01 SOL per wallet |
bundle_sell on pump, launchlab or dbc | 0.005 SOL per wallet |
bundle_buy on pumpswap, cpmm, amm, damm2, dlmm, clmm or pancake | 0.012 SOL per wallet |
bundle_sell on pumpswap, cpmm, amm, damm2, dlmm, clmm or pancake | 0.006 SOL per wallet |
collect_wallets | 0.001 SOL per wallet |
reclaim_wallet_rent | 0.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.
| Tool | What it does |
|---|---|
get_token | Reads a token's program, authorities and metadata |
list_wallet_tokens | Lists the tokens a wallet holds, up to 200 |
list_wallets | Shows SOL balances for up to 100 wallets, and optionally one token |
estimate_cost | Gives the service fee for an action |
upload_metadata | Hosts a token's image and metadata on Solz, and returns the URL |
create_token | Creates an SPL or Token-2022 token, and mints the supply to the wallet |
update_metadata | Updates metadata, or adds it if the wallet holds the mint authority |
make_immutable | Locks metadata for good |
revoke_authority | Gives up mint or freeze authority for good |
transfer_authority | Hands mint, freeze or metadata authority to another wallet |
create_liquidity_pool | Creates a Raydium CPMM, Raydium AMM, PumpSwap or Meteora DAMM v2 pool |
add_liquidity | Adds to a Raydium CPMM or AMM pool |
remove_liquidity | Withdraws from a Raydium CPMM, Raydium AMM, PumpSwap or Meteora DAMM v2 pool |
burn_lp | Burns LP tokens, or any other token, for good |
launch_pumpfun | Launches on pump.fun, with an optional first buy and bundle |
launch_launchlab | Launches on Raydium LaunchLab, with an optional first buy and bundle |
bundle_buy | Several wallets buy one token together |
bundle_sell | Several wallets sell one token together |
collect_wallets | Sends all of a token, or all SOL, from several wallets to one address |
reclaim_wallet_rent | Closes token accounts across several wallets, and sends the SOL to one address |
submit | Sends transactions a wallet has signed |
get_status | Shows 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.
| Limit | Applies to |
|---|---|
| 120 a minute | get_token, list_wallet_tokens, list_wallets, estimate_cost, get_status |
| 30 a minute | The other tools that build actions, and submit |
| 20 an hour | create_token, launch_pumpfun, launch_launchlab |
| 10 an hour | upload_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.
| Code | What it means | What to do |
|---|---|---|
unauthorized | The API key is missing or wrong | Check the header, or create a new key |
invalid_input | An input is missing or not valid | Read the message, which names the input |
max_total_required | Multi-wallet actions need max_total_sol | Add it |
cost_over_cap | It would cost more than max_total_sol | Raise your limit, or reduce the amounts |
rate_limited | You've gone over a limit | Wait, then try again |
network_unavailable | The Solana network request failed | Try again shortly |
request_expired | The request's 30 minutes have passed | Run the tool again |
already_submitted | This request was already sent | Use get_status |
transaction_expired | The transaction expired before it was sent | Run the tool again for a new one |
send_failed | The network refused the transaction, for example the wallet doesn't have enough SOL | Read the message, and check the wallet's balance. Then run the tool again |
simulation_failed | The trade would fail now, so nothing was built. The message says why | Fix what it names, then run the tool again |
amount_too_small | A sale would give a wallet nothing after fees | Sell more from that wallet, or remove it |
pool_not_found | There's no supported pool at that address, or none for this token on that dex | Check pool_id or dex |
pool_not_supported | Adding liquidity only works on Raydium CPMM and AMM pools | Use a pool of one of those types |
position_not_found | The wallet has no liquidity in this pool. On Meteora DAMM v2, it needs exactly one position, and has none or several | Check the wallet and the pool |
insufficient_lp | You asked to withdraw more than the wallet holds. The message says how much it holds | Lower lp_amount |
missing_signature | A transaction passed to submit is missing a valid signature. The message names the wallet | Sign with that wallet, then submit again |
fee_missing | The signed transactions no longer pay the Solz service fee, so submit refuses them | Sign the transactions as they were returned, without changing them |
request_failed | Any other failure | Read 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.