diff --git a/app/api/v1/paid/[product]/route.ts b/app/api/v1/paid/[product]/route.ts index 65076eb..a27b261 100644 --- a/app/api/v1/paid/[product]/route.ts +++ b/app/api/v1/paid/[product]/route.ts @@ -24,7 +24,29 @@ function paymentRequired(req: NextRequest, product: NonNullable = { + mcp: [ + { + target: 'Hosted MCP servers with metered data tools', + fit_score: 94, + reason: 'They already expose machine-readable tools, so adding a paid HTTP 402 gate does not change the agent workflow.', + discovery_queries: [ + 'topic:mcp-server pricing archived:false', + 'topic:mcp-server data enrichment archived:false', + 'topic:mcp-server paid tools archived:false', + ], + suggested_paid_tool: 'premium_search', + price_usdc: '0.05-0.25', + affiliate_bps: 2500, + outreach_hook: 'Your MCP server already has agent distribution; Pyrimid can turn the highest-cost tool into a paid endpoint with USDC settlement.', + }, + { + target: 'MCP wrappers around paid SaaS APIs', + fit_score: 89, + reason: 'The vendor has direct marginal API cost and a clear buyer value story for per-call pricing.', + discovery_queries: [ + 'topic:mcp-server api key archived:false', + 'topic:mcp-server enrichment archived:false', + 'topic:mcp-server search api archived:false', + ], + suggested_paid_tool: 'enrich_company', + price_usdc: '0.10-0.50', + affiliate_bps: 2000, + outreach_hook: 'Move API-key friction behind x402 so agents can buy one result without creating a vendor account first.', + }, + { + target: 'Open-source MCP tools with hosted demos', + fit_score: 82, + reason: 'They can keep the local tool free while selling hosted execution, queue priority, or fresh data.', + discovery_queries: [ + 'topic:mcp-server hosted archived:false', + 'topic:mcp-server deploy cloud archived:false', + 'topic:mcp-server cloud tool archived:false', + ], + suggested_paid_tool: 'hosted_run', + price_usdc: '0.02-0.15', + affiliate_bps: 1500, + outreach_hook: 'Keep open source free and monetize the hosted path agents can call reliably in production.', + }, + ], + 'agent-frameworks': [ + { + target: 'Agent frameworks with plugin or tool marketplaces', + fit_score: 91, + reason: 'A default commerce resolver lets every downstream agent recommend and buy tools without custom payment code.', + discovery_queries: [ + 'topic:ai-agents marketplace framework archived:false', + 'topic:ai-agent plugin marketplace archived:false', + 'tool registry agent framework in:name,description archived:false', + ], + suggested_paid_tool: 'catalog_recommendation', + price_usdc: '0.01-0.10', + affiliate_bps: 3000, + outreach_hook: 'Pyrimid can be the paid-tool layer your framework does not have to build.', + }, + { + target: 'Vertical agents with recurring user questions', + fit_score: 84, + reason: 'Agents that repeatedly call data or research tools can route buyers into paid calls at the moment of intent.', + discovery_queries: [ + 'topic:ai-agent research api archived:false', + 'topic:ai-agent lead generation api archived:false', + 'topic:ai-agent market data api archived:false', + ], + suggested_paid_tool: 'paid_recommendation', + price_usdc: '0.05-0.25', + affiliate_bps: 3500, + outreach_hook: 'Let the agent earn when it recommends a paid result instead of handing off to a subscription page.', + }, + ], + 'api-tools': [ + { + target: 'AI data APIs with free demos and paid plans', + fit_score: 93, + reason: 'They already sell per-seat or credit-based access; x402 adds one-off agent purchases.', + discovery_queries: [ + 'ai api pricing free tier in:name,description archived:false', + 'data api credits pricing in:name,description archived:false', + 'enrichment api pricing in:name,description archived:false', + ], + suggested_paid_tool: 'single_lookup', + price_usdc: '0.05-1.00', + affiliate_bps: 2000, + outreach_hook: 'Add a no-login paid endpoint for agents that only need one result.', + }, + { + target: 'Scraping and enrichment services', + fit_score: 87, + reason: 'Their compute and proxy costs map naturally to per-call USDC pricing and clear output schemas.', + discovery_queries: [ + 'web scraping api pricing in:name,description archived:false', + 'lead enrichment api pricing in:name,description archived:false', + 'serp api pricing in:name,description archived:false', + ], + suggested_paid_tool: 'fresh_record', + price_usdc: '0.02-0.40', + affiliate_bps: 1500, + outreach_hook: 'Agent buyers want one clean JSON result, not a dashboard subscription.', + }, + ], + }; + const leadProfiles = leadSets[segment] || leadSets.mcp; + const discovery = await discoverVendorLeads(leadProfiles, query); + const leads = discovery.leads.length > 0 ? discovery.leads : leadProfiles.map((lead) => ({ + ...lead, + source: 'segment_profile', + evidence: ['static segment profile'], + catalog_metadata_hint: catalogMetadataForLead(lead), + })); 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.' }, - ], + lead_count: leads.length, + discovery: { + source: 'github_repository_search', + searched_queries: discovery.searched_queries, + errors: discovery.errors, + fallback_used: discovery.leads.length === 0, + generated_at: new Date().toISOString(), + }, + scoring_model: { + high_fit: 'Existing GitHub/API/MCP surface + per-call value + machine-readable output + low account friction.', + reject_if: ['requires private user data', 'no API or tool endpoint', 'unclear marginal value', 'no public pricing signal'], + }, + leads, + strategy_templates: leadProfiles, + next_actions: leads.map((lead) => { + const leadUrl = 'url' in lead ? lead.url : ''; + const discoveryQuery = 'discovery_query' in lead ? lead.discovery_query : lead.discovery_queries[0]; + return { + target: lead.target, + action: leadUrl ? `Review ${leadUrl} and contact the maintainer with the outreach hook.` : `Run discovery query: ${discoveryQuery}`, + submit_to_catalog: lead.catalog_metadata_hint, + }; + }), }; } case 'mcp-server-audit': { const url = query.url || 'https://example.com/mcp'; + const normalizedUrl = normalizeUrl(url); + const host = safeHost(normalizedUrl); + const serverKind = inferMcpServerKind(normalizedUrl); + const targetInspection = await inspectMcpTarget(normalizedUrl); return { audit: { url, - recommended_paid_tools: ['search', 'enrich', 'export', 'analyze'], - pricing: '$0.01-$0.25 per call depending on compute/data cost', + normalized_url: normalizedUrl, + server_kind: serverKind, + inspection: targetInspection, + recommended_paid_tools: [ + { + name: 'premium_search', + route: '/api/paid/search', + price_usdc: '0.03-0.10', + value: 'Fresh indexed search or higher result limits.', + output_schema: { + type: 'object', + properties: { + results: { type: 'array', items: { type: 'object' } }, + citations: { type: 'array', items: { type: 'string' } }, + freshness: { type: 'string' }, + }, + required: ['results', 'citations', 'freshness'], + }, + }, + { + name: 'enrich', + route: '/api/paid/enrich', + price_usdc: '0.10-0.50', + value: 'Expensive third-party API calls, enrichment, or entity matching.', + output_schema: { + type: 'object', + properties: { + entity: { type: 'object' }, + confidence: { type: 'number' }, + sources: { type: 'array', items: { type: 'string' } }, + }, + required: ['entity', 'confidence', 'sources'], + }, + }, + { + name: 'export', + route: '/api/paid/export', + price_usdc: '0.05-0.25', + value: 'Structured file generation, bulk export, or normalized JSON download.', + output_schema: { + type: 'object', + properties: { + download_url: { type: 'string' }, + row_count: { type: 'number' }, + expires_at: { type: 'string' }, + }, + required: ['download_url', 'row_count', 'expires_at'], + }, + }, + { + name: 'analyze', + route: '/api/paid/analyze', + price_usdc: '0.15-1.00', + value: 'LLM, crawling, browser, or compute-heavy analysis that should not be free.', + output_schema: { + type: 'object', + properties: { + summary: { type: 'string' }, + recommendations: { type: 'array', items: { type: 'string' } }, + risk_notes: { type: 'array', items: { type: 'string' } }, + }, + required: ['summary', 'recommendations', 'risk_notes'], + }, + }, + ], + pricing: { + default_range: '$0.01-$0.25 per call', + raise_price_when: ['external API cost is non-trivial', 'browser automation is required', 'fresh data must be fetched', 'LLM tokens are used'], + keep_free_when: ['static metadata', 'health checks', 'tool discovery', 'documentation'], + }, + route_shape: { + unpaid_response: { + status: 402, + body: { + error: 'payment_required', + accepts: [ + { + scheme: 'exact', + network: 'base', + asset: 'USDC', + maxAmountRequired: '0.10', + resource: `https://${host}/api/paid/analyze`, + mimeType: 'application/json', + }, + ], + }, + }, + paid_retry_headers: ['X-PAYMENT', 'X-PAYMENT-TX'], + }, + catalog_metadata: { + vendor_id_hint: host.replace(/[^a-z0-9]+/gi, '-').toLowerCase(), + product_id_hint: `${serverKind}-paid-tool`, + categories: ['mcp-tools', 'agent-commerce', 'developer-tools'], + tags: ['mcp', 'x402', 'base-usdc', serverKind, ...targetInspection.detected_features.slice(0, 4)], + affiliate_bps_recommendation: 1500, + }, + risk_notes: [ + 'Do not put authentication-only tools behind x402 if they still require a separate vendor account.', + 'Avoid charging for static MCP metadata; charge for fresh data, compute, or premium limits.', + 'Return deterministic JSON schemas so buyer agents can evaluate the purchase automatically.', + ], integration_steps: [ 'Add 402 response with x402 accepts[] metadata', 'Register vendor/product in Pyrimid catalog', @@ -95,6 +352,351 @@ function payload(productId: string, req: NextRequest, proof: string) { } } +function githubHeaders() { + const headers: Record = { + Accept: 'application/vnd.github+json', + 'User-Agent': 'pyrimid-seed-paid-endpoint', + }; + if (process.env.GITHUB_TOKEN) { + headers.Authorization = `Bearer ${process.env.GITHUB_TOKEN}`; + } + return headers; +} + +function fetchWithTimeout(url: string, init: RequestInit = {}, timeoutMs = 3000) { + return fetch(url, { ...init, signal: init.signal || AbortSignal.timeout(timeoutMs) }); +} + +async function discoverVendorLeads(leadProfiles: LeadProfile[], query: Record) { + const requestedLimit = Number.parseInt(query.limit || '6', 10); + const limit = Number.isFinite(requestedLimit) ? Math.min(Math.max(requestedLimit, 1), 10) : 6; + const customQuery = query.q?.trim(); + const queryPlan = customQuery + ? [{ search: customQuery, profile: leadProfiles[0] }] + : leadProfiles.flatMap((profile) => profile.discovery_queries.slice(0, 2).map((search) => ({ search, profile }))).slice(0, 5); + + const leads = new Map>(); + const errors: Array<{ query: string; status?: number; message: string }> = []; + + for (const { search, profile } of queryPlan) { + try { + const searchQuery = search.includes('archived:') ? search : `${search} archived:false`; + const url = `https://api.github.com/search/repositories?q=${encodeURIComponent(searchQuery)}&sort=updated&order=desc&per_page=5`; + const response = await fetchWithTimeout(url, { headers: githubHeaders(), cache: 'no-store' }); + if (!response.ok) { + errors.push({ query: search, status: response.status, message: `GitHub search failed with HTTP ${response.status}` }); + continue; + } + const data = await response.json() as { items?: GitHubRepositoryItem[] }; + for (const item of data.items || []) { + const candidate = candidateFromRepository(item, profile, search); + if (!candidate.repository) continue; + const existing = leads.get(candidate.repository); + if (!existing || candidate.fit_score > existing.fit_score) { + leads.set(candidate.repository, candidate); + } + } + } catch (err) { + errors.push({ query: search, message: err instanceof Error ? err.message : 'Unknown GitHub search error' }); + } + } + + return { + searched_queries: queryPlan.map((item) => item.search), + errors, + leads: Array.from(leads.values()).sort((a, b) => b.fit_score - a.fit_score).slice(0, limit), + }; +} + +function candidateFromRepository(item: GitHubRepositoryItem, profile: LeadProfile, discoveryQuery: string) { + const repository = item.full_name || ''; + const description = item.description || ''; + const topics = item.topics || []; + const evidenceText = `${repository} ${description} ${topics.join(' ')}`.toLowerCase(); + const evidence = [ + repository ? `repository:${repository}` : '', + item.stargazers_count !== undefined ? `stars:${item.stargazers_count}` : '', + item.language ? `language:${item.language}` : '', + topics.length ? `topics:${topics.join(',')}` : '', + ].filter(Boolean); + + return { + source: 'github_search', + target: repository, + repository, + url: item.html_url || '', + description, + stars: item.stargazers_count || 0, + language: item.language || 'unknown', + topics, + updated_at: item.updated_at, + fit_score: scoreRepositoryLead(profile.fit_score, evidenceText, item.stargazers_count || 0), + reason: profile.reason, + discovery_query: discoveryQuery, + suggested_paid_tool: profile.suggested_paid_tool, + price_usdc: profile.price_usdc, + affiliate_bps: profile.affiliate_bps, + outreach_hook: profile.outreach_hook, + catalog_metadata_hint: catalogMetadataForLead(profile, repository || description), + evidence, + }; +} + +function scoreRepositoryLead(baseScore: number, evidenceText: string, stars: number) { + let score = baseScore; + const coreSignals = [ + evidenceText.includes('mcp'), + evidenceText.includes('x402') || evidenceText.includes('payment'), + evidenceText.includes('api') || evidenceText.includes('tool'), + evidenceText.includes('pricing') || evidenceText.includes('paid'), + evidenceText.includes('agent'), + ].filter(Boolean).length; + if (evidenceText.includes('mcp')) score += 5; + if (evidenceText.includes('x402') || evidenceText.includes('payment')) score += 4; + if (evidenceText.includes('api') || evidenceText.includes('tool')) score += 3; + if (evidenceText.includes('pricing') || evidenceText.includes('paid')) score += 3; + if (coreSignals === 0) score -= 20; + if (coreSignals === 1 && stars < 5) score -= 10; + if (stars > 500) score += 4; + else if (stars > 100) score += 2; + else if (stars < 5) score -= 6; + return Math.min(Math.max(score, 40), 98); +} + +function catalogMetadataForLead(lead: LeadProfile, seed?: string) { + const vendorSeed = seed || lead.target; + return { + vendor_id_hint: vendorSeed.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '').slice(0, 60), + product_id_hint: lead.suggested_paid_tool, + affiliate_bps: lead.affiliate_bps, + price_usdc: lead.price_usdc, + }; +} + +async function inspectMcpTarget(normalizedUrl: string) { + const githubRepo = parseGitHubRepo(normalizedUrl); + if (githubRepo) { + return inspectGitHubRepo(githubRepo.owner, githubRepo.repo); + } + + const detected = new Set(); + const missing = new Set(['mcp_manifest', 'llms_txt', 'agents_txt', 'x402_payment_metadata']); + const evidenceUrls: string[] = []; + const errors: string[] = []; + const base = new URL(normalizedUrl); + const unsafeProbe = unsafeProbeReason(base); + if (unsafeProbe) { + return { + inspected_as: 'url', + detected_features: Array.from(detected), + missing_features: Array.from(missing), + evidence_urls: evidenceUrls, + errors: [unsafeProbe], + }; + } + + const probes = [ + { feature: 'mcp_manifest', url: new URL('/.well-known/mcp.json', base).toString() }, + { feature: 'llms_txt', url: new URL('/llms.txt', base).toString() }, + { feature: 'agents_txt', url: new URL('/agents.txt', base).toString() }, + ]; + + for (const probe of probes) { + try { + const response = await fetchWithTimeout(probe.url, { method: 'GET', cache: 'no-store' }, 2000); + if (response.ok) { + detected.add(probe.feature); + missing.delete(probe.feature); + evidenceUrls.push(probe.url); + } + } catch (err) { + errors.push(`${probe.feature}: ${err instanceof Error ? err.message : 'probe failed'}`); + } + } + + return { + inspected_as: 'url', + detected_features: Array.from(detected), + missing_features: Array.from(missing), + evidence_urls: evidenceUrls, + errors, + }; +} + +function parseGitHubRepo(input: string) { + try { + const url = new URL(input); + if (url.hostname !== 'github.com') return null; + const [owner, repo] = url.pathname.split('/').filter(Boolean); + if (!owner || !repo) return null; + return { owner, repo: repo.replace(/\.git$/, '') }; + } catch { + return null; + } +} + +async function inspectGitHubRepo(owner: string, repo: string) { + const detected = new Set(); + const missing = new Set(['mcp_manifest', 'llms_txt', 'agents_txt', 'x402_payment_metadata']); + const evidenceUrls: string[] = []; + const errors: string[] = []; + let metadata: Record = {}; + + try { + const repoResponse = await fetchWithTimeout(`https://api.github.com/repos/${owner}/${repo}`, { headers: githubHeaders(), cache: 'no-store' }); + if (repoResponse.ok) { + const data = await repoResponse.json() as GitHubRepositoryItem; + metadata = { + repository: data.full_name, + stars: data.stargazers_count, + language: data.language, + topics: data.topics || [], + updated_at: data.updated_at, + }; + const repoText = `${data.full_name || ''} ${data.description || ''} ${(data.topics || []).join(' ')}`.toLowerCase(); + if (repoText.includes('mcp')) detected.add('mcp_signal'); + if (repoText.includes('x402') || repoText.includes('payment')) { + detected.add('x402_payment_metadata'); + missing.delete('x402_payment_metadata'); + } + } + } catch (err) { + errors.push(`repo_metadata: ${err instanceof Error ? err.message : 'metadata fetch failed'}`); + } + + const contentsUrl = `https://api.github.com/repos/${owner}/${repo}/contents`; + try { + const contentsResponse = await fetchWithTimeout(contentsUrl, { headers: githubHeaders(), cache: 'no-store' }); + if (contentsResponse.ok) { + const contents = await contentsResponse.json() as Array<{ name?: string; html_url?: string }>; + for (const item of contents) { + const name = (item.name || '').toLowerCase(); + if (name === 'llms.txt') { + detected.add('llms_txt'); + missing.delete('llms_txt'); + if (item.html_url) evidenceUrls.push(item.html_url); + } + if (name === 'agents.txt') { + detected.add('agents_txt'); + missing.delete('agents_txt'); + if (item.html_url) evidenceUrls.push(item.html_url); + } + if (name.includes('mcp')) { + detected.add('mcp_manifest'); + missing.delete('mcp_manifest'); + if (item.html_url) evidenceUrls.push(item.html_url); + } + if (name.includes('402') || name.includes('payment')) { + detected.add('x402_payment_metadata'); + missing.delete('x402_payment_metadata'); + if (item.html_url) evidenceUrls.push(item.html_url); + } + } + } + } catch (err) { + errors.push(`repo_contents: ${err instanceof Error ? err.message : 'contents fetch failed'}`); + } + + return { + inspected_as: 'github_repository', + metadata, + detected_features: Array.from(detected), + missing_features: Array.from(missing), + evidence_urls: evidenceUrls, + errors, + }; +} + +function normalizeUrl(input: string) { + const trimmed = input.trim(); + if (!trimmed) return 'https://example.com/mcp'; + + try { + const url = new URL(trimmed); + url.hash = ''; + return url.toString().replace(/\/$/, ''); + } catch { + const repoLike = trimmed.match(/^([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)$/); + if (repoLike) return `https://github.com/${repoLike[1]}/${repoLike[2].replace(/\.git$/, '')}`; + + try { + const withProtocol = trimmed.includes('://') ? trimmed : `https://${trimmed}`; + const url = new URL(withProtocol); + url.hash = ''; + return url.toString().replace(/\/$/, ''); + } catch { + return 'https://example.com/mcp'; + } + } +} + +function safeHost(input: string) { + try { + return new URL(input).host || 'example.com'; + } catch { + return 'example.com'; + } +} + +function unsafeProbeReason(url: URL) { + if (!['http:', 'https:'].includes(url.protocol)) { + return `network probes skipped for unsupported protocol: ${url.protocol}`; + } + + const hostname = url.hostname.toLowerCase().replace(/^\[|\]$/g, ''); + if ( + hostname === 'localhost' || + hostname === 'metadata.google.internal' || + hostname.endsWith('.local') || + hostname.endsWith('.internal') + ) { + return 'network probes skipped for local or internal hostname'; + } + + if (isPrivateIp(hostname)) { + return 'network probes skipped for private, loopback, or link-local IP'; + } + + return null; +} + +function isPrivateIp(hostname: string) { + const ipv4 = hostname.match(/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/); + if (ipv4) { + const parts = ipv4.slice(1).map(Number); + if (parts.some((part) => part > 255)) return true; + const [first, second] = parts; + return ( + first === 0 || + first === 10 || + first === 127 || + first === 169 && second === 254 || + first === 172 && second >= 16 && second <= 31 || + first === 192 && second === 168 + ); + } + + return ( + hostname === '::1' || + hostname === '::' || + hostname.startsWith('fc') || + hostname.startsWith('fd') || + hostname.startsWith('fe80:') || + hostname.startsWith('::ffff:127.') || + hostname.startsWith('::ffff:10.') || + hostname.startsWith('::ffff:192.168.') + ); +} + +function inferMcpServerKind(input: string) { + const text = input.toLowerCase(); + if (text.includes('search')) return 'search'; + if (text.includes('data') || text.includes('enrich')) return 'data'; + if (text.includes('audit') || text.includes('security')) return 'audit'; + if (text.includes('agent')) return 'agent-discovery'; + return 'general'; +} + export async function GET(req: NextRequest, context: { params: Promise<{ product: string }> }) { const { product: productId } = await context.params; const product = getSeedProduct(productId); @@ -128,7 +730,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', diff --git a/lib/seed-products.ts b/lib/seed-products.ts index c47b524..680e383 100644 --- a/lib/seed-products.ts +++ b/lib/seed-products.ts @@ -123,7 +123,92 @@ 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: { + segment: { type: 'string' }, + lead_count: { type: 'number' }, + discovery: { + type: 'object', + properties: { + source: { const: 'github_repository_search' }, + searched_queries: { type: 'array', items: { type: 'string' } }, + errors: { + type: 'array', + items: { + type: 'object', + properties: { + query: { type: 'string' }, + status: { type: 'number' }, + message: { type: 'string' }, + }, + }, + }, + fallback_used: { type: 'boolean' }, + generated_at: { type: 'string' }, + }, + required: ['source', 'searched_queries', 'errors', 'fallback_used', 'generated_at'], + }, + scoring_model: { type: 'object' }, + leads: { + type: 'array', + items: { + type: 'object', + properties: { + source: { type: 'string' }, + target: { type: 'string' }, + repository: { type: 'string' }, + url: { type: 'string' }, + description: { type: 'string' }, + stars: { type: 'number' }, + language: { type: 'string' }, + topics: { type: 'array', items: { type: 'string' } }, + updated_at: { type: 'string' }, + fit_score: { type: 'number' }, + reason: { type: 'string' }, + discovery_query: { type: 'string' }, + suggested_paid_tool: { type: 'string' }, + price_usdc: { type: 'string' }, + affiliate_bps: { type: 'number' }, + outreach_hook: { type: 'string' }, + catalog_metadata_hint: { type: 'object' }, + evidence: { type: 'array', items: { type: 'string' } }, + }, + required: ['target', 'fit_score', 'reason', 'suggested_paid_tool', 'price_usdc', 'affiliate_bps', 'outreach_hook', 'catalog_metadata_hint'], + }, + }, + strategy_templates: { + type: 'array', + items: { + type: 'object', + properties: { + target: { type: 'string' }, + fit_score: { type: 'number' }, + reason: { type: 'string' }, + discovery_queries: { type: 'array', items: { type: 'string' } }, + suggested_paid_tool: { type: 'string' }, + price_usdc: { type: 'string' }, + affiliate_bps: { type: 'number' }, + outreach_hook: { type: 'string' }, + }, + }, + }, + next_actions: { + type: 'array', + items: { + type: 'object', + properties: { + target: { type: 'string' }, + action: { type: 'string' }, + submit_to_catalog: { type: 'object' }, + }, + required: ['target', 'action', 'submit_to_catalog'], + }, + }, + routed_by: { const: 'pyrimid' }, + }, + required: ['segment', 'lead_count', 'discovery', 'scoring_model', 'leads', 'strategy_templates', 'next_actions', 'routed_by'], + }, monthly_volume: 0, monthly_buyers: 0, network: 'base', @@ -144,7 +229,52 @@ 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: { + url: { type: 'string' }, + normalized_url: { type: 'string' }, + server_kind: { type: 'string' }, + inspection: { + type: 'object', + properties: { + inspected_as: { type: 'string' }, + detected_features: { type: 'array', items: { type: 'string' } }, + missing_features: { type: 'array', items: { type: 'string' } }, + evidence_urls: { type: 'array', items: { type: 'string' } }, + errors: { type: 'array', items: { type: 'string' } }, + }, + required: ['inspected_as', 'detected_features', 'missing_features', 'evidence_urls', 'errors'], + }, + recommended_paid_tools: { + type: 'array', + items: { + type: 'object', + properties: { + name: { type: 'string' }, + route: { type: 'string' }, + price_usdc: { type: 'string' }, + value: { type: 'string' }, + output_schema: { type: 'object' }, + }, + required: ['name', 'route', 'price_usdc', 'value', 'output_schema'], + }, + }, + pricing: { type: 'object' }, + route_shape: { type: 'object' }, + catalog_metadata: { type: 'object' }, + risk_notes: { type: 'array', items: { type: 'string' } }, + integration_steps: { type: 'array', items: { type: 'string' } }, + }, + required: ['url', 'normalized_url', 'server_kind', 'inspection', 'recommended_paid_tools', 'pricing', 'route_shape', 'catalog_metadata', 'risk_notes', 'integration_steps'], + }, + routed_by: { const: 'pyrimid' }, + }, + required: ['audit', 'routed_by'], + }, monthly_volume: 0, monthly_buyers: 0, network: 'base',