Handsel. Protocol rails for agent-native commerce
Reference implementation · Stripe test mode · No external vendor live yet
Zero-form first value for usage-priced APIs

The machine economy runs a tab, not a signup.

Handsel unbundles your signup form into six protocol acts, carried in the OAuth 2.1 / MCP authorization slots agent clients already speak. An unmodified client reaches first value, and a co-signed receipt, with zero forms — the relationship materializes from use, not before it.

the climb — one unmodified MCP client · 401 → co-signed receipt · no forms
01vendorPOST /mcp401 + WWW-Authenticate: Bearer · RFC 9728 challenge, unchanged
02vendorGET /.well-known/oauth-protected-resourceauthorization_servers: [ core ] · RFC 9728
03coreGET /.well-known/oauth-authorization-serverAS metadata · RFC 8414
04corePOST /v1/oauth/register201 client_id · dynamic client registration · RFC 7591
05coreGET /authorize302 → redirect_uri?code=…&iss=https%3A%2F%2Fcore.handsel.ai · provisions on sight — no page, no form, no fields
06corePOST /v1/tokencode + PKCE S256 → guest token, ent.cpd embedded · OAuth 2.1
07vendorPOST /mcpretry with the bearer → MCP session initializes
08vendorPOST /mcptools/list → [ define ]
09vendorPOST /mcptools/call define("serendipity") → 200 · price-0 co-signed receipt

Protocol trace — illustrative, from SPEC §21's arc; the live in-browser run activates when the core enables browser CORS.

define("serendipity") returns the real bundled definition — no model, no fake inference.

0 forms · 1 grant · a co-signed receipt
I sell a usage-priced API. A machine doesn't push through your signup form — it routes around it to a rival that answers on the first call. Mount ~20 lines of config, keep your tool handler: zero forms, every call receipted. Integrate ↓ Show me my API →
I build agents. Zero Handsel-specific code to reach first value: your stock MCP client's own OAuth machinery drives the rung-0 climb — the rungs above ride the same rails, connector-supplied — and the account becomes a side effect of the first call. Quickstart for agents →
The wallSPEC §1

The signup form is not friction to be reduced. It is a wall to be removed.

A signup form is six services a vendor sells to itself and bills your users, six minutes at a time.

Handsel doesn't shrink the form — it unbundles it. Each job becomes a just-in-time step-up on one grant that climbs tiers without ever re-onboarding, paid the instant it's needed and not before. Underneath, every vendor sees a pairwise subject: a per-vendor identifier that can't be joined across vendors. The seventh job — making the user fund the vendor's risk up front — Handsel simply drops.

The form sold the vendorHandsel re-provides it asPaid when
Credential issuance Rung 0 · guest a grant, on sight on first call
Identity + contact capture Rung 1 · claimed verified OIDC / email, same grant quota runs out
Pricing consent + payment path Rung 2 · mandated user-signed, spend-capped mandate — no card at the vendor at commit
Risk filtering Rung 3 · assured org attestation / higher cap the floor demands it
A durable principal always pairwise subject, stable bottom-to-top always, silently
Making the user fund the vendor's risk never

Six jobs, one grant — each re-provided just in time, none demanded up front.

The six actsSPEC §8–14

Six acts, in order. Each carried in a slot your client already speaks.

Software stops being sold by persuasion and starts being bought on evidence.

  1. IOpena grant, on sight
  2. IITrybudgeted, TTL-bounded trials
  3. IIIProveone blinded workload, signed
  4. IVCommitpromote the winner in place
  5. VEvaporatelosers garbage-collect
  6. VISettleco-signed receipts, never arrears
Integrate@handsel/sdk

~20 lines of config. Keep your tool handler.

Mount the SDK over your existing endpoint. It serves the well-knowns, signs your tier schedule, emits the standard RFC 9728 401 challenge and the step-up 403 unchanged, meters guest quota locally with no phone-home, and writes a vendor-signed receipt per call.

The vendor edge

/**
 * Word Count — a Handsel MCP vendor over @handsel/mcp. One REAL deterministic tool,
 * word_count (a pure function of its input — no dataset, no inference, no network on
 * the answer path), sits behind the Handsel ladder: @handsel/sdk enforces guest/claimed
 * quota, the 401/403 challenges, and receipts. Configure via env (see .env.example).
 */
import { serveMcpVendor } from '@handsel/mcp'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { z } from 'zod'

const env = z
  .object({
    HANDSEL_CORE: z.string().url(),
    VENDOR_RESOURCE: z.string().url(),
    VENDOR_SIGNING_SEED: z
      .string()
      .regex(/^[0-9a-fA-F]{64}$/, 'expected a 64-hex Ed25519 seed from `handsel keygen`')
      .optional(),
    VENDOR_SECRET: z.string().optional(),
    CORE_SECRET: z.string().optional(),
    PORT: z.coerce.number().int().positive().optional(),
  })
  .parse(process.env)

await serveMcpVendor({
  product: 'word-count',
  name: 'Word Count',
  resource: env.VENDOR_RESOURCE,
  handselCore: env.HANDSEL_CORE,
  ...(env.VENDOR_SIGNING_SEED ? { signingSeed: env.VENDOR_SIGNING_SEED } : {}),
  ...(env.VENDOR_SECRET ? { vendorSecret: env.VENDOR_SECRET } : {}),
  ...(env.CORE_SECRET ? { coreSecret: env.CORE_SECRET } : {}),
  // Pricing models — the receipt bills price_usd_micros * units (patterns: the
  // @handsel/sdk README, "Pricing models"). Beyond per-call flat:
  //   metered       — unit: 'token' + onCall: (body) => ({ units: yourCount(body) })
  //   success-gated — bill_on_miss: false + onCall: (body) => ({ miss: wasEmpty(body) })
  //   freemium      — price_usd_micros: 0 (calls co-sign price-0 proof-of-use receipts)
  pricing: { unit: 'call', price_usd_micros: 500 },
  tiers: { guest: { calls_per_day: 5 }, claimed: { calls_per_day: 500 } },
  terms: 'Word Count demo vendor. Calls are metered per the vendor-signed schedule.',
  port: env.PORT ?? 8787,
  // Return a fresh MCP server per request; @handsel/mcp owns the transport. Register
  // your real tools here — word_count is pure + deterministic (Covenant 3).
  buildServer: () => {
    const server = new McpServer({ name: 'word-count', version: '0.1.0' })
    server.registerTool(
      'word_count',
      {
        title: 'Count words',
        description:
          'Count the whitespace-separated words in a string. Deterministic; no dataset, no inference.',
        inputSchema: { text: z.string().describe('The text to count words in.') },
      },
      ({ text }) => {
        const trimmed = text.trim()
        const count = trimmed === '' ? 0 : trimmed.split(/\s+/).length
        return { content: [{ type: 'text' as const, text: String(count) }] }
      },
    )
    return server
  },
})

Byte-identical to what npm create @handsel/vendor --product word-count scaffolds — the one synced hello-world across the SDK docs and this site (docs:sync).

The ladder, one command at a time

None of these are on npm yet — this is the shape of the arc.

$ npm create @handsel/vendor scaffold
$ npx handsel keygen Ed25519 seed
$ npx handsel register claim a tenant
$ npx handsel verify self-grading exam
$ npx handsel claim connect Stripe
$ npx handsel connect go live

0 forms · 0 sales calls · 0 humans. KYC is Stripe's, caps are published, promotion is code.

Three depths, one gradient: SDK (20 lines) · proxy (zero code) · your agent (/handsel-integrate).

Your agent reads /llms.txt and opens the PR.

ProofSPEC §17

A reference implementation that runs end to end today — you just can't npm-install it yet.

What's real, stated plainly:

0forms

the rung-0 zero-form MCP e2e (pnpm e2e:mcp-guest) — a stock client reaching a receipted first call, over real Postgres.

12invariants

SPEC §17's inviolable set — no negative balance, bounded overspend, idempotent settlement, no cash-out — asserted by fuzz and conformance against real Postgres.

$0custody

no withdrawal, transfer, or resale-liquid path anywhere in the protocol; refunds only to the funding source.

Spec, SDK, and conformance suite are private today — by choice, until the design partners have shaped them. Stripe is in test mode by design: no real money moves until counsel signs off, and money-path deploys are human-gated.

# the whole arc, from a clone — design partners get the clone
pnpm i && pnpm db:up && pnpm e2e:mcp-guest
PricingSPEC §12.3 · §17

1.75% of Handsel-settled flow. $0 until money moves. The sandbox is free.

The rails' cut rides as an application_fee_amount on the vendor's own Stripe Connect account — on the primary rail, Handsel is not merchant of record and holds $0.

Integer USD micros · Balances never negative · No cash-out, anywhere

Test mode by design — no live payment moves until counsel signs off; money-path deploys are human-gated.

Design partners3–5 slots · pre-launch

We're hand-picking 3–5 design partners before the spec goes public.

If you sell a usage-priced API, the pitch is one sentence: we'll show you your API running behind Handsel — an unmodified agent client reaching your tool with zero forms, every call receipted, settlement in Stripe test mode — before you commit to anything.

One partner per category. First movers shape the tier schedule and the spec while both can still move, and get the handsel-enabled mark first when the registry flag ships.

We don't do forms — not even ours. Email is the whole flow:

The register stays honest here: reference implementation, Stripe test mode, no external vendor live yet. You'd be the first external vendor — that's the design-partner offer, not a gap.

Playground — paste your API's base URL and watch an unmodified agent climb it behind an ephemeral Handsel proxy: 401 challenge, zero-form guest grant, your real response, a co-signed receipt — sandbox tier, gone in an hour. Preview the playground → (the hosted run is still landing; until then the page degrades honestly and shows the local-run recipe.)

The signup form was the checkout counter of software sold to humans by persuasion. Handsel is where software is bought by machines on evidence:
open · try · prove · commit · evaporate · settle

Your API behind Handsel — an unmodified agent reaching your tool with zero forms — before you commit to anything.

Show me my API behind Handsel