diff --git a/app/api/v1/paid/[product]/route.ts b/app/api/v1/paid/[product]/route.ts index 65076eb..07fa775 100644 --- a/app/api/v1/paid/[product]/route.ts +++ b/app/api/v1/paid/[product]/route.ts @@ -24,7 +24,266 @@ function paymentRequired(req: NextRequest, product: NonNullable = { + mcp: { + query: 'mcp server tools marketplace ai api language:TypeScript OR language:Python', + product: 'Paid MCP tool endpoint', + pitch: 'Add optional x402 gating to one high-value MCP tool and list the product in the Pyrimid catalog.', + seeds: [ + { + segment: 'mcp', + target: 'Open-source MCP servers with data-heavy tools', + url: 'https://github.com/search?q=mcp+server+tools+api&type=repositories', + score: 74, + fit_reasons: ['MCP-native surface', 'tool calls can map directly to per-call pricing'], + suggested_product: 'Premium enrich/search/export tool', + pitch: 'Gate the most compute- or data-intensive tool at $0.05-$0.25 per call with x402 metadata.', + }, + ], + }, + 'agent-frameworks': { + query: 'agent framework plugin marketplace tools TypeScript Python', + product: 'Default Pyrimid resolver integration', + pitch: 'Let framework users discover paid tools and earn affiliate revenue on routed purchases.', + seeds: [ + { + segment: 'agent-frameworks', + target: 'Agent frameworks with plugin or tool registries', + url: 'https://github.com/search?q=agent+framework+plugin+marketplace&type=repositories', + score: 70, + fit_reasons: ['Existing plugin distribution model', 'affiliate commission aligns with framework ecosystem growth'], + suggested_product: 'Embedded paid-tool resolver', + pitch: 'Ship PyrimidResolver as an optional monetization layer for tool recommendations.', + }, + ], + }, + 'api-tools': { + query: 'AI API SaaS per call pricing OpenAPI', + product: 'Paid API endpoint listed in Pyrimid catalog', + pitch: 'Turn a valuable API route into an agent-purchasable x402 product with Base USDC settlement.', + seeds: [ + { + segment: 'api-tools', + target: 'AI API wrappers with measurable per-call value', + url: 'https://github.com/search?q=AI+API+OpenAPI+pricing&type=repositories', + score: 68, + fit_reasons: ['API route already exists', 'per-call cost can be priced and verified'], + suggested_product: 'Single paid JSON API call', + pitch: 'Expose one endpoint through Pyrimid middleware and publish catalog metadata.', + }, + ], + }, + x402: { + query: 'x402 payment API USDC Base agent', + product: 'Pyrimid catalog + affiliate split for existing x402 product', + pitch: 'List an existing x402 product in Pyrimid so agents can discover it and route affiliate purchases.', + seeds: [ + { + segment: 'x402', + target: 'Existing x402 APIs without affiliate routing', + url: 'https://github.com/search?q=x402+USDC+API&type=repositories', + score: 76, + fit_reasons: ['Already uses x402', 'Pyrimid can add catalog discovery and affiliate splits'], + suggested_product: 'Catalog listing for existing paid route', + pitch: 'Keep the current 402 flow, add Pyrimid product metadata and commission routing.', + }, + ], + }, +}; + +function clampLimit(value: string | undefined, fallback = 5) { + const parsed = Number.parseInt(value || '', 10); + if (!Number.isFinite(parsed)) return fallback; + return Math.min(10, Math.max(1, parsed)); +} + +function recencyScore(pushedAt: string | null | undefined) { + if (!pushedAt) return 0; + const ageDays = (Date.now() - Date.parse(pushedAt)) / 86_400_000; + if (ageDays < 30) return 18; + if (ageDays < 180) return 12; + if (ageDays < 365) return 6; + return 0; +} + +async function githubRepositoryLeads(segment: string, query: string, limit: number): Promise<{ leads: VendorLead[]; error?: string }> { + const url = new URL('https://api.github.com/search/repositories'); + url.searchParams.set('q', query); + url.searchParams.set('sort', 'updated'); + url.searchParams.set('order', 'desc'); + url.searchParams.set('per_page', String(limit)); + + try { + const response = await fetch(url, { + headers: { + Accept: 'application/vnd.github+json', + 'User-Agent': 'pyrimid-vendor-lead-discovery', + }, + next: { revalidate: 900 }, + }); + if (!response.ok) return { leads: [], error: `github_search_${response.status}` }; + const data = await response.json(); + const items = Array.isArray(data.items) ? data.items : []; + return { + leads: items.slice(0, limit).map((repo: any): VendorLead => { + const stars = Number(repo.stargazers_count || 0); + const topics = Array.isArray(repo.topics) ? repo.topics : []; + const score = Math.min( + 95, + 45 + Math.min(20, Math.log10(stars + 1) * 8) + recencyScore(repo.pushed_at) + Math.min(12, topics.length * 2) + ); + return { + segment, + target: repo.full_name || repo.name || 'unknown-repo', + url: repo.html_url, + score: Math.round(score), + fit_reasons: [ + `${stars} GitHub stars`, + repo.pushed_at ? `updated ${repo.pushed_at.slice(0, 10)}` : 'unknown update date', + topics.length ? `topics: ${topics.slice(0, 4).join(', ')}` : 'topic metadata unavailable', + ], + suggested_product: LEAD_SEGMENTS[segment]?.product || 'Paid API/MCP endpoint', + pitch: LEAD_SEGMENTS[segment]?.pitch || 'Add x402 payment metadata and Pyrimid catalog listing.', + }; + }), + }; + } catch (error) { + return { leads: [], error: error instanceof Error ? error.message : 'github_search_failed' }; + } +} + +async function vendorLeadDiscovery(query: Record) { + const segment = (query.segment || 'mcp').toLowerCase(); + const config = LEAD_SEGMENTS[segment] || LEAD_SEGMENTS.mcp; + const limit = clampLimit(query.limit); + const searchQuery = query.q || config.query; + const live = await githubRepositoryLeads(segment, searchQuery, limit); + const leads = [...live.leads, ...config.seeds] + .sort((a, b) => b.score - a.score) + .slice(0, limit); + + return { + segment, + search_query: searchQuery, + generated_at: new Date().toISOString(), + leads, + lead_schema: { + target: 'repo, vendor, or ecosystem surface to contact', + score: '0-100 fit score from recency, public traction, and metadata richness', + fit_reasons: 'evidence an agent can verify before outreach', + suggested_product: 'first paid product shape to list in Pyrimid', + }, + outreach_checklist: [ + 'Confirm the project exposes an MCP server, API route, or tool marketplace', + 'Pick one high-value endpoint/tool with clear per-call value', + 'Return 402 with x402 accepts[] metadata before payment', + 'Publish Pyrimid catalog metadata: vendorId, productId, endpoint, price, affiliateBps, output_schema', + 'Run one non-self buyer transaction and keep the Base tx hash as proof', + ], + fallback_used: live.leads.length === 0, + source_errors: live.error ? [live.error] : [], + }; +} + +async function fetchTextSample(url: string) { + try { + const response = await fetch(url, { + headers: { Accept: 'application/json, text/plain, text/html;q=0.7, */*;q=0.2' }, + next: { revalidate: 600 }, + }); + const text = await response.text(); + return { + ok: response.ok, + status: response.status, + content_type: response.headers.get('content-type'), + sample: text.slice(0, 8000), + }; + } catch (error) { + return { + ok: false, + status: 0, + content_type: null, + sample: '', + error: error instanceof Error ? error.message : 'fetch_failed', + }; + } +} + +function keywordEvidence(sample: string) { + const lower = sample.toLowerCase(); + return { + has_mcp: /\bmcp\b|tools\/list|json-rpc/.test(lower), + has_tool_schema: /inputschema|tool(s)?\s*[:=]|function/.test(lower), + has_pricing: /price|pricing|usd|usdc|billing|credit/.test(lower), + has_x402: /x402|402 payment|payment-required|x-payment/.test(lower), + has_auth: /api[_-]?key|bearer|authorization|oauth/.test(lower), + }; +} + +async function mcpServerAudit(query: Record) { + const url = query.url || 'https://example.com/mcp'; + const fetched = await fetchTextSample(url); + const evidence = keywordEvidence(fetched.sample || ''); + const observations = [ + evidence.has_mcp ? 'MCP/JSON-RPC signals found in fetched content.' : 'No strong MCP marker found in fetched sample; verify server endpoint manually.', + evidence.has_tool_schema ? 'Tool/schema language present; individual tools can be priced separately.' : 'Tool schema not obvious from sample; publish tools/list or server card metadata.', + evidence.has_x402 ? 'x402/payment-required markers already present.' : 'No x402 marker detected; add 402 response with accepts[] metadata.', + evidence.has_pricing ? 'Pricing/billing language detected.' : 'No public pricing detected; start with one low-friction $0.01-$0.25 tool.', + ]; + + const recommendedTools = evidence.has_tool_schema + ? ['premium_search', 'bulk_enrich', 'export_report', 'deep_analyze'] + : ['status_check', 'catalog_lookup', 'summary_report', 'data_export']; + + return { + audit: { + url, + fetched: { + ok: fetched.ok, + status: fetched.status, + content_type: fetched.content_type, + sample_chars: fetched.sample.length, + error: fetched.error, + }, + evidence, + observations, + recommended_paid_tools: recommendedTools.map((name, idx) => ({ + name, + suggested_price_usdc: [0.01, 0.05, 0.1, 0.25][idx], + why: idx < 2 ? 'low-cost discovery/conversion tool' : 'higher-value compute or export action', + })), + x402_route_shape: { + unpaid: 'Return HTTP 402 with accepts[] including network=eip155:8453, asset=Base USDC, amount, payTo, and resource URL.', + paid: 'Verify X-PAYMENT or X-PAYMENT-TX on Base, then return JSON with product_id, payment_tx, buyer, and result.', + }, + catalog_metadata: ['vendorId', 'productId', 'name', 'description', 'endpoint', 'method', 'price_usdc', 'affiliateBps', 'output_schema'], + risk_notes: [ + 'Do not put secrets in MCP tool descriptions or public catalog metadata.', + 'Avoid pricing tools that only wrap free public data unless the transformation adds clear value.', + 'Keep one free health/catalog endpoint so agents can discover the paid route before buying.', + ], + integration_steps: [ + 'Choose one high-value tool and define a stable JSON output schema.', + 'Add an unpaid 402 branch with x402 accepts[] metadata.', + 'Add payment verification for Base USDC and reject stale or wrong-amount proofs.', + 'Register the product in the Pyrimid catalog with affiliateBps for distribution agents.', + 'Run one non-self test purchase and keep the Base transaction hash.', + ], + }, + }; +} + +async function payload(productId: string, req: NextRequest, proof: string) { const query = Object.fromEntries(req.nextUrl.searchParams.entries()); switch (productId) { @@ -51,33 +310,10 @@ 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.' }, - ], - }; - } - case 'mcp-server-audit': { - const url = query.url || 'https://example.com/mcp'; - return { - audit: { - url, - recommended_paid_tools: ['search', 'enrich', 'export', 'analyze'], - pricing: '$0.01-$0.25 per call depending on compute/data cost', - 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', - ], - }, - }; - } + case 'vendor-lead-discovery': + return vendorLeadDiscovery(query); + case 'mcp-server-audit': + return mcpServerAudit(query); case 'x402-integration-plan': { const service = query.service || 'agent-api'; return { @@ -128,7 +364,7 @@ export async function GET(req: NextRequest, context: { params: Promise<{ product payment_tx: verification.txHash, payment_amount: verification.amount?.toString(), buyer: verification.buyer, - ...payload(product.product_id, req, proof), + ...(await payload(product.product_id, req, proof)), routed_by: 'pyrimid', links: { docs: 'https://pyrimid.ai/quickstart',