Using Fatstack tools with the AI SDK
Give an agent a wallet and a spending limit, and let it discover and pay for tools on its own. Discovery is free; only calls cost money.
Two ways in
This page covers the AI SDK ToolSet, which is the right choice when you are already building with the AI SDK and want typed tools and spend guards in your own code.
If you would rather not integrate per listing, add the entire catalog as one MCP server: POST https://www.fatstack.net/api/mcp. Every live listing arrives as a callable tool and the payment terms come with it. The MCP endpoint.
MCP client configuration
{
"mcpServers": {
"fatstack": {
"type": "http",
"url": "https://www.fatstack.net/api/mcp"
}
}
}Install
npm
npm install @fatstack/ai-sdk-tools ai viem@fatstack/ai-sdk-tools turns the Fatstack catalogue into an AI SDK ToolSet. Each listing becomes one tool; calling it pays for it.
Spend guards are required
guards is a required argument with no default. A tool set that can spend money should not be constructible by forgetting something. Omit it and you get a type error, and at runtime a MissingSpendGuardError thrown before the catalogue is fetched — so a missing cap fails on the line that forgot it, rather than looking like a network problem later.
This matters more here than in most SDKs: an agent holds a key, and payments are final. There is no chargeback to fall back on, so the ceiling is the mechanism, not a convenience.
Configuration
import { fatstackTools } from '@fatstack/ai-sdk-tools';
import { privateKeyToAccount } from 'viem/accounts';
const tools = await fatstackTools({
wallet: privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`),
networks: ['base'],
guards: {
maxPerDay: 0.50, // required — a hard USD ceiling per UTC day
maxPerCall: 0.01, // optional — refuse any single call dearer than this
},
});What happens if you omit it
// This does not compile, and would not run if it did.
const tools = await fatstackTools({
wallet,
networks: ['base'],
});
// TypeScript: Property 'guards' is missing.
// At runtime: MissingSpendGuardError, thrown before the catalogue is even fetched.maxPerDay is a hard USD ceiling per UTC day and is required. maxPerCall is optional and refuses any single call above it. Guards are evaluated after the quote is known and before anything is signed, so exceeding one throws with nothing signed and nothing spent.
A runnable example
This runs against Base mainnet and spends real USDC, because that is what the public catalogue serves — every live listing settles on eip155:8453. There is currently no testnet listing, so a Sepolia configuration would discover nothing and could never complete a call.
Fund a throwaway wallet with a small amount of USDC on Base — a dollar covers hundreds of calls at typical listing prices. No ETH is needed: the agent signs an authorisation rather than sending a transaction, and never pays gas. The spend guards are what bound the cost, which is why they are required.
Discover, pay, use the result
import { generateText } from 'ai';
import { fatstackTools } from '@fatstack/ai-sdk-tools';
import { privateKeyToAccount } from 'viem/accounts';
const wallet = privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`);
// Discovery is free and happens once. Only calling a tool costs anything.
const tools = await fatstackTools({
wallet,
networks: ['base'],
guards: { maxPerDay: 0.50, maxPerCall: 0.01 },
});
const { text } = await generateText({
model, // any AI SDK model
tools,
prompt: 'Convert 20 degrees Celsius to Fahrenheit.',
});
console.log(text);The model picks a tool, the tool set pays for it, and the result comes back. A settled call takes about a second.
Without the AI SDK
You do not need the catalogue, or a model. payFetch is a fetch that handles a 402 wherever it finds one.
Pay any x402 endpoint directly
import { payFetch } from '@fatstack/x402/client';
import { privateKeyToAccount } from 'viem/accounts';
const res = await payFetch(
'https://echo.fatstack.net/mcp',
{
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: { name: 'echo_json', arguments: { payload: { hello: 'world' } } },
}),
},
{
wallet: privateKeyToAccount(process.env.AGENT_PRIVATE_KEY as `0x${string}`),
networks: ['base'],
guards: { maxPerCall: 0.01, maxPerDay: 0.10 },
},
);
// The settlement transaction is on the response.
console.log(res.status, res.headers.get('payment-response'));Two things worth knowing
Listing text is untrusted. Names and descriptions are written by providers and read by your agent, which makes them an injection surface. Every listing is screened and human-reviewed before going live, but screening is a filter, not a proof. Keep listing text in a delimited, labelled untrusted block, and never interpolate it into a system prompt. Spend guards are the backstop that holds even if a description does convince your agent to call something.
Every quote states its network. A quote carries a testnet flag alongside the chain id, so nothing has to know that eip155:84532 is play money and eip155:8453 is not.
Payments are final. No refunds. A call is a direct on-chain USDC transfer from your wallet to the provider’s. Once settled, nobody — including Fatstack — can reverse it.
Source
The SDK, this integration and the payment contracts are MIT and public at github.com/f4tst4ck/fatstack-tools. The example above is in integrations/ai-sdk-tools/example and is run against the published package, not the source tree.