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
136 changes: 130 additions & 6 deletions app/api/v1/paid/[product]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,93 @@ function paymentRequired(req: NextRequest, product: NonNullable<ReturnType<typeo
);
}

function auditTarget(url: string) {
let parsed: URL | null = null;
try {
parsed = new URL(url);
} catch {
parsed = null;
}

const host = parsed?.hostname || 'unknown-host';
const path = parsed?.pathname || '';
const lower = `${host}${path}`.toLowerCase();
const hasLikelyMcpPath = /(^|[-/.])mcp($|[-/.])|tools|jsonrpc|rpc/.test(lower);
const likelySurface = hasLikelyMcpPath ? 'mcp-server' : 'agent-api';

return {
host,
path: path || '/',
likely_surface: likelySurface,
hasLikelyMcpPath,
monetization_fit: hasLikelyMcpPath ? 'high' : 'medium',
};
}

function normalizeVendorId(url: string) {
try {
return new URL(url).hostname.replace(/^www\./, '').replace(/[^a-z0-9]+/gi, '-').replace(/^-|-$/g, '').toLowerCase();
} catch {
return 'mcp-vendor';
}
}

function suggestedProductId(url: string) {
const vendor = normalizeVendorId(url);
const path = (() => {
try {
return new URL(url).pathname;
} catch {
return '';
}
})()
.replace(/[^a-z0-9]+/gi, '-')
.replace(/^-|-$/g, '')
.toLowerCase();

return `${vendor}-${path || 'paid-tool'}`.slice(0, 80);
}

function recommendedMcpTools(target: ReturnType<typeof auditTarget>) {
const baseTools = [
{
name: 'paid_search',
price_usdc: '0.05',
why: 'Search and ranking are easy for buyer agents to value per call.',
},
{
name: 'paid_enrich',
price_usdc: '0.10',
why: 'Enrichment often calls paid upstream APIs and maps cleanly to usage-based pricing.',
},
{
name: 'paid_export',
price_usdc: '0.25',
why: 'Exports produce durable artifacts and justify higher per-call pricing.',
},
];

if (target.likely_surface === 'mcp-server') {
return [
...baseTools,
{
name: 'paid_tool_audit',
price_usdc: '0.10',
why: 'Agents can pay for a machine-readable monetization report on the server itself.',
},
];
}

return [
...baseTools,
{
name: 'paid_api_plan',
price_usdc: '0.10',
why: 'API vendors can sell integration plans before exposing a full MCP server.',
},
];
}

function payload(productId: string, req: NextRequest, proof: string) {
const query = Object.fromEntries(req.nextUrl.searchParams.entries());

Expand Down Expand Up @@ -64,17 +151,54 @@ function payload(productId: string, req: NextRequest, proof: string) {
}
case 'mcp-server-audit': {
const url = query.url || 'https://example.com/mcp';
const target = auditTarget(url);
return {
audit: {
url,
recommended_paid_tools: ['search', 'enrich', 'export', 'analyze'],
pricing: '$0.01-$0.25 per call depending on compute/data cost',
target,
recommended_paid_tools: recommendedMcpTools(target),
pricing_tiers: [
{ tier: 'utility', price_usdc: '0.01-0.05', best_for: 'cheap lookups, validation, metadata expansion' },
{ tier: 'standard', price_usdc: '0.05-0.25', best_for: 'API calls with paid upstream data or moderate compute' },
{ tier: 'premium', price_usdc: '0.25-2.00', best_for: 'slow research, large exports, proprietary datasets, or human-in-the-loop work' },
],
x402_route_shape: {
unauthenticated_response: {
status: 402,
required_headers: ['X-PAYMENT or X-PAYMENT-TX on retry'],
body_fields: ['error', 'message', 'accepts[]', 'docs', 'catalog'],
},
accepts_example: {
scheme: 'exact',
network: 'base',
asset: 'USDC',
payTo: 'Pyrimid Router contract',
maxAmountRequired: 'price in USDC',
resource: url,
},
},
catalog_metadata: {
vendorId: normalizeVendorId(url),
productId: suggestedProductId(url),
endpoint: url,
method: 'POST or GET, matching the paid MCP/API surface',
affiliateBps: '1000-5000',
output_schema: 'Describe the successful paid response, not the 402 error response',
},
integration_steps: [
'Add 402 response with x402 accepts[] metadata',
'Register vendor/product in Pyrimid catalog',
'Expose tool schema in MCP server card',
'Add affiliateBps for distribution agents',
'Choose one high-value MCP tool to monetize before gating the whole server',
'Return a 402 response with x402 accepts[] metadata when payment proof is missing',
'Verify payment on Base before executing the paid tool',
'Publish agents.txt, llms.txt, and a server card that names the paid tools',
'Register vendor/product metadata in the Pyrimid catalog with affiliateBps for distribution agents',
],
risk_notes: [
'Do not accept payment proof without checking amount, asset, network, recipient, and freshness',
'Keep free discovery metadata available so buyer agents can understand the paid tool before purchase',
'Use per-tool pricing instead of one server-wide price when compute or data costs vary',
'Avoid returning secrets, private API keys, or non-redacted customer data in paid tool output',
],
readiness_score: target.hasLikelyMcpPath ? 82 : 68,
},
};
}
Expand Down
19 changes: 18 additions & 1 deletion lib/seed-products.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,24 @@ export const SEED_PRODUCTS: Omit<SeedProduct, 'indexed_at'>[] = [
affiliate_bps: 4000,
endpoint: `${SEED_PRODUCT_BASE}/mcp-server-audit?url=https://example.com/mcp`,
method: 'GET',
output_schema: { type: 'object', properties: { audit: { type: 'object' }, routed_by: { const: 'pyrimid' } } },
output_schema: {
type: 'object',
properties: {
audit: {
type: 'object',
properties: {
target: { type: 'object' },
recommended_paid_tools: { type: 'array' },
pricing_tiers: { type: 'array' },
x402_route_shape: { type: 'object' },
catalog_metadata: { type: 'object' },
risk_notes: { type: 'array' },
readiness_score: { type: 'number' },
},
},
routed_by: { const: 'pyrimid' },
},
},
monthly_volume: 0,
monthly_buyers: 0,
network: 'base',
Expand Down