diff --git a/app/api/v1/paid/[product]/route.ts b/app/api/v1/paid/[product]/route.ts index 65076eb..4745d54 100644 --- a/app/api/v1/paid/[product]/route.ts +++ b/app/api/v1/paid/[product]/route.ts @@ -2,6 +2,587 @@ import { NextRequest, NextResponse } from 'next/server'; import { getSeedProduct, paymentRequirement } from '@/lib/seed-products'; import { verifyPyrimidPaymentTx } from '@/lib/payment-verification'; +type LeadSegment = 'mcp' | 'agent-frameworks' | 'api-tools' | 'x402'; + +type QueryParams = Record; + +type GitHubRepo = { + full_name: string; + html_url: string; + description: string | null; + topics?: string[]; + stargazers_count: number; + forks_count: number; + open_issues_count?: number; + pushed_at?: string; + updated_at?: string; + language?: string | null; + license?: { spdx_id?: string | null } | null; +}; + +type GitHubSearchResponse = { + total_count?: number; + incomplete_results?: boolean; + items?: GitHubRepo[]; +}; + +type GitHubReadmeResponse = { + download_url?: string | null; +}; + +type LeadCandidate = { + name: string; + url: string; + description: string; + topics: string[]; + stars: number; + forks: number; + language?: string | null; + updatedAt?: string; + sourceType: 'github_search' | 'curated_seed'; + sourceQuery?: string; +}; + +const LEAD_SEGMENTS: Record = { + mcp: { + githubQuery: 'mcp model context protocol server stars:>10 pushed:>2025-01-01', + keywords: ['mcp', 'model context protocol', 'tool', 'server', 'agent'], + buyerNeed: 'Agents need paid access to useful MCP tools with clear schemas and per-call pricing.', + suggestedProduct: 'Paid MCP tool bundle with search, enrich, analyze, or export calls', + priceRange: '$0.05-$0.50 per successful tool call', + }, + 'agent-frameworks': { + githubQuery: 'agent framework plugin tool stars:>25 pushed:>2025-01-01', + keywords: ['agent', 'framework', 'plugin', 'tool', 'marketplace', 'sdk'], + buyerNeed: 'Frameworks can route downstream tool purchases and earn affiliate commission for every agent.', + suggestedProduct: 'Pyrimid resolver integration and affiliate product recommendations', + priceRange: '5%-50% affiliate share on downstream paid products', + }, + 'api-tools': { + githubQuery: 'ai api data sdk stars:>20 pushed:>2025-01-01', + keywords: ['api', 'sdk', 'data', 'enrichment', 'search', 'client'], + buyerNeed: 'API vendors already have metered usage, so x402 can expose a clean agent-purchasable route.', + suggestedProduct: 'Paid API endpoint listed in the Pyrimid catalog', + priceRange: '$0.01-$1.00 per call depending on data and compute cost', + }, + x402: { + githubQuery: 'x402 agent api pushed:>2025-01-01', + keywords: ['x402', '402', 'payment', 'base', 'usdc', 'agent'], + buyerNeed: 'x402-adjacent projects need discovery, pricing, and affiliate routing beyond raw payment middleware.', + suggestedProduct: 'Catalog-listed paid resource with Pyrimid commission routing', + priceRange: '$0.05-$0.25 per call plus affiliate bps', + }, +}; + +const CURATED_LEADS: Record = { + mcp: [ + { + name: 'MCP server vendors with data-heavy tools', + url: 'https://github.com/topics/mcp-server', + description: 'Servers that expose search, retrieval, scraping, or analysis tools can charge agents per useful result.', + topics: ['mcp', 'server', 'tools', 'agent-commerce'], + stars: 0, + forks: 0, + sourceType: 'curated_seed', + }, + { + name: 'Hosted MCP marketplaces', + url: 'https://github.com/topics/model-context-protocol', + description: 'Directories and hosted MCP providers can add paid tool cards and earn distribution fees.', + topics: ['mcp', 'marketplace', 'directory', 'x402'], + stars: 0, + forks: 0, + sourceType: 'curated_seed', + }, + ], + 'agent-frameworks': [ + { + name: 'Agent frameworks with tool/plugin ecosystems', + url: 'https://github.com/topics/ai-agent', + description: 'Frameworks can embed product discovery so builders monetize agent recommendations.', + topics: ['agent', 'framework', 'plugins', 'sdk'], + stars: 0, + forks: 0, + sourceType: 'curated_seed', + }, + ], + 'api-tools': [ + { + name: 'AI API wrappers with per-call cost', + url: 'https://github.com/topics/api-client', + description: 'Wrappers around costly data, scraping, verification, or generation APIs are strong x402 candidates.', + topics: ['api', 'sdk', 'data', 'usage-based'], + stars: 0, + forks: 0, + sourceType: 'curated_seed', + }, + ], + x402: [ + { + name: 'x402 payment middleware examples', + url: 'https://github.com/search?q=x402+agent+api&type=repositories', + description: 'Projects already experimenting with HTTP 402 can add Pyrimid catalog and affiliate distribution.', + topics: ['x402', 'payment', 'base', 'usdc'], + stars: 0, + forks: 0, + sourceType: 'curated_seed', + }, + ], +}; + +const PAID_TOOL_BLUEPRINTS = [ + { + name: 'search', + keywords: ['search', 'query', 'retrieve', 'lookup', 'index'], + buyerValue: 'Return ranked, source-linked results agents can use directly.', + price: '$0.02-$0.10', + outputSchema: { type: 'object', properties: { results: { type: 'array' }, citations: { type: 'array' } } }, + affiliateBps: 2500, + }, + { + name: 'enrich', + keywords: ['enrich', 'extract', 'profile', 'metadata', 'classify'], + buyerValue: 'Convert raw records or URLs into structured data with confidence scores.', + price: '$0.05-$0.25', + outputSchema: { type: 'object', properties: { entity: { type: 'object' }, confidence: { type: 'number' } } }, + affiliateBps: 3000, + }, + { + name: 'analyze', + keywords: ['analyze', 'score', 'risk', 'audit', 'detect', 'review'], + buyerValue: 'Run a higher-value decision or risk assessment that saves agent workflow time.', + price: '$0.10-$0.75', + outputSchema: { type: 'object', properties: { score: { type: 'number' }, findings: { type: 'array' } } }, + affiliateBps: 3500, + }, + { + name: 'export', + keywords: ['export', 'report', 'csv', 'download', 'dataset'], + buyerValue: 'Package results as reusable artifacts for agent or human handoff.', + price: '$0.05-$0.50', + outputSchema: { type: 'object', properties: { artifact_url: { type: 'string' }, rows: { type: 'number' } } }, + affiliateBps: 2500, + }, + { + name: 'verify', + keywords: ['verify', 'validate', 'compliance', 'security', 'trust'], + buyerValue: 'Give agents a trust/compliance gate before acting on third-party data.', + price: '$0.10-$1.00', + outputSchema: { type: 'object', properties: { verdict: { type: 'string' }, evidence: { type: 'array' } } }, + affiliateBps: 3000, + }, +]; + +function githubHeaders(): HeadersInit { + const headers: HeadersInit = { + Accept: 'application/vnd.github+json', + 'User-Agent': 'pyrimid-seed-lead-discovery', + }; + if (process.env.GITHUB_TOKEN) { + headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; + } + return headers; +} + +async function fetchWithTimeout(url: string, init: RequestInit = {}, timeoutMs = 3500) { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), timeoutMs); + try { + return await fetch(url, { ...init, signal: controller.signal, cache: 'no-store' }); + } finally { + clearTimeout(timeout); + } +} + +function normalizeSegment(value?: string): LeadSegment { + if (value && Object.prototype.hasOwnProperty.call(LEAD_SEGMENTS, value)) { + return value as LeadSegment; + } + return 'mcp'; +} + +function clampLimit(value?: string, fallback = 5, max = 8) { + const parsed = Number.parseInt(value || '', 10); + if (Number.isNaN(parsed)) return fallback; + return Math.max(1, Math.min(parsed, max)); +} + +function keywordHits(text: string, keywords: string[]) { + const lower = text.toLowerCase(); + return keywords.filter((keyword) => lower.includes(keyword)).length; +} + +function daysSince(date?: string) { + if (!date) return Number.POSITIVE_INFINITY; + const timestamp = Date.parse(date); + if (Number.isNaN(timestamp)) return Number.POSITIVE_INFINITY; + return Math.floor((Date.now() - timestamp) / 86_400_000); +} + +function scoreLead(candidate: LeadCandidate, segment: LeadSegment) { + const text = `${candidate.name} ${candidate.description} ${candidate.topics.join(' ')}`.toLowerCase(); + const segmentScore = Math.min(30, keywordHits(text, LEAD_SEGMENTS[segment].keywords) * 8); + const monetizationNeed = Math.min(20, keywordHits(text, ['api', 'data', 'search', 'enrich', 'analysis', 'export', 'scrape', 'verify']) * 5); + const freshnessDays = daysSince(candidate.updatedAt); + const freshness = freshnessDays <= 30 ? 15 : freshnessDays <= 180 ? 10 : freshnessDays <= 365 ? 6 : 2; + const distribution = Math.min(15, keywordHits(text, ['agent', 'mcp', 'tool', 'plugin', 'sdk', 'marketplace', 'directory']) * 4); + const traction = Math.min(15, Math.round(Math.log10(candidate.stars + 1) * 8) + Math.min(5, Math.floor(candidate.forks / 20))); + const x402Fit = Math.min(5, keywordHits(text, ['x402', 'payment', 'paid', 'billing', 'usdc', 'base']) * 3); + const total = Math.max(1, Math.min(100, segmentScore + monetizationNeed + freshness + distribution + traction + x402Fit)); + + return { + total, + breakdown: { + segment_fit: segmentScore, + monetization_need: monetizationNeed, + freshness, + distribution_surface: distribution, + existing_traction: traction, + x402_readiness: x402Fit, + }, + }; +} + +function mapLead(candidate: LeadCandidate, segment: LeadSegment) { + const score = scoreLead(candidate, segment); + const config = LEAD_SEGMENTS[segment]; + + return { + target: candidate.name, + url: candidate.url, + segment, + fit_score: score.total, + score_breakdown: score.breakdown, + evidence: [ + candidate.description, + candidate.topics.length ? `topics: ${candidate.topics.slice(0, 6).join(', ')}` : 'topics unavailable', + candidate.updatedAt ? `updated: ${candidate.updatedAt}` : 'updated date unavailable', + `${candidate.stars} stars / ${candidate.forks} forks`, + ], + monetization_angle: config.buyerNeed, + suggested_product: config.suggestedProduct, + price_suggestion_usdc: config.priceRange, + integration_path: [ + 'Wrap the highest-value tool/API call with a 402 payment-required response.', + 'Return x402 accepts[] metadata with Base USDC, vendorId, productId, affiliateBps, and JSON output schema.', + 'List the paid route in the Pyrimid catalog so buyer agents can discover and purchase it.', + ], + outreach_pitch: `You already have a strong ${segment} surface. Pyrimid can turn one high-value call into an agent-purchasable product with Base USDC settlement and affiliate distribution.`, + next_action: 'Open an issue/PR proposing one paid endpoint and a Pyrimid catalog listing.', + source: { + type: candidate.sourceType, + query: candidate.sourceQuery, + language: candidate.language, + }, + }; +} + +async function fetchGitHubLeadCandidates(segment: LeadSegment, limit: number, queryOverride?: string) { + const searchQuery = queryOverride || LEAD_SEGMENTS[segment].githubQuery; + const url = new URL('https://api.github.com/search/repositories'); + url.searchParams.set('q', searchQuery); + url.searchParams.set('sort', 'updated'); + url.searchParams.set('order', 'desc'); + url.searchParams.set('per_page', String(limit)); + + try { + const res = await fetchWithTimeout(url.toString(), { headers: githubHeaders() }, 4500); + if (!res.ok) { + return { candidates: [] as LeadCandidate[], status: `github_http_${res.status}`, query: searchQuery }; + } + + const data = await res.json() as GitHubSearchResponse; + const candidates = (data.items || []).map((repo) => ({ + name: repo.full_name, + url: repo.html_url, + description: repo.description || 'No description provided.', + topics: repo.topics || [], + stars: repo.stargazers_count || 0, + forks: repo.forks_count || 0, + language: repo.language, + updatedAt: repo.pushed_at || repo.updated_at, + sourceType: 'github_search' as const, + sourceQuery: searchQuery, + })); + + return { + candidates, + status: 'ok', + query: searchQuery, + total_count: data.total_count ?? candidates.length, + incomplete_results: data.incomplete_results ?? false, + }; + } catch (error) { + return { + candidates: [] as LeadCandidate[], + status: error instanceof Error ? `github_error_${error.name}` : 'github_error', + query: searchQuery, + }; + } +} + +async function vendorLeadDiscovery(query: QueryParams) { + const segment = normalizeSegment(query.segment); + const limit = clampLimit(query.limit, 5, 8); + const discovery = await fetchGitHubLeadCandidates(segment, limit, query.q); + const candidates = discovery.candidates.length + ? discovery.candidates + : CURATED_LEADS[segment].map((candidate) => ({ ...candidate, sourceQuery: discovery.query })); + const leads = candidates + .map((candidate) => mapLead(candidate, segment)) + .sort((a, b) => b.fit_score - a.fit_score) + .slice(0, limit); + + return { + segment, + generated_at: new Date().toISOString(), + source_status: discovery.status, + github_query: discovery.query, + fallback_used: discovery.candidates.length === 0, + total_available: discovery.total_count, + incomplete_results: discovery.incomplete_results, + scoring_model: { + max_score: 100, + factors: ['segment_fit', 'monetization_need', 'freshness', 'distribution_surface', 'existing_traction', 'x402_readiness'], + }, + leads, + recommended_workflow: [ + 'Start with the top lead and identify one paid tool/API call that has direct buyer-agent value.', + 'Propose a 402 route plus catalog metadata instead of a broad platform integration.', + 'Offer an affiliateBps split so agents have a reason to distribute the vendor product.', + ], + }; +} + +function normalizeTargetUrl(value?: string) { + const raw = value || 'https://example.com/mcp'; + try { + return new URL(raw.startsWith('http://') || raw.startsWith('https://') ? raw : `https://${raw}`); + } catch { + return new URL('https://example.com/mcp'); + } +} + +function parseGitHubRepo(url: URL) { + if (url.hostname !== 'github.com') return null; + const [owner, repoWithSuffix] = url.pathname.split('/').filter(Boolean); + if (!owner || !repoWithSuffix) return null; + return { owner, repo: repoWithSuffix.replace(/\.git$/, '') }; +} + +function slug(value: string) { + return value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 48) || 'mcp-vendor'; +} + +async function fetchText(url: string, accept = 'text/plain, text/markdown, application/json;q=0.8, */*;q=0.1') { + try { + const res = await fetchWithTimeout(url, { headers: { Accept: accept, 'User-Agent': 'pyrimid-mcp-audit' } }, 3500); + if (!res.ok) return { ok: false, status: res.status, text: '' }; + return { ok: true, status: res.status, text: (await res.text()).slice(0, 12_000) }; + } catch { + return { ok: false, status: 0, text: '' }; + } +} + +async function inspectTarget(url: URL) { + const githubRepo = parseGitHubRepo(url); + const evidence: string[] = []; + const texts: string[] = []; + let repo: GitHubRepo | null = null; + + if (githubRepo) { + const repoUrl = `https://api.github.com/repos/${githubRepo.owner}/${githubRepo.repo}`; + try { + const repoRes = await fetchWithTimeout(repoUrl, { headers: githubHeaders() }, 3500); + if (repoRes.ok) { + repo = await repoRes.json() as GitHubRepo; + evidence.push(`github repo found: ${repo.full_name}`); + evidence.push(`${repo.stargazers_count || 0} stars / ${repo.forks_count || 0} forks / ${repo.open_issues_count || 0} open issues`); + } else { + evidence.push(`github repo lookup returned ${repoRes.status}`); + } + } catch { + evidence.push('github repo lookup failed'); + } + + try { + const readmeRes = await fetchWithTimeout(`${repoUrl}/readme`, { headers: githubHeaders() }, 3500); + if (readmeRes.ok) { + const readme = await readmeRes.json() as GitHubReadmeResponse; + if (readme.download_url) { + const readmeText = await fetchText(readme.download_url, 'text/plain, text/markdown'); + if (readmeText.ok && readmeText.text) { + texts.push(readmeText.text); + evidence.push('README inspected'); + } + } + } + } catch { + evidence.push('README lookup failed'); + } + } else { + const base = `${url.protocol}//${url.host}`; + const probes = [ + url.toString(), + `${base}/.well-known/mcp.json`, + `${base}/server.json`, + `${base}/llms.txt`, + `${base}/agents.txt`, + ]; + + for (const probe of probes) { + const fetched = await fetchText(probe); + if (fetched.ok && fetched.text) { + texts.push(fetched.text); + evidence.push(`inspected ${probe}`); + } else if (fetched.status) { + evidence.push(`${probe} returned ${fetched.status}`); + } + } + } + + const text = [ + repo?.full_name, + repo?.description, + (repo?.topics || []).join(' '), + ...texts, + ].filter(Boolean).join('\n').slice(0, 20_000).toLowerCase(); + + return { + target_type: githubRepo ? 'github_repository' : 'web_or_mcp_url', + repository: repo ? { + full_name: repo.full_name, + url: repo.html_url, + description: repo.description, + topics: repo.topics || [], + stars: repo.stargazers_count || 0, + forks: repo.forks_count || 0, + open_issues: repo.open_issues_count || 0, + license: repo.license?.spdx_id || null, + updated_at: repo.pushed_at || repo.updated_at, + } : undefined, + evidence, + text, + }; +} + +function recommendPaidTools(text: string, hostOrRepo: string) { + const matched = PAID_TOOL_BLUEPRINTS + .map((tool) => ({ tool, hits: keywordHits(text, tool.keywords) })) + .filter(({ hits }) => hits > 0) + .sort((a, b) => b.hits - a.hits) + .map(({ tool }) => tool); + + const selected = (matched.length ? matched : PAID_TOOL_BLUEPRINTS).slice(0, 4); + + return selected.map((tool, index) => ({ + tool_name: tool.name, + launch_priority: index + 1, + buyer_value: tool.buyerValue, + pricing: { + suggested_usdc: tool.price, + model: 'per successful call', + reason: 'Usage maps cleanly to one agent action and can be protected by a single x402 payment requirement.', + }, + x402_route: { + method: 'POST', + path: `/api/paid/${slug(hostOrRepo)}-${tool.name}`, + unpaid_status: 402, + required_headers_after_payment: ['X-PAYMENT', 'X-PAYMENT-TX'], + }, + catalog_metadata: { + vendor_id: slug(hostOrRepo), + product_id: `${slug(hostOrRepo)}-${tool.name}`, + category: 'mcp-tools', + tags: ['mcp', tool.name, 'x402', 'base-usdc', 'pyrimid'], + network: 'base', + asset: 'USDC', + affiliate_bps: tool.affiliateBps, + output_schema: tool.outputSchema, + }, + })); +} + +function auditRiskNotes(text: string, evidence: string[]) { + const notes = []; + if (!keywordHits(text, ['price', 'pricing', 'paid', 'billing', 'payment', 'x402'])) { + notes.push('No explicit pricing/payment signal found; start with an optional paid endpoint instead of gating the whole service.'); + } + if (!keywordHits(text, ['schema', 'json', 'mcp', 'tool'])) { + notes.push('Tool schema signals are weak; publish clear input/output JSON schemas before listing in the catalog.'); + } + if (!evidence.some((item) => item.includes('README inspected') || item.includes('inspected'))) { + notes.push('Source inspection was limited; verify tool names and capabilities before committing catalog metadata.'); + } + if (!keywordHits(text, ['rate limit', 'quota', 'abuse', 'cache'])) { + notes.push('Add rate limits, quota accounting, and cache policy before routing paid calls in production.'); + } + return notes; +} + +function readinessScore(text: string, evidence: string[]) { + const mcp = keywordHits(text, ['mcp', 'model context protocol', 'tool']) ? 25 : 10; + const schemas = keywordHits(text, ['schema', 'json', 'openapi', 'input', 'output']) ? 20 : 8; + const monetizable = keywordHits(text, ['search', 'enrich', 'analyze', 'export', 'verify', 'data', 'api']) ? 25 : 12; + const maintenance = evidence.some((item) => item.includes('stars') || item.includes('README inspected')) ? 15 : 6; + const payment = keywordHits(text, ['x402', 'paid', 'billing', 'payment', 'usdc']) ? 15 : 3; + return Math.min(100, mcp + schemas + monetizable + maintenance + payment); +} + +async function mcpServerAudit(query: QueryParams) { + const targetUrl = normalizeTargetUrl(query.url); + const inspection = await inspectTarget(targetUrl); + const targetName = inspection.repository?.full_name || targetUrl.hostname; + const tools = recommendPaidTools(inspection.text, targetName); + const score = readinessScore(inspection.text, inspection.evidence); + + return { + audit: { + input_url: query.url || 'https://example.com/mcp', + normalized_url: targetUrl.toString(), + generated_at: new Date().toISOString(), + target_type: inspection.target_type, + readiness_score: score, + confidence: inspection.evidence.length >= 2 ? 'medium' : 'low', + inspected: { + repository: inspection.repository, + evidence: inspection.evidence.slice(0, 12), + }, + recommended_paid_tools: tools, + pricing_strategy: { + default_model: 'per successful tool call', + starter_prices: ['$0.02 search/query', '$0.05 enrichment/export', '$0.10+ analysis/verification'], + affiliate_bps_guidance: 'Start at 2500-4000 bps for distribution agents, then lower once organic buyer demand is proven.', + }, + route_shape: { + unpaid_response: { + status: 402, + body_fields: ['error', 'message', 'accepts', 'docs', 'catalog'], + accepts_fields: ['x402Version', 'scheme', 'network', 'asset', 'maxAmountRequired', 'payTo', 'resource', 'vendorId', 'productId', 'affiliateBps'], + }, + paid_request: { + headers: ['X-PAYMENT or X-PAYMENT-TX'], + response_fields: ['product_id', 'vendor_id', 'payment_tx', 'recommended result payload', 'routed_by'], + }, + }, + integration_steps: [ + 'Pick one recommended tool and expose it as a narrow paid route instead of gating the entire MCP server.', + 'Return HTTP 402 with x402 accepts[] metadata when the payment header is missing.', + 'Verify the Base transaction or x402 payload before executing the expensive tool call.', + 'Publish catalog metadata with vendorId, productId, endpoint, price, affiliateBps, tags, and output_schema.', + 'Add the paid tool to llms.txt, agents.txt, MCP server cards, and the Pyrimid catalog for agent discovery.', + ], + risk_notes: auditRiskNotes(inspection.text, inspection.evidence), + }, + }; +} + function paymentRequired(req: NextRequest, product: NonNullable>) { const requirement = paymentRequirement(product, req.url); return NextResponse.json( @@ -24,7 +605,7 @@ function paymentRequired(req: NextRequest, product: NonNullable add paid tool -> route purchases through Pyrimid.', }, }; } @@ -52,31 +633,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.' }, - ], - }; + return { lead_discovery: await vendorLeadDiscovery(query) }; } 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', - ], - }, - }; + return mcpServerAudit(query); } case 'x402-integration-plan': { const service = query.service || 'agent-api'; @@ -122,13 +682,15 @@ export async function GET(req: NextRequest, context: { params: Promise<{ product ); } + const productPayload = await payload(product.product_id, req); + return NextResponse.json({ product_id: product.product_id, vendor_id: product.vendor_id, payment_tx: verification.txHash, payment_amount: verification.amount?.toString(), buyer: verification.buyer, - ...payload(product.product_id, req, proof), + ...productPayload, routed_by: 'pyrimid', links: { docs: 'https://pyrimid.ai/quickstart', diff --git a/lib/seed-products.ts b/lib/seed-products.ts index c47b524..6a45419 100644 --- a/lib/seed-products.ts +++ b/lib/seed-products.ts @@ -123,7 +123,37 @@ export const SEED_PRODUCTS: Omit[] = [ affiliate_bps: 4000, endpoint: `${SEED_PRODUCT_BASE}/vendor-lead-discovery?segment=mcp`, method: 'GET', - output_schema: { type: 'object', properties: { leads: { type: 'array' }, routed_by: { const: 'pyrimid' } } }, + output_schema: { + type: 'object', + properties: { + lead_discovery: { + type: 'object', + properties: { + segment: { type: 'string' }, + source_status: { type: 'string' }, + github_query: { type: 'string' }, + scoring_model: { type: 'object' }, + leads: { + type: 'array', + items: { + type: 'object', + properties: { + target: { type: 'string' }, + url: { type: 'string' }, + fit_score: { type: 'number' }, + score_breakdown: { type: 'object' }, + monetization_angle: { type: 'string' }, + suggested_product: { type: 'string' }, + integration_path: { type: 'array' }, + outreach_pitch: { type: 'string' }, + }, + }, + }, + }, + }, + routed_by: { const: 'pyrimid' }, + }, + }, monthly_volume: 0, monthly_buyers: 0, network: 'base', @@ -144,7 +174,38 @@ export const SEED_PRODUCTS: Omit[] = [ 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: { + input_url: { type: 'string' }, + normalized_url: { type: 'string' }, + readiness_score: { type: 'number' }, + inspected: { type: 'object' }, + recommended_paid_tools: { + type: 'array', + items: { + type: 'object', + properties: { + tool_name: { type: 'string' }, + buyer_value: { type: 'string' }, + pricing: { type: 'object' }, + x402_route: { type: 'object' }, + catalog_metadata: { type: 'object' }, + }, + }, + }, + pricing_strategy: { type: 'object' }, + route_shape: { type: 'object' }, + integration_steps: { type: 'array' }, + risk_notes: { type: 'array' }, + }, + }, + routed_by: { const: 'pyrimid' }, + }, + }, monthly_volume: 0, monthly_buyers: 0, network: 'base',