---
name: libramen
description: Use when the user wants to find, scope, price, compare, check availability for, or book a real-world service provider through Libramen MCP, including local experiences, appointments, trade work, professional services, and other qualified service-provider transactions. Do not use for digital goods, online shopping, or general information queries that do not involve a real-world service provider.
---

# Libramen — Agentic Commerce Skill

> Connect your user to qualified service providers through the Libramen network via MCP. This skill handles discovery, scoping, pricing, and payment for real-world services — from trade projects to professional engagements.

## When to Activate

Activate when the user wants to:
- Scope a service (e.g., "I need my roof re-done", "get a cybersecurity audit quote")
- Engage a professional or trade provider (e.g., "find a consulting firm", "get a plumber")
- Compare qualified service providers for a specific need
- Check availability or pricing for a real-world service

Do NOT activate for:
- Online shopping / e-commerce (digital goods)
- General information queries
- Tasks that don't involve a real-world service provider

---

## Quick Reference

### Tools

| Tool | Purpose | Key Input | Key Output |
|------|---------|-----------|------------|
| `search_businesses` | Discover providers across the network | `search`, `location` | `businesses[]` with `gateway` URL |
| `list_products` | Browse a business's service catalog | `organization` (slug) | `items[]` with name, basePrice |
| `check_availability` | Query available time slots | `productId`, `startDate` | `slots[]` with capacity status |
| `evaluate_transaction` | Qualify a transaction iteratively | `productId`, `parameters` | `status`, `nextQuestion` or `mandate` |
| `confirm_mandate` | Execute payment after user approval | `mandateToken`, `paymentMethod`, `buyer` | `status` (CAPTURED / PAYMENT_REQUIRED / PENDING / PENDING_APPROVAL / FAILED) |
| `finalize_x402_payment` | Settle an X402 stablecoin payment after on-chain transfer | `mandateToken`, `txHash` | `status` (EXECUTED / FAILED) |
| `get_mandate_status` | Look up mandate state when a buyer asks "how's my booking?" — typically after `PENDING_APPROVAL` (manual-confirm orgs) | `organizationId`, `mandateId` | `status`, `ttlExpiresAt`, `rejectionReason?` |

### Rate Limits

| Metric | Limit |
|--------|-------|
| `search_businesses` per IP | 10 / minute |
| All MCP requests per IP | 40 / minute |
| `evaluate_transaction` per IP+org | 60 / hour |
| `confirm_mandate` per IP+org | 10 / hour |
| `confirm_mandate` attempts per mandate | 3 (then re-issue mandate via `evaluate_transaction`) |
| Distinct orgs per IP | 5 / hour |
| Active mandates per session | 1 |
| Connections per IP | 10 |

---

## Connection

Libramen is a **remote MCP server over SSE**. There is no npm package or local install — register the endpoint as a remote SSE MCP server in your host's MCP config (Claude Code, Cursor, ChatGPT desktop, your own runtime, etc. — exact registration syntax is host-specific; consult your host's "remote MCP server" docs).

- **Endpoint:** `https://api.libramen.ai/api/v1/mcp/sse`
- **Transport:** Server-Sent Events. The SSE stream's first `endpoint` event returns the per-session `/api/v1/mcp/message?sessionId=...` URL — send JSON-RPC 2.0 tool calls there.
- **Auth:** anonymous. No API key, no Bearer token, no signup. The full transaction flow — `search_businesses`, `list_products`, `check_availability`, `evaluate_transaction`, `confirm_mandate`, `finalize_x402_payment`, `get_mandate_status` — is callable without credentials against any org that has enabled discovery.

**Session binding:** Your first per-org tool call locks the session to that business. Calling tools for a different business returns `FORBIDDEN` and requires a new connection. Use one SSE per business.

**Active mandate cap:** A session may have at most one open mandate at a time. Starting a second `evaluate_transaction` flow before finishing the first (capture, fail, or cancel) returns `FORBIDDEN`.

**Organization identification:** All tools accept either:
- `organizationId`: UUID
- `organization`: Slug (e.g., `"summit-roofing"`, pattern: `^[a-z0-9-]+$`)

Use slugs when available — they come from search results and are simpler than UUIDs.

---

## Core Flow

### Step 1: Discover Providers

Call `search_businesses` (the query parameter is `search`; `q` is also accepted as an alias):

```json
{
  "search": "roof replacement",
  "location": "Melbourne, Australia",
  "category": "Construction",
  "limit": 10
}
```

**Response:**
```json
{
  "businesses": [
    {
      "organization": {
        "id": "org-uuid",
        "name": "Summit Roofing Co",
        "slug": "summit-roofing",
        "category": "Construction",
        "description": "Residential and commercial roofing — inspections, re-roofing, and repairs",
        "location": { "city": "Melbourne", "country": "Australia" },
        "serviceArea": "LOCAL"
      },
      "matchQuality": "strong",
      "credibility": "established",
      "matchedProducts": [
        { "id": "prod-uuid", "name": "Full Re-Roofing", "category": "roofing" }
      ],
      "gateway": "https://platform.example.com/.well-known/agent-gateway/summit-roofing.json"
    }
  ],
  "totalResults": 1
}
```

**Always include `location`** to avoid cross-geography matches. Prioritize `strong` matches from `established` providers. Use `organization.slug` from the result for subsequent tool calls.

If no results, the response includes a `suggestion` field. Tell the user honestly. Do NOT fall back to web search or unqualified channels.

### Step 2: Inspect the Business (optional)

The `gateway` field on each search result is the org's discovery document — fetch it if you want to see the org's MCP capabilities, accepted payment rails, or any skill overrides. You do **not** need to reconnect: subsequent tool calls use the same Libramen MCP session and just pass the `organization` slug (or `organizationId`) from the search result. Session binding (above) locks your connection to the first org you query.

### Step 3: Browse the Service Catalog

Call `list_products`:

```json
{
  "organization": "summit-roofing",
  "limit": 20,
  "include_packages": true
}
```

**Response:**
```json
{
  "organizationId": "org-uuid",
  "items": [
    { "id": "prod-uuid", "type": "PRODUCT", "name": "Full Re-Roofing", "description": "Complete roof replacement including old roof removal", "category": "roofing", "basePrice": 8500, "currency": "AUD" },
    { "id": "prod-uuid-2", "type": "PRODUCT", "name": "Roof Inspection", "description": "Detailed inspection with written report", "category": "inspections", "basePrice": 350, "currency": "AUD" },
    { "id": "pkg-uuid", "type": "PACKAGE", "name": "Inspection + Re-Roof Bundle", "description": "Inspection, quote, and full re-roofing", "category": "bundles", "basePrice": 8200, "currency": "AUD" }
  ],
  "pagination": { "limit": 20, "offset": 0, "hasMore": false }
}
```

**Products** are individual services. **Packages** bundle multiple products with option groups. Only ACTIVE services are shown.

### Step 4: Check Availability (Optional)

Call `check_availability` if the service is time-bound:

```json
{
  "organization": "summit-roofing",
  "productId": "prod-uuid",
  "startDate": "2026-05-01",
  "endDate": "2026-05-14",
  "participantCount": 1
}
```

**Response:**
```json
{
  "productId": "prod-uuid",
  "dateRange": { "from": "2026-05-01T00:00:00Z", "to": "2026-05-14T00:00:00Z" },
  "slots": [
    { "startTime": "2026-05-05T08:00:00Z", "endTime": "2026-05-05T16:00:00Z", "remainingCapacity": 2, "status": "AVAILABLE" },
    { "startTime": "2026-05-08T08:00:00Z", "endTime": "2026-05-08T16:00:00Z", "remainingCapacity": 1, "status": "LIMITED" },
    { "startTime": "2026-05-12T08:00:00Z", "endTime": "2026-05-12T16:00:00Z", "remainingCapacity": 0, "status": "FULL" }
  ],
  "nextAvailable": { "startTime": "2026-05-05T08:00:00Z", "endTime": "2026-05-05T16:00:00Z", "remainingCapacity": 2 },
  "totalSlots": 3
}
```

Defaults: `startDate` = now, `endDate` = startDate + 7 days. `nextAvailable` searches 30 days ahead.

Not all services are time-slotted. Project-based services (construction, consulting) may not have time slots — availability is most relevant for appointment-based services (inspections, consultations, training). If a product has no availability data, skip this step.

### Step 5: Scope and Qualify

`evaluate_transaction` is the core tool. It uses **progressive disclosure** — start with empty parameters and let the system guide you through what it needs.

```json
{
  "organization": "summit-roofing",
  "productId": "prod-uuid",
  "parameters": {}
}
```

**Optional field:**
- `requestedDate` — ISO date string. When set, the unified availability resolver fires inline against the operator's external live-availability source (calendar, iCal feed, or custom API) in the same call. Pass it instead of (not in addition to) calling `check_availability` separately when you already know the date — saves one tool call and ensures the resolver's slot decision is what binds the mandate.

#### Response: REQUIRES_MORE_INFO

The system tells you exactly what information to collect from the user:

```json
{
  "organizationId": "org-uuid",
  "status": "REQUIRES_MORE_INFO",
  "isEligible": false,
  "outcomes": [],
  "nextQuestion": {
    "parameterId": "roof-area-id",
    "parameterName": "Roof Area (sqm)",
    "prompt": "What is the approximate roof area in square metres?",
    "type": "NUMBER",
    "helpText": "Measure the footprint of the building — typical house is 150-250 sqm"
  }
}
```

Ask the user for the value. If `nextQuestion.options` is present, present them as choices — don't ask for free text. Then re-call with the answer **added to all previous parameters**:

```json
{
  "organization": "summit-roofing",
  "productId": "prod-uuid",
  "parameters": { "roof-area-id": 180 }
}
```

The system may ask several questions (roof pitch, material preference, access constraints, etc.). Keep iterating — each call returns the next requirement until resolved.

`nextQuestion.type` values you may see: `NUMBER`, `TEXT`, `STRING`, `DATE`, `DATETIME`, `TIME`, `BOOLEAN`, `SELECT`, `MULTISELECT`, `RANGE`. Treat unknown values as free text.

#### Response: BLOCKED

A business requirement wasn't met. The `blockReason.message` is human-readable — surface it to the user, ask them to adjust their input, then re-run `evaluate_transaction`.

```json
{
  "organizationId": "org-uuid",
  "status": "BLOCKED",
  "isEligible": false,
  "outcomes": [],
  "blockReason": {
    "constraintId": "c-uuid",
    "constraintName": "Minimum Roof Area",
    "message": "Value must be at least 50"
  }
}
```

If a requirement can't be met:
1. Re-run `evaluate_transaction` with adjusted parameters after the user clarifies.
2. Browse other services from the same provider (`list_products`) — a different product may have different requirements.
3. Search for alternative providers (`search_businesses`) with adjusted criteria.
4. Tell the user honestly that this provider can't accommodate their needs.

#### Response: APPROVED

The transaction is fully qualified with pricing:

```json
{
  "organizationId": "org-uuid",
  "status": "APPROVED",
  "isEligible": true,
  "outcomes": [
    { "type": "PRICING", "value": { "grandTotal": 9200, "currency": "AUD" } }
  ],
  "mandate": {
    "token": "eyJhbGciOi...",
    "amount": 9200,
    "currency": "AUD",
    "expiresAt": "2026-04-17T14:05:00Z"
  },
  "available_payment_methods": ["X402", "STRIPE"],
  "paymentMethods": {
    "x402": true,
    "stripeSpt": true,
    "stripe": { "publishableKey": "pk_live_...", "networkId": "profile_..." },
    "mpp": { "tempoRecipientAddress": "tempo1...", "testnet": false },
    "googlePay": null
  }
}
```

**Slot reservation:** APPROVED immediately reserves the first available slot. The reservation is held for 5 minutes (the mandate expiry). If the mandate expires without payment, the reservation releases automatically.

**Choosing a payment method:** read the structured `paymentMethods` field for unambiguous capability discovery. Use `paymentMethods.stripeSpt === true` to know the org accepts Stripe Shared Payment Tokens; use `paymentMethods.stripe.publishableKey` if you need to tokenize a card client-side; use `paymentMethods.stripe.networkId` (the merchant's Stripe profile ID, format `profile_xxx`) if you're a Stripe Link agent or another wallet minting an SPT scoped to this merchant. The `available_payment_methods` array is a coarse hint with provider names (`X402`, `STRIPE`, `MPP`); do NOT rely on it to detect SPT capability — `STRIPE_SPT` does not appear there.

#### Optional: Packages with Options

For packages with option groups, pass `selectedOptions`:

```json
{
  "organization": "summit-roofing",
  "packageId": "pkg-uuid",
  "parameters": { "roof-area-id": 180 },
  "selectedOptions": { "material-group-id": "colorbond-option-id" }
}
```

### Step 5b: Get Mandate Status (manual-approval flow)

Call `get_mandate_status({ organizationId, mandateId })` when the buyer asks for an update on a `PENDING_APPROVAL` mandate. **This is a lookup, not a polling endpoint.** Rate limits apply per mandate.

Returns one of:

| `status` | Meaning |
|----------|---------|
| `PENDING_APPROVAL` | Operator hasn't decided yet. `ttlExpiresAt` populated — communicate that window to the buyer. |
| `CONFIRMED` | Operator approved (manual-confirm) OR an X402 mandate is between `confirm_mandate` and `finalize_x402_payment`. Capture / on-chain settlement still pending. |
| `EXECUTED` | Settled. Payment captured; booking is committed. |
| `REJECTED` | Operator declined. `rejectionReason` may be populated — relay verbatim to the buyer. |
| `EXPIRED` | Operator didn't respond in time. Slot released, no payment. |
| `PAYMENT_FAILED` | Operator approved but the card capture failed. Slot released — buyer can try again with a different payment method. |
| `CANCELLED` | Mandate was cancelled before settlement (buyer-initiated or operator-initiated). |
| `PENDING` | Payment in progress (e.g., Stripe async-capture didn't resolve within the long-poll window). |
| `NOT_FOUND` | Wrong org for this mandate, or unknown mandate. |

### Step 6: Confirm Payment

**Never call `confirm_mandate` without explicit user approval of the amount.**

`confirm_mandate` requires three things: the mandate token, an explicit `paymentMethod`, and a `buyer` object. **Omitting `paymentMethod` returns `FAILED { errorCode: "MISSING_PAYMENT_METHOD" }`** — there is no implicit default. The error message lists the rails the merchant has configured; pick one explicitly.

**Confirmation channel.** For inline outcomes (`CAPTURED` / `PAYMENT_REQUIRED` / `PENDING` / `FAILED`) the agent is the buyer's confirmation channel — relay the result directly. The buyer also always receives an email receipt for every successful booking, so you do not need to be the only channel.

**`PENDING_APPROVAL` status — operator manual approval flow (the exception).** Some operators require human review before any booking commits. When `confirm_mandate` returns `{ status: "PENDING_APPROVAL", mandateId, ttlExpiresAt }`, do NOT poll. Tell the buyer the operator will respond within the time window indicated by `ttlExpiresAt`, then disconnect — operator review is asynchronous and may take hours. From this point the buyer's email is the canonical channel: the operator's lifecycle emails (pending → approved / rejected / expired / payment-failed) drive status, not the agent. If the buyer asks the agent for an update before an email arrives (or on a fresh reconnect), call `get_mandate_status({ organizationId, mandateId })` (Step 5b). Operators on manual-confirm orgs only support **STRIPE_SPT** and **CARD_TOKEN** rails; presenting `X402` or `MPP` returns `FAILED { errorCode: "MANUAL_CONFIRM_RAIL_UNSUPPORTED" }`.

**Mandate state after confirm.** For X402, `confirm_mandate` returns `PAYMENT_REQUIRED` and the mandate sits at `CONFIRMED` until you call `finalize_x402_payment`, which advances it to `EXECUTED`. For instant rails (Stripe SPT, CARD_TOKEN, MPP), a `CAPTURED` response means the mandate is already `EXECUTED`.

#### Buyer Identity

`buyer` is **required** on every `confirm_mandate` call. Missing or malformed buyer returns `FAILED { errorCode: "VALIDATION_ERROR" }` and the payment is never attempted. The business needs this to deliver the service, send receipts, and defend chargebacks.

Required fields:
- `email` — RFC 5322 valid address.
- `phone_number` — E.164 format recommended (e.g., `"+14155551234"`).
- A name: either `full_name` OR both `first_name` and `last_name`.

Optional fields:
- `customer_id` — your stable identifier for this buyer (pass-through, not stored by Libramen).
- `account_type` — `"guest"`, `"registered"`, or `"business"`.

Collect this information **after the APPROVED response, before calling `confirm_mandate`** — never during the scoping/qualification flow.

#### Request

Full-name variant:

```json
{
  "organization": "summit-roofing",
  "mandateToken": "eyJhbGciOi...",
  "paymentMethod": { "type": "X402" },
  "buyer": {
    "email": "alex@example.com",
    "phone_number": "+14155551234",
    "full_name": "Alex Example"
  }
}
```

Split-name variant:

```json
{
  "organization": "summit-roofing",
  "mandateToken": "eyJhbGciOi...",
  "paymentMethod": { "type": "STRIPE_SPT", "sptToken": "spt_test_xxx" },
  "buyer": {
    "email": "alex@example.com",
    "phone_number": "+14155551234",
    "first_name": "Alex",
    "last_name": "Example",
    "account_type": "registered"
  }
}
```

#### Payment Methods

| Type | Description | Required Field | Provenance |
|------|-------------|----------------|------------|
| `X402` | Stablecoin/USDC on-chain payment | none | n/a (you broadcast on-chain) |
| `STRIPE_SPT` | Stripe Shared Payment Token (SPT) | `sptToken` (e.g. `spt_xxx`) | Minted by any wallet that can issue Stripe SPTs — Stripe Link via [`link-cli`](https://github.com/stripe/link-cli) is the canonical example. Your user approves a single-use credential on their device, then you pass `sptToken` to `confirm_mandate`. Libramen does not issue these. |
| `CARD_TOKEN` | Stripe `tok_xxx` / `pm_xxx` payment-method token | `token` | You must integrate Stripe Elements or Payment Intents on your side to obtain the token. |
| `MPP` | Machine Payments Protocol credential | `mppCredential` (or `_meta["org.paymentauth/credential"]` on the request) | Issued by your MPP provider; Libramen does not issue these. The `_meta` transport is the canonical spec-aligned path; the schema field is also accepted. **Your wallet must hold enough TIP-20 stablecoin to cover the payment amount plus Tempo gas** (sub-$0.001 per transfer via Tempo's Fee AMM) — the agent broadcasts the on-chain transfer itself before presenting the credential. |

#### Stripe Link SPT via Link CLI

Use this path when `paymentMethods.stripeSpt === true` and the buyer chooses Link. Libramen does not mint the token; Link or another agent wallet does.

1. Read `paymentMethods.stripe.networkId` from the APPROVED response. This is the merchant Stripe profile ID.
2. Use the approved `mandate.amount` exactly, in minor units, and the mandate currency lowercased for the Link CLI `--currency` value. Do not convert, round, or reprice.
3. Authenticate Link and choose a saved payment method:

```sh
link-cli auth status
link-cli auth login --client-name "Your agent name" # only if not authenticated
link-cli payment-methods list --format json
```

4. Create the spend request. For SPT requests, pass `--credential-type shared_payment_token` and `--network-id`; do not pass `--merchant-name` or `--merchant-url`. `--context` must be at least 100 characters and should describe the exact Libramen booking the buyer is approving.

```sh
link-cli spend-request create \
  --credential-type shared_payment_token \
  --network-id <paymentMethods.stripe.networkId> \
  --payment-method-id <link_payment_method_id> \
  --amount <mandate.amount> \
  --currency <mandate.currency-lowercase> \
  --context "<100+ char human-readable booking description>" \
  --line-item "name:<service name>,unit_amount:<mandate.amount>,quantity:1" \
  --total "type:total,display_text:Total,amount:<mandate.amount>" \
  --request-approval \
  --format json
```

`--request-approval` pushes the approval to Link. In agent/non-TTY mode, follow the returned `_next.command`; otherwise resume polling manually:

```sh
link-cli spend-request retrieve <lsrq_id> \
  --interval 2 \
  --max-attempts 300 \
  --format json
```

5. After the spend request status is `approved`, retrieve the actual single-use SPT. This include is required; a plain retrieve may omit the credential.

```sh
link-cli spend-request retrieve <lsrq_id> \
  --include shared_payment_token \
  --format json
```

Read `shared_payment_token.id` from the JSON response and pass it to Libramen:

```json
{
  "paymentMethod": {
    "type": "STRIPE_SPT",
    "sptToken": "spt_xxx"
  }
}
```

SPTs are single-use and short-lived. If `confirm_mandate` returns `CREDENTIAL_EXPIRED`, `WRONG_AMOUNT`, `WRONG_MERCHANT`, `CARD_DECLINED`, or another typed SPT error, create a fresh spend request only when the recovery table says retry is appropriate. Stripe SPT confirmation may wait up to 90 seconds for webhook settlement; MCP clients should use `resetTimeoutOnProgress: true` or a request timeout of at least 100 seconds.

**X402 `payerAddress` (optional):** if your wallet address is fixed, set `paymentMethod.payerAddress` to your 0x-prefixed EVM address — the server will reject on-chain transfers from any other address (front-run protection). Comparison is case-insensitive (`0xABC...` and `0xabc...` are treated as the same address). **First-write-wins:** once a mandate is confirmed with a `payerAddress`, retrying with a different address returns `PAYER_ADDRESS_MISMATCH`. Omit `payerAddress` if you use ephemeral or rotating wallets, since front-run protection isn't useful in that model anyway.

**MPP probe transport (important — different from X402).** Calling `confirm_mandate` with `paymentMethod: { type: "MPP" }` and **no credential** is a probe. The server responds with a **JSON-RPC error code `-32042`** carrying the challenge in `error.data.mppChallenge` (fields: `wwwAuthenticate`, `paymentMethods`, `amount`, `currency`, optional `description`) — NOT a successful tool response with `status: "PAYMENT_REQUIRED"`. Catch the JSON-RPC error, present the challenge to your MPP provider, then retry `confirm_mandate` with the resulting credential. Don't write `if (result.status === 'PAYMENT_REQUIRED')` for MPP — that path is X402-only.

#### Responses

**CAPTURED** — payment succeeded:
```json
{ "status": "CAPTURED", "paymentId": "pay-uuid" }
```

`paymentId` shape depends on rail: Stripe SPT / CARD_TOKEN return a `pay-uuid` you can look up in the Stripe dashboard; MPP returns the Tempo tx hash (0x-prefixed, 64 hex chars) you can paste into the [Tempo block explorer](https://explore.testnet.tempo.xyz); X402 returns the tx hash you submitted to `finalize_x402_payment`.

**PAYMENT_REQUIRED** — X402 invoice issued; you must broadcast the transfer on-chain and then call `finalize_x402_payment`:
```json
{
  "status": "PAYMENT_REQUIRED",
  "invoice": {
    "invoice": "encoded-invoice",
    "paymentAddress": "0x1234...",
    "network": "BASE_SEPOLIA",
    "expiresAt": "2026-04-17T14:20:00Z"
  }
}
```

Valid `network` values are `BASE`, `BASE_SEPOLIA`, `ETHEREUM` — uppercase.

**PENDING** — payment in progress (e.g., Stripe async-capture didn't resolve within the 90s server-side wait):
```json
{ "status": "PENDING", "paymentId": "pay-uuid" }
```

**FAILED** — payment failed:
```json
{ "status": "FAILED", "error": "Insufficient funds", "errorCode": "PAYMENT_FAILED" }
```

#### Cancellation Policy

When the operator has configured a support contact, every non-FAILED `confirm_mandate` response (CAPTURED, PAYMENT_REQUIRED, PENDING, PENDING_APPROVAL) carries:

```json
{
  "cancellationPolicy": {
    "method": "contact_operator",
    "contactEmail": "support@example.com",
    "instructions": "..."
  }
}
```

Relay `contactEmail` and `instructions` to the buyer so they know where to request a cancellation or refund. Libramen does not process refunds — the operator handles money movement on their side.

#### Stripe SPT error codes

When the rail is `STRIPE_SPT`, Libramen returns one of these typed `errorCode` values so you can pick the right recovery action without parsing free-form text:

| `errorCode` | What it means | Recovery |
|-------------|---------------|----------|
| `WRONG_AMOUNT` | The SPT was authorised for a different amount than the mandate. | Re-mint an SPT for the mandate's `amount`. |
| `CREDENTIAL_EXPIRED` | The SPT's TTL elapsed (Stripe Link mints with ~9 min TTL). | Re-mint a fresh SPT and retry. |
| `WRONG_MERCHANT` | The SPT is bound to a different merchant's network_id. | Terminal — re-minting won't help. Do not retry against this merchant. The error message is generic by design (won't echo any other merchant's identity). |
| `CARD_DECLINED` | Underlying card was declined. | Surface to the user; they may need a different funding source. |
| `MANDATE_STATE_MISMATCH` | The mandate has already advanced past `PENDING` (e.g. concurrent confirm). | Don't retry — re-evaluate. |
| `PAYMENT_INTENT_FAILED` | Stripe rejected the intent (catch-all). | Retry once with a fresh SPT; if it persists, surface to the user. |
| `PAYMENT_FAILED` | Catch-all for unmapped Stripe failures. | Retry with a fresh SPT. |
| `CAPTURE_FAILED` | Auth succeeded but capture failed. | Retry once. |

#### Paying with Stripe Link

Stripe Link agents (any agent using [`link-cli`](https://github.com/stripe/link-cli) or another wallet that mints Stripe SPTs) use the detailed **Stripe Link SPT via Link CLI** flow above. The critical handoff is: create a spend request for `paymentMethods.stripe.networkId`, wait for approval, retrieve it with `--include shared_payment_token`, then pass `shared_payment_token.id` as `paymentMethod.sptToken`.

**Long-poll caveat.** For 3DS or async-capture flows, Libramen holds the response open up to 90 seconds while it waits for Stripe to confirm settlement. Set your MCP client's `requestTimeout` to ≥ 100 s, or pass both `resetTimeoutOnProgress: true` AND a `progressToken` on the request — without `progressToken`, no progress events fire and `resetTimeoutOnProgress` has nothing to reset against. Handle the `errorCode` per the table above.

### Step 7: Settle X402 (only when `confirm_mandate` returned PAYMENT_REQUIRED)

After receiving the X402 invoice, broadcast the stablecoin transfer on-chain to the `paymentAddress` for the `amount` shown in the mandate, **wait for at least one block confirmation**, then call `finalize_x402_payment`:

```json
{
  "organization": "summit-roofing",
  "mandateToken": "eyJhbGciOi...",
  "txHash": "0xabc...64hexchars"
}
```

`txHash` is the 0x-prefixed 32-byte (64-hex-char) transaction hash of the on-chain transfer.

**Response — EXECUTED:**
```json
{
  "status": "EXECUTED",
  "paymentId": "0xabc...64hexchars",
  "mandateStatus": "EXECUTED"
}
```

`paymentId` on EXECUTED equals the `txHash` you submitted.

**Response — FAILED:**
```json
{
  "status": "FAILED",
  "error": "Transaction receipt not found",
  "errorCode": "CHAIN_VERIFICATION_FAILED"
}
```

The server verifies the on-chain receipt against the mandate's expected recipient, amount, and network. If the tx isn't yet visible on-chain, you'll get `CHAIN_VERIFICATION_FAILED` with reason "Transaction receipt not found" — wait and retry with backoff (typical: 5–10s for testnets, 15–30s for mainnet). Do not assume failure on the first attempt.

### Step 8: Cleanup

Close SSE connections when the transaction completes or the user abandons.

---

## Error Handling

Most errors arrive as JSON-RPC 2.0 envelopes with an `error.data.errorCode`. Two outliers:
- **Connection-limit errors** (more than 10 concurrent SSE per IP) come back as **plain HTTP 429** with body `{ error, message, limit, current }` — not JSON-RPC.
- **MPP probes** come back as JSON-RPC `-32042` with the challenge in `error.data.mppChallenge` (not a `FAILED` tool result).

Example envelope:

```json
{
  "jsonrpc": "2.0",
  "error": {
    "code": -32005,
    "message": "Rate limit exceeded",
    "data": { "errorCode": "RATE_LIMIT_EXCEEDED", "retryAfterSeconds": 30 }
  },
  "id": null
}
```

### Error Codes

| `errorCode` | Where | Recovery |
|-------------|-------|----------|
| `VALIDATION_ERROR` | any | Fix request shape (missing fields, wrong types, malformed buyer) |
| `MISSING_PAYMENT_METHOD` | confirm | Pick a rail and re-call |
| `MISSING_TOKEN` | confirm | Provide `token` (CARD_TOKEN) or `sptToken` (STRIPE_SPT) |
| `INVALID_TOKEN` | confirm / finalize | Mandate token is bad or tampered — re-evaluate |
| `EXPIRED` | confirm / finalize | Mandate window passed — re-evaluate |
| `ALREADY_USED` | confirm | Mandate already consumed — re-evaluate |
| `FORBIDDEN` | any | Wrong org for this session OR another mandate is still open — finish or cancel it, then re-evaluate |
| `NOT_FOUND` | any | Unknown product, organization, or mandate — check IDs |
| `RATE_LIMIT_EXCEEDED` | any | Honor `retryAfterSeconds` |
| `RAIL_TIMEOUT` | confirm | Rail didn't settle in time — retry once; if it persists, try a different rail |
| `PROVIDER_NOT_CONFIGURED` | confirm | Merchant hasn't finished payment setup for this rail — pick another |
| `MANUAL_CONFIRM_RAIL_UNSUPPORTED` | confirm | Manual-confirm org only accepts `STRIPE_SPT` or `CARD_TOKEN` |
| `EXTERNAL_SOURCE_REJECTED` | confirm | Slot was free at evaluate time but the operator's live calendar reported it busy at confirm. Mandate auto-cancels — re-evaluate with a different date or product. |
| `INVALID_PAYER_ADDRESS` | confirm (X402) | Bad EVM address format |
| `PAYER_ADDRESS_MISMATCH` | confirm (X402) | Retrying with a different wallet — keep the original or re-evaluate |
| `NO_X402_TRANSACTION` | finalize | You called finalize without a prior `confirm_mandate(type=X402)` — start over |
| `CHAIN_VERIFICATION_FAILED` | finalize | Tx unmined, wrong recipient/amount/network, or reverted — wait and retry, or re-evaluate |
| `TX_HASH_ALREADY_USED` | finalize | This tx already settled another mandate — start over |
| `CAPTURE_FAILED` | confirm (Stripe) | Authorisation succeeded but capture failed — retry once |
| `SERVICE_UNAVAILABLE` | any | Engine not configured — try another provider |

### JSON-RPC error codes

| Code | Meaning | Action |
|------|---------|--------|
| `-32005` | Rate limit exceeded | Wait `retryAfterSeconds`, then retry |
| `-32042` | Payment required (MPP probe) | Complete the payment challenge in `error.data.mppChallenge` and retry |

---

## Discovery Fallback (robots.txt)

If `search_businesses` returns no results and the user has a specific business website:

1. Fetch `{business_url}/robots.txt`
2. Look for: `Agent-Gateway: https://platform.com/.well-known/agent-gateway/{slug}.json`
3. If found, fetch that URL — it returns a discovery document with the MCP endpoint
4. Connect via the `endpoints.mcp_sse` value from the discovery document

Most businesses will NOT have this configured. If absent, tell the user the business isn't set up for agent transactions. Do not fabricate engagements through unqualified channels.

---

## Gotchas

| Mistake | Fix |
|---------|-----|
| Searching without location | Always include `location` — avoids matching a provider in the wrong country |
| Using UUIDs when slugs work | `organization: "summit-roofing"` is simpler than passing UUIDs |
| Sending both `productId` and `packageId` | Exactly one must be provided, never both |
| Not accumulating parameters | Each `evaluate_transaction` call must include ALL previous answers + the new one |
| Ignoring `nextQuestion.type` | The `type` field tells you the expected value format |
| Treating BLOCKED as a dead end | Read `blockReason.message` and re-run `evaluate_transaction` with adjusted parameters |
| Calling `confirm_mandate` without `paymentMethod` | It's required — no implicit default. Returns `MISSING_PAYMENT_METHOD` |
| Calling `confirm_mandate` without `buyer` | The buyer object (email + phone + name) is required for the business to deliver the service |
| Reading `available_payment_methods` to detect SPT | Read `paymentMethods.stripeSpt` instead — `STRIPE_SPT` doesn't appear in the array |
| Stopping at PAYMENT_REQUIRED for X402 | After broadcasting on-chain, you MUST call `finalize_x402_payment` to settle |
| Calling `finalize_x402_payment` before the tx is mined | Returns `CHAIN_VERIFICATION_FAILED` ("receipt not found") — wait then retry |
| Switching wallets between X402 confirm retries | Returns `PAYER_ADDRESS_MISMATCH` — keep the original `payerAddress` or re-evaluate |
| Mandate expired before confirming | Mandates expire in 5 minutes — confirm promptly after APPROVED |
| Confirming without user consent | Never call `confirm_mandate` without the user explicitly approving the amount |
| Switching orgs mid-session | Session locks to first org queried — switch requires a new connection |
| Starting a second mandate while one is open | Returns `FORBIDDEN` — finish, fail, or cancel the first |
| Burning the 3-attempt mandate cap | Each `confirm_mandate` call counts; after 3 failures the mandate is dead — re-issue via `evaluate_transaction` |
| Ignoring `retryAfterSeconds` | Rate limit errors include retry timing — honor it, don't hammer |
| Skipping availability check | Not all services are time-slotted, but for those that are, call `check_availability` (or pass `requestedDate` to `evaluate_transaction`) before scoping |
| "MCP not installed" / host can't find Libramen | Libramen is a remote SSE MCP server, not a local package. Register `https://api.libramen.ai/api/v1/mcp/sse` as a remote SSE server in your host's MCP config — there's nothing to npm-install |

---

## Example: Consulting Engagement

Here's the same flow for a professional services scenario:

```
1. search_businesses: { "search": "cybersecurity audit", "location": "London, UK" }
   → CyberShield Consulting (slug: "cybershield", credibility: "established")

2. Fetch the gateway URL → connect to endpoints.mcp_sse
   list_products on the cybershield session
   → "Compliance Audit" (£12,000), "Penetration Test" (£8,000), "Full Security Review Package" (£18,000)

3. evaluate_transaction: { "productId": "audit-id", "parameters": {} }
   → REQUIRES_MORE_INFO: "How many employees does your organization have?" (type: NUMBER)

4. evaluate_transaction: { "parameters": { "employee-count": 200 } }
   → REQUIRES_MORE_INFO: "Which compliance framework?" (type: SELECT, options: ["ISO 27001", "SOC 2", "PCI DSS"])

5. evaluate_transaction: { "parameters": { "employee-count": 200, "framework": "SOC 2" } }
   → REQUIRES_MORE_INFO: "What is your target completion date?" (type: DATE)

6. evaluate_transaction: { "parameters": { ..., "target-date": "2026-07-01" } }
   → APPROVED: £14,500 GBP, mandate token issued, paymentMethods.stripeSpt: true

7. Present price to user; collect buyer email/phone/name; user approves.
8. confirm_mandate: { mandateToken, paymentMethod: { type: "STRIPE_SPT", sptToken }, buyer: {...} }
   → CAPTURED + cancellationPolicy with the operator's support email
```

---

## Core Principle

**Knowing you don't know is more valuable than fabricating an engagement.**

This skill connects to businesses that have modeled their real operational requirements into a machine-navigable system. The `evaluate_transaction` pipeline doesn't guess — it evaluates against deterministic rules, real availability, and actual pricing. A "no match" or "BLOCKED" result is honest and useful. A fabricated engagement through an unqualified channel wastes the user's time and erodes trust.
