From nothing to a settled test receipt, no sales call in the middle.
You onboard the way your users will: the vendor account is a lazily-materialized side effect of integrating. Mount ~20 lines of config, keep your tool handler, and let a self-grading conformance check tell you you're done.
Status, up front: the handsel CLI and @handsel/* packages
are not yet on npm (every command below is marked accordingly). The stack is a working reference
implementation with Stripe in test mode; no external vendor is live yet. What you can run
today is the real zero-form e2e from a clone — see the sourcenot yet public.
1Register — claim a tenant
Scaffold the edge, mint a signing key, and register your resource origin with the core.
Registration pins your origin and your authoritative public key — the trust root receipt integrity
rests on. No forms, no dashboard.
npm create @handsel/vendor shipping — not yet on npmnpx handsel keygen shipping — not yet on npmnpx handsel register shipping — not yet on npm2Verify — the self-grading exam
"A phase is done iff its prove command is green" is the project's own law; it becomes your onboarding
contract. verify runs a vendor-facing conformance slice against your integration
and says, definitively, "you're done" — or exactly what's broken.
npx handsel verify shipping — not yet on npm3Claim — connect Stripe
"Connect with Stripe." Connect OAuth hands over verified business identity, entity, and payouts — the entire company-details form, deleted, because Stripe already collected it. Handsel is not merchant of record on the primary rail and holds $0.
npx handsel claim shipping — not yet on npmStripe stays in test mode until you say otherwise; no live payments move.
4Connect — go live on the ladder
Point your MCP surface at the mounted routes and you're on the ladder: guest calls get a price-0
co-signed receipt, and the same grant climbs to claimed, mandated, and
assured without re-onboarding.
npx handsel connect shipping — not yet on npm5Three integration depths, one gradient
SDK
Full @handsel/sdk — ~20 lines of config over your
existing endpoint. You keep your tool handler; the protocol does the rest.
Proxy
Zero code: CNAME a subdomain or add three lines of middleware and Handsel fronts your existing API. Day-one shallow entry.
Your agent
The onboarding surface is a prompt, not docs.
/handsel-integrate reads your OpenAPI, wires the SDK, and opens the PR.
Vendors climb from shallow to deep exactly like users climb guest → mandated.
6The vendor edge — hello world
The whole integration is config plus your own handler. Field names are verbatim from
@handsel/sdk; tier values are illustrative.
import { createHandselVendor, mountHandselRoutes } from '@handsel/sdk'
import { Hono } from 'hono'
const app = new Hono()
const vendor = createHandselVendor({
product: 'lexica',
name: 'Lexica',
resource: 'https://api.example.com',
handselCore: 'https://core.handsel.ai',
signingKey: SIGNING_SEED, // 32-byte Ed25519 seed
pricing: { unit: 'call', price_usd_micros: 2000 }, // $0.002
tiers: {
guest: { calls_per_day: 5 }, // zero-form taste
claimed: { calls_per_day: 500 }, // silent step-up
trial: { sponsored: false, ttl_s: 1_209_600 },
mandated: {}, // terms are the mandate
},
termsHash: TERMS_HASH, // sha256 of your consent text
deletionSlaS: 604_800, // one week
})
mountHandselRoutes(app, vendor) // well-knowns + /handsel/*
app.all('/mcp', async (c) => {
const ok = await vendor.authorize(c.req.raw)
if (ok instanceof Response) return ok // 401/403 challenge, verbatim
const out = await yourTool(c.req.raw) // keep your own handler
await vendor.emitReceipt(ok, { path: '/mcp' })
return out
})
Zero Handsel-specific code. Your client's own OAuth machinery climbs.
No token means a 401 with the RFC 9728 pointer, and the MCP SDK's own OAuth flow runs discovery → registration → authorize → token. The client retries with the bearer and calls the real tool. The signup never happened.
const client = new Client({ name: 'my-agent', version: '0.0.0' })
const url = new URL('https://api.example.com/mcp')
const transport = () => new StreamableHTTPClientTransport(url, { authProvider })
// First connect: 401 → the SDK's OWN auth flow runs. No forms.
await client.connect(transport()).catch(() => {})
// Second connect: retry with the bearer, then call the REAL tool.
await client.connect(transport())
const result = await client.callTool({
name: 'define',
arguments: { word: 'serendipity' },
})
// → the real definition returns; the account is now a side effect of use.
Condensed from apps/mcp-e2e — a real, passing end-to-end test against the live
stack over real Postgres.
Building an agent that discovers and onboards vendors autonomously? Point it at
/llms.txt — the machine-readable what-Handsel-is, the discovery chain,
and the hello-world, written to be pasted into an agent.
Playground — a live, hosted climb against your API is on the way. It ships from another workstream; this page will link it when it lands.