Skip to main content
Glama

VectorAgent

An icon store where the buyer is an agent and the payment is the license.

An agent finds the tools over MCP, searches for free, gets an HTTP 402 when it asks for a paid icon, signs a sub-cent USDC transfer on Stellar testnet, and gets the SVG back with the transaction that bought it stamped into the file.

Testnet only. Every number below was measured rather than estimated.

Check it before reading the rest

None of this needs to be taken on trust. One real settlement:

Payment

ec6405b8…a6409751 - 0.0180 USDC, agent wallet to vault

Receipt

f60c428f…9baadda8 - a manage_data entry named va:ec6405b887dede5453ef4ac663760791

What it bought

7 icons, in a 524-byte manifest whose sha256 is 5a759b7d…a643b843, which is the value that entry holds

The entry's name is the first 32 characters of the payment hash, so the link between the money and the list is public too. The settlements page refetches every manifest and re-hashes it in your browser: anything that does not match the chain is dropped rather than shown, so the page cannot vouch for a list the ledger has not already committed to. Every settlement there was paid by this project's own wallet, some from the demo on the home page and some from an agent calling the MCP tools directly.

The live demo runs an actual purchase when you press the button. It is not a recording.

Related MCP server: Svg/icons MCP

What this is

A proof of one narrow thing: an agent can buy a digital asset on its own, with no human approving the purchase, and the payment can be the provenance record rather than a license file someone has to keep believing in.

Three standards stacked, none of them invented here. MCP is how agents find tools, x402 is how a server charges for a request, and Stellar settles cheaply enough that writing the receipt costs less than the icon.

The findings, including what happened when four agents met it cold and only two of them bought anything, are written up here. This README is the implementation.

Quickstart

npm install
cp .env.example .env
npm run setup-wallets   # AGENT + VAULT keypairs, funded with XLM, USDC trustlines opened

Two things you have to bring yourself:

  • A facilitator API key. Set FACILITATOR_API_KEY in .env. The default FACILITATOR_URL is OpenZeppelin's hosted testnet facilitator.

  • Testnet USDC for the agent wallet. The Circle faucet is a web form with no public API, so this step is manual: open https://faucet.circle.com, network Stellar Testnet, token USDC, paste your AGENT_PUBLIC_KEY. 20 USDC per request, refillable every two hours.

Then run the payment loop end to end:

npm run build:edge                # copy src/ and assets/ into the function
npm run dev:edge                  # terminal 1: the x402 resource server on :4021
npm run test:flow shopping-cart   # terminal 2: one paid fetch over plain HTTP

And the static site, if you want the settlements page and the demo:

npm run build:previews   # 1,550 watermarked PNGs into site/dist/preview
npm run build:site       # settlements page, library browser
npm run build:demo       # the home page
npm run serve:site       # http://localhost:4180

PREVIEW_BASE already defaults to that address, so the previews in a 402 challenge resolve against your own copy rather than anyone else's.

The asset library

1,550 original icons across 30 categories, built from compact specs in assets/specs/ by npm run build:assets. Every icon is tokenized the same way: each part carries a data-slot marker and paints through a CSS custom property chain, so one file serves light, dark, mono and duotone without variants.

stroke="var(--tby-accent-stroke, var(--tby-accent, var(--tok-color-secondary, #1a1f2b)))"

Tier

Count

Price

What it is

free

1,000

$0.000

common UI primitives, 20 categories

standard

500

$0.002

domain-specific (dev-infra, fintech, healthcare, …)

premium

50

$0.010

animated variants of standard icons

npm run preview:assets writes assets/qa/preview.html, a self-contained page with every icon inlined and toggles for theme, palette and a per-region accent override. npm run qa:assets writes per-category PNG contact sheets.

These are a test set, published under the same license as the code. They exist to make the payment flow real, not to be a product.

Pricing

Price is a property of the icon's tier, not of the route. The x402 route config takes a function, so one endpoint quotes three different prices:

price: (ctx) => getIcon(iconIdFromPath(ctx.path))?.price ?? TIERS.standard.price

Three cases resolve in order, and the first two never mint a challenge:

Request

Result

unknown id

404 ahead of the payment middleware, so a typo is never billable

free icon

200 with the SVG, no 402 round trip at all

paid icon

402 + preview, then the SVG once the transfer settles

The 402 is not a dead end. Its body carries the free assessment payload (tier, price, tags, and a watermarked 96px preview URL) while the machine-readable requirements ride in the PAYMENT-REQUIRED header, so the two never collide. src/preview.ts renders the watermark; it is deliberately low-resolution and overprinted, enough to judge the icon and useless as the asset.

Latency, and why baskets exist

A settlement costs a Stellar ledger close, and Stellar testnet closes a ledger every exactly 5.00 seconds. Measured end to end, a single paid fetch is ~8-10s, and splitting it around the ledger boundary shows where it goes:

Time

Request to ledger close

~4.9-5.6s (protocol floor)

Ledger close to response

~1.8-4.8s (facilitator confirm, pure overhead)

Free icon

51 ms

402 challenge only

4 ms

So roughly half the wait happens after the money has already moved, and the other half cannot be removed while we wait for settlement at all. That makes per-icon fetching a dead end for an agent building a screen: twenty icons one at a time is about three minutes.

GET /bundle/:ids buys a basket in one settlement, which is the cheapest fix available and changes no trust assumptions. Measured:

5 icons sequentially   39.00s   (7.80s each)
5 icons as one basket   7.50s   5.2x faster, same $0.010 spent
20 icons as one basket  8.06s   versus roughly 180s one at a time

Latency is per settlement, not per icon, so a twenty-icon basket costs about the same wall time as buying one. The fetch_vector_icons MCP tool says so in its description, so a calling agent has a reason to prefer it.

The basket keeps the same three-case ordering as a single fetch: an unknown id rejects the whole basket before the middleware so nothing is billed, an all-free basket never sees a challenge, and free icons ride along inside a paid basket at no cost so the caller never has to sort them. Limit is 40.

Two things this does not fix, and they are the real roadmap: the facilitator overhead above (PayAI took x402 on Base from 1.7s to 750ms by chasing exactly this), and genuine first-contact one-off requests, which no deployed x402 pattern currently solves without pre-funding. Releasing an asset on verification alone is tempting and is what we first reached for, but it is a documented vulnerability class rather than a pattern: a production deployment measured 86.95% leakage under burst.

The license attribute

A delivered asset carries a pointer to the settlement that paid for it, so provenance survives without us:

<svg data-va-asset="vector-db"
     data-va-license="standard"
     data-va-tx="0a0155746e73…ccfb"
     data-va-network="stellar:testnet" …>

Free assets are stamped too, with data-va-license="free" and no citation. The claim is that an audit finds zero unattributed assets, and "free" answers "where did this come from" while a missing attribute does not.

data-va-network is load-bearing: a bare hash is meaningless without knowing which ledger to resolve it against, and resolving it is the whole point.

The MCP server stamps this, not the resource server. The Stellar scheme's default payment flow is authorization, which settles after the route handler produces its body, so the server cannot know the transaction hash when it builds the SVG. The MCP server can: it holds the wallet, it made the payment, and it gets the settlement receipt alongside the asset. The party that paid writes the pointer to its own payment into its own copy.

Auditing

npx tsx scripts/audit-licenses.ts ./some-app/src

Walks a tree, finds every stamped asset (including SVGs inlined into JSX), and resolves each citation against public Horizon. It never calls the VectorAgent registry, which is the point: the audit still works if the company is gone. verify_vector_license is the same check as an MCP tool.

What this does and does not prove

Resolving a citation proves a settled transfer of a given amount reached a given account at a given time, and the verifier additionally checks that the amount matches the asset's tier price. It does not, on its own, prove that transfer bought this icon, because the asset id is not carried inside the transaction: that link still comes from the registry's records. Closing it needs the asset id in a Stellar memo or an asymmetric signature.

Testing

npm run dev:edge       # terminal 1
npm run test:pricing   # terminal 2 - HTTP + ledger
npm run test:mcp       # terminal 2 - the MCP tool layer
npm run test:bundle    # terminal 2 - batching, measured against sequential
npm run test:manifest  # terminal 2 - canonical bytes and their digest
npm run test:attest    # terminal 2 - the on-chain receipt round trip

test:pricing asserts an unknown id is never billable, a free icon settles nothing, and standard and premium are quoted and settled at $0.002 and $0.010, checking the vault's balance on Horizon rather than trusting the server. test:mcp spawns the MCP server over stdio and checks that an agent sees the right tools, that search and preview cost nothing, and that only fetch_vector_icon can spend.

Every one of these spends real testnet USDC from the agent wallet, which is the point: they fail if the money does not actually move.

Using it from Claude Code

.mcp.json at the project root registers the MCP server:

{
  "mcpServers": {
    "vectoragent-poc": {
      "type": "stdio",
      "command": "npx",
      "args": ["tsx", "src/mcp-server.ts"]
    }
  }
}
  1. Start (or restart) a Claude Code session in this project directory. Claude Code reads .mcp.json at startup, not while a session is already running.

  2. Approve the server when prompted (first time only) - it's a subprocess Claude Code starts on your machine, so it asks for consent once.

  3. Confirm it connected: run /mcp inside the session, or claude mcp list from a terminal.

  4. Ask it to use the tools, e.g.: "Search VectorAgent for a vector database icon, preview the best paid match, then fetch it and build a React status card with it."

The six tools, and which of them can spend:

Tool

Cost

What it does

search_vector_icons

free

ranked matches with tier, price and match_confidence

preview_vector_icon

free

metadata + watermarked preview URL, reads the 402 without paying

list_vector_icons

free

catalog counts; pass a tier to list ids

verify_vector_license

free

resolves an SVG's data-va-tx against Horizon, never against us

fetch_vector_icon

tier-priced

returns one SVG, settling first if the icon is not free

fetch_vector_icons

summed

a basket of up to 40 in ONE settlement; prefer this

Watch the resource server's terminal log the 402 -> verify -> settle sequence in real time.

If you'd rather use the plain Claude Desktop chat app instead of Claude Code, the same JSON block goes under mcpServers in claude_desktop_config.json (Windows: %APPDATA%\Claude\claude_desktop_config.json), with cwd set to this project's absolute path since that app doesn't infer it from where the config file lives.

Architecture

Claude (Code or Desktop)
   |  MCP tool call: fetch_vector_icons([ids])
   v
src/mcp-server.ts  (holds AGENT's Stellar keypair)
   |  HTTP GET, wrapped with @x402/fetch
   v
supabase/functions/vectoragent  (x402 resource server; npm run dev:edge locally)
   |  free icon  -> 200 + SVG, no challenge, stop here
   |  unknown id -> 404, no challenge, stop here
   |  paid icon  -> 402 + PAYMENT-REQUIRED header (price from the icon's tier)
   |               + body carrying the watermarked preview URL
   |  <- mcp-server signs a Soroban USDC transfer, retries
   v
OpenZeppelin-hosted x402 facilitator (channels.openzeppelin.com/x402/testnet)
   |  verifies + broadcasts the signed transfer, fees sponsored on testnet
   v
Stellar testnet ledger -- AGENT -> VAULT
   |
   |  the function returns the SVGs; mcp-server.ts stamps each one with the
   |  transaction that paid for it and hands them back
   v
A second, independent transaction writes the receipt: sha256 of the itemised
manifest, into a manage_data entry keyed by the payment hash. Soroban payments
cannot carry a memo, which is the only reason this is two transactions.

site/ is a static build with no backend of its own: the settlements page reads Horizon and the content store straight from the browser, so it is a second witness to the same settlements rather than this project reporting on itself.

Why the paid route is not a Worker

Everything else here is static and could sit on one platform, so the split is worth explaining. Cloudflare Workers allow 10ms of CPU per request on the free plan. Signing one receipt transaction measures 7.5ms on its own, before the payment signing, the hashing or the catalogue search. A Supabase edge function allows 2s, so that is where the paid route runs.

Two things do not survive that runtime and are handled rather than ported:

  • @resvg/resvg-js is a native binding Deno cannot load, so previews are pre-rendered into the static site by npm run build:previews and /preview/:file redirects to wherever PREVIEW_BASE points.

  • Express does not run on Deno, so routing is a small hand-written router. The x402 paywall itself ports cleanly, because @x402/express is a thin wrapper over the framework-agnostic @x402/core/server.

There is one implementation, not two. An Express server did this first and was deleted once the function worked, because two versions of ten routes drift and nothing tells you when.

Scope

Built and working: the payment loop, tiered pricing, the asset library, watermarked previews, baskets, the license attribute, the on-chain receipt, in-browser verification, and a live demo that spends real testnet money.

Still out, and deliberately so:

  • Binding the citation to the asset id. The license attribute ships, but the transaction itself does not carry the asset id, so the "which icon did this buy" link still depends on our records. See the section above.

  • Semantic search. src/search.ts is a transparent lexical scorer, not a vector index. Good enough to exercise pricing; not good enough for "isometric 3D cloud database node". Two of the four test agents ignored its scores entirely and read the descriptions instead.

  • Creator ingestion and payouts. The split, the publisher UI and the off-ramp are all unbuilt. Every asset here is one we authored, so there is no creator to pay yet.

  • Mainnet. Testnet only, with a hosted facilitator. The test agents knew it, too: one of them called the funds "valueless faucet tokens" while deciding to spend them, which is a confound no amount of testnet proves away.

License

MIT, covering the code and the 1,550 icons alike. Use any of it for anything.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    Visual icon search, retrieval, and comparison for AI agents. Search 200k+ icons semantically, render side-by-side comparison grids, and retrieve raw SVG markup — all tools return images so vision-capable LLMs can see the icons.
    1
    -
  • A
    license
    Not graded
    quality
    C
    maintenance
    Enables AI coding tools to search, inspect, recommend, and export SVG icons from svgicons.com for use in design systems, frontend projects, and AI-assisted workflows.
    MIT
  • F
    license
    Not graded
    quality
    F
    maintenance
    Enables AI agents to query the largest CC0 database for provenance, license verification, and market data, and generate CC0 images, all via pay-per-call x402 micropayments.
    -