Package Market Search
estimate_marketSearch npm or PyPI to gauge market crowding for a package category. Get live result counts and representative matches to validate if a market niche is empty, niche, or competitive. Avoid unsupported claims with data.
Instructions
Search npm or PyPI to estimate how crowded a package category is before you claim that a market is empty, niche, or competitive. Use this when you have a category or search phrase such as 'edge orm' and want live result counts plus representative matches. Do not use it to compare exact known package names or to infer adoption from downloads; it reflects search results, not market share. Registry responses are cached for 5 minutes.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Short registry search phrase to evaluate, for example 'mcp memory server' or 'edge orm'. | |
| registry | No | Registry to search. Use 'npm' for JavaScript ecosystems and 'pypi' for Python ecosystems. | npm |
Output Schema
| Name | Required | Description | Default |
|---|---|---|---|
| query | Yes | Search phrase that was evaluated. | |
| registry | Yes | Registry that was searched. | |
| totalResults | Yes | Total number of matching packages reported by the registry search. | |
| topResults | Yes | Representative top search matches that help interpret the market count. |
Implementation Reference
- src/index.ts:676-763 (registration)Registration of the 'estimate_market' tool with input/output schemas and annotations.
"estimate_market", { title: "Package Market Search", description: "Search npm or PyPI to estimate how crowded a package category is before " + "you claim that a market is empty, niche, or competitive. Use this when " + "you have a category or search phrase such as 'edge orm' and want live " + "result counts plus representative matches. Do not use it to compare exact " + "known package names or to infer adoption from downloads; it reflects search " + "results, not market share. Registry responses are cached for 5 minutes.", inputSchema: { query: z.string().trim().min(2).describe( "Short registry search phrase to evaluate, for example 'mcp memory server' or 'edge orm'.", ), registry: z.enum(["npm", "pypi"]).default("npm").describe( "Registry to search. Use 'npm' for JavaScript ecosystems and 'pypi' for Python ecosystems.", ), }, outputSchema: { query: z.string().describe( "Search phrase that was evaluated.", ), registry: z.enum(["npm", "pypi"]).describe( "Registry that was searched.", ), totalResults: z.number().int().nonnegative().nullable().describe( "Total number of matching packages reported by the registry search.", ), topResults: z.array(z.object({ name: z.string().describe( "Package name returned by the registry.", ), description: z.string().describe( "Short package summary from registry metadata.", ), version: z.string().describe( "Latest version string returned in the result payload.", ), score: z.string().describe( "Registry relevance score when npm provides one.", ).optional(), })).describe( "Representative top search matches that help interpret the market count.", ), }, annotations: readOnlyNetworkToolAnnotations, }, async ({ query, registry }) => { if (registry === "npm") { const data = await searchNpm(sql, query); const results = []; for (const pkg of (data.objects || []).slice(0, 10)) { const p = pkg.package; results.push({ name: p.name, description: (p.description || "").slice(0, 120), version: p.version, score: pkg.score?.final?.toFixed(3), }); } logUsage("estimate_market", true); return structuredToolResult({ query, registry, totalResults: data.total ?? null, topResults: results, }); } if (registry === "pypi") { const data = await searchPyPI(sql, query); logUsage("estimate_market", true); return structuredToolResult({ query, registry, totalResults: data.total, topResults: data.results, }); } return structuredToolResult({ query, registry, totalResults: 0, topResults: [], }); } ); - src/index.ts:723-762 (handler)Handler function that executes the 'estimate_market' tool logic. Searches npm or PyPI registry using the provided query and returns total results count and top matching packages.
async ({ query, registry }) => { if (registry === "npm") { const data = await searchNpm(sql, query); const results = []; for (const pkg of (data.objects || []).slice(0, 10)) { const p = pkg.package; results.push({ name: p.name, description: (p.description || "").slice(0, 120), version: p.version, score: pkg.score?.final?.toFixed(3), }); } logUsage("estimate_market", true); return structuredToolResult({ query, registry, totalResults: data.total ?? null, topResults: results, }); } if (registry === "pypi") { const data = await searchPyPI(sql, query); logUsage("estimate_market", true); return structuredToolResult({ query, registry, totalResults: data.total, topResults: data.results, }); } return structuredToolResult({ query, registry, totalResults: 0, topResults: [], }); } - src/index.ts:687-720 (schema)Input schema defines 'query' (string, min 2 chars) and 'registry' (enum 'npm'|'pypi', defaults to 'npm'). Output schema includes query, registry, totalResults, and topResults array with name/description/version/score.
query: z.string().trim().min(2).describe( "Short registry search phrase to evaluate, for example 'mcp memory server' or 'edge orm'.", ), registry: z.enum(["npm", "pypi"]).default("npm").describe( "Registry to search. Use 'npm' for JavaScript ecosystems and 'pypi' for Python ecosystems.", ), }, outputSchema: { query: z.string().describe( "Search phrase that was evaluated.", ), registry: z.enum(["npm", "pypi"]).describe( "Registry that was searched.", ), totalResults: z.number().int().nonnegative().nullable().describe( "Total number of matching packages reported by the registry search.", ), topResults: z.array(z.object({ name: z.string().describe( "Package name returned by the registry.", ), description: z.string().describe( "Short package summary from registry metadata.", ), version: z.string().describe( "Latest version string returned in the result payload.", ), score: z.string().describe( "Registry relevance score when npm provides one.", ).optional(), })).describe( "Representative top search matches that help interpret the market count.", ), }, - src/index.ts:506-510 (helper)Helper that searches npm registry using the cachedFetch wrapper. Called by the estimate_market handler for npm searches.
async function searchNpm(sql: SqlTagFn, query: string, size = 10): Promise<NpmSearchResult> { const url = `https://registry.npmjs.org/-/v1/search?text=${encodeURIComponent(query)}&size=${size}`; const { body } = await cachedFetch(sql, url); return JSON.parse(body); } - src/index.ts:513-535 (helper)Helper that searches PyPI registry by fetching and parsing the HTML search results page. Called by the estimate_market handler for PyPI searches.
async function searchPyPI(sql: SqlTagFn, query: string): Promise<{ total: number; results: { name: string; description: string; version: string }[] }> { const url = `https://pypi.org/search/?q=${encodeURIComponent(query)}&o=`; const { body } = await cachedFetch(sql, url); const results: { name: string; description: string; version: string }[] = []; const nameRegex = /class="package-snippet__name">([^<]+)<\/span>/g; const versionRegex = /class="package-snippet__version">([^<]+)<\/span>/g; const descRegex = /class="package-snippet__description">([^<]*)<\/p>/g; const names: string[] = []; const versions: string[] = []; const descriptions: string[] = []; let match; while ((match = nameRegex.exec(body)) !== null) names.push(match[1].trim()); while ((match = versionRegex.exec(body)) !== null) versions.push(match[1].trim()); while ((match = descRegex.exec(body)) !== null) descriptions.push(match[1].trim()); for (let i = 0; i < Math.min(names.length, 10); i++) { results.push({ name: names[i], description: (descriptions[i] || "").slice(0, 120), version: versions[i] || "unknown", }); } return { total: names.length, results }; }