Pricing Page Scan
check_pricingExtract plan costs, free tiers, and plan names from a public pricing page to provide live evidence for quoting.
Instructions
Fetch a public pricing page and extract first-pass pricing signals before you quote plan costs, free tiers, or plan names. Use this when you have a likely pricing URL and need live evidence from the page itself. The tool uses heuristic text extraction from the fetched HTML, so it can miss JavaScript-rendered, logged-in, or heavily obfuscated pricing details. Results are cached for 5 minutes.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Public pricing-page URL to analyze, for example https://stripe.com/pricing. |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Pricing page that was analyzed. | |
| cached | No | True when the page body came from the 5-minute cache instead of a new fetch. | |
| pricesFound | No | Distinct price-like strings extracted from the page text. | |
| plansDetected | No | Normalized plan labels detected from the page text. | |
| hasFreeOption | No | True when the page contains signals that a free plan or $0 option exists. | |
| hasFreeTrial | No | True when the page contains signals that a free trial exists. | |
| pageLength | No | Size of the fetched page body in characters. | |
| error | No | Fetch or parsing error when the pricing page could not be analyzed. |
Implementation Reference
- src/index.ts:765-844 (registration)The check_pricing tool is registered via this.server.registerTool() with the name 'check_pricing'. This is the registration and handler combined — the tool is defined with inputSchema, outputSchema, and the async handler function all within the registerTool call.
// ─────────────────────────────────────────────── // PAID $0.02: check_pricing // ─────────────────────────────────────────────── this.server.registerTool( "check_pricing", { title: "Pricing Page Scan", description: "Fetch a public pricing page and extract first-pass pricing signals before " + "you quote plan costs, free tiers, or plan names. Use this when you already " + "have a likely pricing URL and need a quick live scan of visible page text. " + "It returns price-like strings, heuristic plan labels, free or free-trial " + "signals, and cache information. It does not map prices to exact plans, " + "normalize currencies, execute checkout flows, or guarantee that a price " + "applies to a specific region or customer type. JavaScript-rendered, " + "logged-in, or heavily obfuscated pricing details can be missed. Results " + "are cached for 5 minutes.", inputSchema: { url: z.string().url().describe( "Public pricing or plans URL to analyze. Prefer the specific pricing page, for example https://stripe.com/pricing, rather than a generic homepage.", ), }, outputSchema: { url: z.string().describe( "Pricing page that was analyzed.", ), cached: z.boolean().describe( "True when the page body came from the 5-minute cache instead of a new fetch.", ).optional(), pricesFound: z.array(z.string()).describe( "Distinct price-like strings extracted from the page text. These are not linked back to specific plans or billing conditions.", ).optional(), plansDetected: z.array(z.string()).describe( "Lowercased heuristic plan labels detected from the page text. They are useful hints, not authoritative plan identifiers.", ).optional(), hasFreeOption: z.boolean().describe( "True when the page contains signals that a free plan or $0 option exists somewhere on the page. This is a page-level signal, not proof that the offer is currently self-serve or globally available.", ).optional(), hasFreeTrial: z.boolean().describe( "True when the page contains signals that a free trial exists somewhere on the page.", ).optional(), pageLength: z.number().int().nonnegative().describe( "Size of the fetched page body in characters.", ).optional(), error: z.string().describe( "Fetch or parsing error when the pricing page could not be analyzed.", ).optional(), }, annotations: readOnlyNetworkToolAnnotations, }, async ({ url }) => { try { const { body, fromCache } = await cachedFetch(sql, url); // Extract pricing signals from page content const priceRegex = /\$\d[\d,]*(?:\.\d{2})?(?:\s*\/\s*(?:mo(?:nth)?|yr|year|user|seat|req|call|token))?/gi; const prices = [...new Set(body.match(priceRegex) || [])].slice(0, 20); const planRegex = /(?:free|starter|basic|pro|premium|enterprise|business|team|hobby|growth|scale)\s*(?:plan|tier)?/gi; const plans = [...new Set((body.match(planRegex) || []).map(p => p.trim().toLowerCase()))]; const hasFree = /free\s*(?:plan|tier|forever|trial)|(?:\$0|0\.00)/i.test(body); const hasFreeTrial = /free\s*trial|try\s*(?:it\s*)?free|start\s*free/i.test(body); logUsage("check_pricing", true); return structuredToolResult({ url, cached: fromCache, pricesFound: prices, plansDetected: plans, hasFreeOption: hasFree, hasFreeTrial: hasFreeTrial, pageLength: body.length, }); } catch (e: unknown) { logUsage("check_pricing", false); return structuredToolResult({ url, error: e instanceof Error ? e.message : String(e), }); } } ); - src/index.ts:786-813 (schema)Input and output schema for check_pricing. Input: url (string). Output: url, cached, pricesFound, plansDetected, hasFreeOption, hasFreeTrial, pageLength, error.
}, outputSchema: { url: z.string().describe( "Pricing page that was analyzed.", ), cached: z.boolean().describe( "True when the page body came from the 5-minute cache instead of a new fetch.", ).optional(), pricesFound: z.array(z.string()).describe( "Distinct price-like strings extracted from the page text. These are not linked back to specific plans or billing conditions.", ).optional(), plansDetected: z.array(z.string()).describe( "Lowercased heuristic plan labels detected from the page text. They are useful hints, not authoritative plan identifiers.", ).optional(), hasFreeOption: z.boolean().describe( "True when the page contains signals that a free plan or $0 option exists somewhere on the page. This is a page-level signal, not proof that the offer is currently self-serve or globally available.", ).optional(), hasFreeTrial: z.boolean().describe( "True when the page contains signals that a free trial exists somewhere on the page.", ).optional(), pageLength: z.number().int().nonnegative().describe( "Size of the fetched page body in characters.", ).optional(), error: z.string().describe( "Fetch or parsing error when the pricing page could not be analyzed.", ).optional(), }, annotations: readOnlyNetworkToolAnnotations, - src/index.ts:815-843 (handler)The async handler function for check_pricing. It fetches the pricing URL, extracts price strings via regex, detects plan labels, checks for free/trial signals, and returns the structured result.
async ({ url }) => { try { const { body, fromCache } = await cachedFetch(sql, url); // Extract pricing signals from page content const priceRegex = /\$\d[\d,]*(?:\.\d{2})?(?:\s*\/\s*(?:mo(?:nth)?|yr|year|user|seat|req|call|token))?/gi; const prices = [...new Set(body.match(priceRegex) || [])].slice(0, 20); const planRegex = /(?:free|starter|basic|pro|premium|enterprise|business|team|hobby|growth|scale)\s*(?:plan|tier)?/gi; const plans = [...new Set((body.match(planRegex) || []).map(p => p.trim().toLowerCase()))]; const hasFree = /free\s*(?:plan|tier|forever|trial)|(?:\$0|0\.00)/i.test(body); const hasFreeTrial = /free\s*trial|try\s*(?:it\s*)?free|start\s*free/i.test(body); logUsage("check_pricing", true); return structuredToolResult({ url, cached: fromCache, pricesFound: prices, plansDetected: plans, hasFreeOption: hasFree, hasFreeTrial: hasFreeTrial, pageLength: body.length, }); } catch (e: unknown) { logUsage("check_pricing", false); return structuredToolResult({ url, error: e instanceof Error ? e.message : String(e), }); } } - src/index.ts:491-498 (helper)cachedFetch helper used by check_pricing to fetch and cache the pricing page body with a 5-minute TTL.
async function cachedFetch(sql: SqlTagFn, url: string): Promise<{ body: string; fromCache: boolean }> { const cached = cacheGet(sql, url); if (cached) return { body: cached, fromCache: true }; const resp = await fetch(url, { headers: { "User-Agent": "GroundTruth/0.3" } }); const body = await resp.text(); if (resp.ok) cacheSet(sql, url, body); return { body, fromCache: false }; } - src/index.ts:467-488 (helper)cacheGet and cacheSet helpers used by cachedFetch for SQLite-backed caching of fetched page bodies.
// --- Cache helpers using Durable Object SQLite tagged template --- function cacheGet(sql: SqlTagFn, key: string): string | null { const rows = sql<{ data: string; ts: number }>`SELECT data, ts FROM cache WHERE key = ${key}`; if (rows.length === 0) return null; const row = rows[0]; if (Date.now() - row.ts > CACHE_TTL_MS) { sql`DELETE FROM cache WHERE key = ${key}`; return null; } return row.data; } function cacheSet(sql: SqlTagFn, key: string, data: string): void { if (data.length > MAX_CACHEABLE_BODY_BYTES) return; const ts = Date.now(); try { sql`INSERT OR REPLACE INTO cache (key, data, ts) VALUES (${key}, ${data}, ${ts})`; } catch { // Cache writes should never take down a verification request. } }