Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 3 additions & 8 deletions app/api/v1/paid/[product]/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextRequest, NextResponse } from 'next/server';
import { getSeedProduct, paymentRequirement } from '@/lib/seed-products';
import { verifyPyrimidPaymentTx } from '@/lib/payment-verification';
import { getVendorLeadDiscovery } from '@/lib/vendor-lead-discovery';

function paymentRequired(req: NextRequest, product: NonNullable<ReturnType<typeof getSeedProduct>>) {
const requirement = paymentRequirement(product, req.url);
Expand Down Expand Up @@ -53,14 +54,8 @@ function payload(productId: string, req: NextRequest, proof: string) {
}
case 'vendor-lead-discovery': {
const segment = query.segment || 'mcp';
return {
segment,
leads: [
{ segment: 'mcp', target: 'MCP servers with paid/data-heavy tools', pitch: 'Add optional x402 payment gate + Pyrimid catalog listing.' },
{ segment: 'agent-frameworks', target: 'Agent frameworks with marketplace/plugin systems', pitch: 'Let builders sell tools to agents with Base USDC settlement.' },
{ segment: 'api-tools', target: 'AI API services with per-call cost', pitch: 'Turn API calls into agent-purchasable products.' },
],
};
const limit = Number.parseInt(query.limit || '3', 10);
return getVendorLeadDiscovery(segment, limit);
}
case 'mcp-server-audit': {
const url = query.url || 'https://example.com/mcp';
Expand Down
313 changes: 294 additions & 19 deletions examples/mcp-paid-tool/README.md
Original file line number Diff line number Diff line change
@@ -1,33 +1,308 @@
# paid MCP tool pattern
# Paid MCP tool pattern for Pyrimid + x402

Best fit: MCP servers with expensive data, scraping, enrichment, analytics, compliance checks, search, or model calls.
This is a reproducible pattern for turning one valuable MCP/API operation into a paid tool that buyer agents can discover, preview, and purchase through Pyrimid.

## Tool design
Best fit: MCP servers with expensive data, scraping, enrichment, analytics, compliance checks, search, retrieval, or model calls where every invocation has a clear marginal cost or business value.

- Free tool: `preview_*` returns schema, price, sample output, and payment requirement.
- Paid tool: `buy_*` returns HTTP 402/x402 requirement until paid.
- Discovery: publish server card, `llms.txt`, `agents.txt`, and Pyrimid catalog entry.
## Fast mental model

## Minimal product metadata
1. Publish a **free preview tool** so agents can inspect schema, price, sample output, and payment instructions without paying.
2. Publish a **paid HTTP endpoint** behind x402. Unpaid requests return `402 Payment Required` with an `accepts[]` payment requirement.
3. Add a **Pyrimid catalog entry** so agents can find the endpoint, understand the commission split, and route purchases through Pyrimid.
4. After payment, the agent retries with `X-PAYMENT` or `X-PAYMENT-TX`; the server verifies payment and returns the paid result.

```text
MCP client / agent
├─ calls preview_vendor_search() for free
├─ receives price + schema + paid endpoint
├─ GET /api/paid/vendor-search without payment
├─ receives HTTP 402 + x402 accepts[] metadata
└─ retries with X-PAYMENT or X-PAYMENT-TX after payment
```

## Endpoint shape

Use a normal HTTPS endpoint for the paid operation and expose it through your MCP tool definition.

```http
GET https://example.com/api/paid/vendor-search?segment=mcp
```

Unpaid response:

```http
HTTP/1.1 402 Payment Required
Content-Type: application/json
X-PAYMENT-REQUIRED: {"x402Version":2,"scheme":"exact","network":"base",...}
X-Pyrimid-Vendor: vendor-search-co
X-Pyrimid-Product: vendor_search
Cache-Control: no-store
```

```json
{
"vendor_id": "your-mcp-server",
"product_id": "paid_search",
"description": "Paid MCP search result with enriched citations",
"category": "search-scraping",
"tags": ["mcp", "search", "x402", "paid-tools"],
"price_usdc": 50000,
"error": "payment_required",
"message": "Pay $0.25 USDC on Base through Pyrimid, then retry with X-PAYMENT or X-PAYMENT-TX.",
"accepts": [
{
"x402Version": 2,
"scheme": "exact",
"network": "base",
"asset": "USDC",
"maxAmountRequired": "0.25",
"payTo": "0xc949AEa380D7b7984806143ddbfE519B03ABd68B",
"resource": "https://example.com/api/paid/vendor-search?segment=mcp",
"description": "High-fit MCP vendor lead discovery with scoring and outreach angles.",
"mimeType": "application/json",
"vendorId": "vendor-search-co",
"productId": "vendor_search",
"affiliateBps": 3000,
"protocol": "pyrimid"
}
],
"docs": "https://pyrimid.ai/quickstart",
"catalog": "https://pyrimid.ai/api/v1/catalog"
}
```

Paid response after a verified retry should be regular JSON and should include enough receipt metadata for an agent audit trail:

```json
{
"product_id": "vendor_search",
"vendor_id": "vendor-search-co",
"payment_tx": "0x...",
"payment_amount": "250000",
"buyer": "0xBuyerAddress",
"result": {
"segment": "mcp",
"leads": [
{
"rank": 1,
"target": "MCP servers with paid/data-heavy tools",
"fit_score": 92,
"reason": "Already expose tool interfaces and can price high-cost calls per use."
}
]
},
"routed_by": "pyrimid"
}
```

## Minimal Pyrimid catalog metadata

Use stable IDs. Agents will key cache, receipts, and replay checks off these fields.

```json
{
"vendor_id": "vendor-search-co",
"vendor_name": "Vendor Search Co",
"vendor_erc8004": false,
"product_id": "vendor_search",
"description": "Paid MCP vendor lead discovery with scored leads, x402 fit, and first outreach angles.",
"category": "growth-data",
"tags": ["mcp", "lead-discovery", "x402", "paid-tools", "base"],
"price_usdc": 250000,
"price_display": "$0.25",
"affiliate_bps": 3000,
"endpoint": "https://your-service.com/api/paid/search",
"endpoint": "https://example.com/api/paid/vendor-search?segment=mcp",
"method": "GET",
"output_schema": {
"type": "object",
"properties": {
"result": {
"type": "object",
"properties": {
"segment": { "type": "string" },
"leads": { "type": "array" }
}
},
"routed_by": { "const": "pyrimid" }
}
},
"network": "base",
"asset": "USDC"
"asset": "USDC",
"source": "pyrimid-seed",
"sdk_integrated": true
}
```

`price_usdc` is in USDC atomic units with 6 decimals (`250000` = `$0.25`). `affiliate_bps` is basis points (`3000` = 30%).

## MCP tool design

Expose a free preview tool and a paid purchase tool. The preview should not need wallet access.

```json
{
"name": "preview_vendor_search",
"description": "Preview the paid vendor lead discovery tool, output schema, price, and x402 requirement.",
"inputSchema": {
"type": "object",
"properties": {
"segment": {
"type": "string",
"enum": ["mcp", "agent-frameworks", "api-tools"]
}
}
}
}
```

```json
{
"name": "buy_vendor_search",
"description": "Buy the vendor lead discovery result through x402/Pyrimid and return the verified JSON payload.",
"inputSchema": {
"type": "object",
"required": ["segment"],
"properties": {
"segment": { "type": "string" },
"max_price_usdc": { "type": "string", "default": "0.25" }
}
}
}
```

## Curl reproduction

Preview the live Pyrimid seed endpoint without paying:

```bash
curl -i "https://pyrimid.ai/api/v1/paid/vendor-lead-discovery?segment=mcp"
```

Expected result: `HTTP/2 402` or `HTTP/1.1 402` with a JSON body containing `accepts[0]` and headers similar to `X-Pyrimid-Vendor` and `X-Pyrimid-Product`.

Inspect catalog discovery:

```bash
curl -s "https://pyrimid.ai/api/v1/catalog?query=vendor-lead-discovery&limit=5" | jq '.products[0]'
```

After a real payment is made by the caller's wallet/payment facilitator, retry with the proof. Do not fake this in production; verify the transaction server-side.

```bash
curl -s \
-H "X-PAYMENT-TX: 0xPAID_TRANSACTION_HASH" \
"https://example.com/api/paid/vendor-search?segment=mcp"
```

## TypeScript snippets

### 1. Return a 402 requirement from an unpaid endpoint

```ts
import { NextRequest, NextResponse } from 'next/server';

const PRICE_USDC = '0.25';
const PYRIMID_ROUTER = '0xc949AEa380D7b7984806143ddbfE519B03ABd68B';

export function paymentRequired(req: NextRequest) {
const requirement = {
x402Version: 2,
scheme: 'exact',
network: 'base',
asset: 'USDC',
maxAmountRequired: PRICE_USDC,
payTo: PYRIMID_ROUTER,
resource: req.url,
description: 'High-fit MCP vendor lead discovery with scoring and outreach angles.',
mimeType: 'application/json',
vendorId: 'vendor-search-co',
productId: 'vendor_search',
affiliateBps: 3000,
protocol: 'pyrimid',
};

return NextResponse.json(
{
error: 'payment_required',
message: `Pay $${PRICE_USDC} USDC on Base through Pyrimid, then retry with X-PAYMENT or X-PAYMENT-TX.`,
accepts: [requirement],
docs: 'https://pyrimid.ai/quickstart',
catalog: 'https://pyrimid.ai/api/v1/catalog',
},
{
status: 402,
headers: {
'X-PAYMENT-REQUIRED': JSON.stringify(requirement),
'X-Pyrimid-Vendor': 'vendor-search-co',
'X-Pyrimid-Product': 'vendor_search',
'Cache-Control': 'no-store',
},
}
);
}
```

### 2. Buyer-agent safe fetch flow

This keeps wallet/payment handling outside the agent until the user or policy layer approves the spend.

```ts
type PaymentRequirement = {
x402Version: number;
scheme: string;
network: string;
asset: string;
maxAmountRequired: string;
payTo: string;
resource: string;
vendorId: string;
productId: string;
affiliateBps?: number;
};

export async function previewPaidTool(url: string) {
const res = await fetch(url, { cache: 'no-store' });
const body = await res.json();

if (res.status !== 402) return { paid: true, body };

const requirement = body.accepts?.[0] as PaymentRequirement | undefined;
return {
paid: false,
requirement,
approvalPrompt: requirement
? `Approve ${requirement.maxAmountRequired} ${requirement.asset} on ${requirement.network} for ${requirement.productId}?`
: 'Payment required, but no x402 accepts[] metadata was returned.',
};
}

export async function fetchAfterApprovedPayment(url: string, paymentTx: string) {
const res = await fetch(url, {
headers: { 'X-PAYMENT-TX': paymentTx },
cache: 'no-store',
});

if (!res.ok) {
throw new Error(`Paid tool failed: ${res.status} ${await res.text()}`);
}

return res.json();
}
```

## Agent reproduction checklist

Use this before submitting a new paid MCP tool to Pyrimid.

- [ ] Free preview path exists and returns schema, price, sample output, and paid endpoint.
- [ ] Unpaid paid-endpoint call returns HTTP 402, not 200.
- [ ] 402 body includes `accepts[0].x402Version`, `scheme`, `network`, `asset`, `maxAmountRequired`, `payTo`, `resource`, `vendorId`, `productId`, and `protocol`.
- [ ] 402 headers include `X-Pyrimid-Vendor`, `X-Pyrimid-Product`, and `Cache-Control: no-store`.
- [ ] Catalog metadata uses stable `vendor_id` and `product_id` values.
- [ ] `price_usdc` uses atomic USDC units; `price_display` is human readable.
- [ ] `affiliate_bps` is intentionally chosen and documented.
- [ ] Paid retry verifies `X-PAYMENT` or `X-PAYMENT-TX` server-side before returning data.
- [ ] Paid response includes `product_id`, `vendor_id`, `payment_tx`, `buyer` if available, `routed_by: "pyrimid"`, and useful result JSON.
- [ ] MCP server card / `llms.txt` / `agents.txt` points agents at the preview and catalog entry.
- [ ] Buyer agents gate wallet spend behind explicit user or policy approval.

## Why route through Pyrimid?

- Agents can find your tool in one catalog.
- Buyer agents get a standard x402 payment flow.
- Affiliates can route demand to your tool.
- Vendor, affiliate, and protocol fees are visible onchain.
- Agents can find your tool in one catalog instead of scraping separate docs.
- Buyer agents get a standard x402 payment flow and reproducible 402 metadata.
- Affiliates can route demand to your tool without custom rev-share plumbing.
- Vendor, affiliate, and protocol fees are visible through Pyrimid/onchain receipts.
Loading