From ad6d9ab6639d3320abac10fe49401c6d352a25b5 Mon Sep 17 00:00:00 2001 From: Christian Andersson Date: Wed, 15 Apr 2026 11:50:45 +0200 Subject: [PATCH] Fetch package docs from JSR, NuGet, and Docker Hub APIs These three registries expose description/README fields in their public APIs but get_package_docs was skipping them and falling back to a GitHub README fetch. Use the native API responses instead: - JSR: description from /scopes/{scope}/packages/{name} - NuGet: description from the latest catalog entry in registration5 - Docker Hub: full_description (or description) from /v2/repositories GitHub fallback still applies when the registry returns no content. --- src/tools/get-package-docs.ts | 111 ++++++++++++++++++++++++++++++++-- 1 file changed, 106 insertions(+), 5 deletions(-) diff --git a/src/tools/get-package-docs.ts b/src/tools/get-package-docs.ts index 166ba0f..a4e2364 100644 --- a/src/tools/get-package-docs.ts +++ b/src/tools/get-package-docs.ts @@ -237,6 +237,95 @@ async function fetchPubDescription( return null; } +/** + * Fetch description from JSR (package-level description + GitHub repo link) + */ +async function fetchJsrDescription( + packageName: string, +): Promise { + const repoConfig = getRepositoryConfig("jsr"); + const match = packageName.match(/^@([^/]+)\/(.+)$/); + if (!match) return null; + const [, scope, name] = match; + + try { + const response = await fetchWithHeaders( + `${repoConfig.url}/scopes/${scope}/packages/${name}`, + { auth: repoConfig.auth }, + ); + if (response.ok) { + const data = await response.json(); + return data.description || null; + } + } catch { + // Fall through + } + return null; +} + +/** + * Fetch description from NuGet registration catalog entry + */ +async function fetchNugetDescription( + packageName: string, +): Promise { + const repoConfig = getRepositoryConfig("nuget"); + + try { + const response = await fetchWithHeaders( + `${repoConfig.url}/registration5-gz-semver2/${packageName.toLowerCase()}/index.json`, + { + auth: repoConfig.auth, + headers: { "Accept-Encoding": "gzip" }, + }, + ); + if (response.ok) { + const data = await response.json(); + // Walk registration pages to find the last catalog entry (latest version) + const pages = data.items || []; + for (let i = pages.length - 1; i >= 0; i--) { + const items = pages[i].items; + if (items && items.length > 0) { + const entry = items[items.length - 1].catalogEntry; + if (entry?.description) { + return entry.description; + } + } + } + } + } catch { + // Fall through + } + return null; +} + +/** + * Fetch full description (README) from Docker Hub + */ +async function fetchDockerDescription( + imageName: string, +): Promise { + const repoConfig = getRepositoryConfig("docker"); + const cleanName = imageName.split(":")[0].split("@")[0]; + const parts = cleanName.split("/"); + const fullName = parts.length === 1 ? `library/${parts[0]}` : cleanName; + + try { + const response = await fetchWithHeaders( + `${repoConfig.url}/v2/repositories/${fullName}`, + { auth: repoConfig.auth }, + ); + if (response.ok) { + const data = await response.json(); + // full_description is the README content, description is a short summary + return data.full_description || data.description || null; + } + } catch { + // Fall through + } + return null; +} + /** * Fetch README from crates.io */ @@ -364,12 +453,24 @@ export async function getPackageDocs( if (content) result.source = "registry"; break; - // These registries don't have README in API, skip to repository fetch - case "maven": - case "go": case "jsr": + content = await fetchJsrDescription(packageName); + if (content) result.source = "registry"; + break; + case "nuget": + content = await fetchNugetDescription(packageName); + if (content) result.source = "registry"; + break; + case "docker": + content = await fetchDockerDescription(packageName); + if (content) result.source = "registry"; + break; + + // These registries don't have README in API, skip to repository fetch + case "maven": + case "go": case "swift": case "github-actions": break; @@ -428,8 +529,8 @@ export function registerGetPackageDocsTool(server: McpServer): void { Supported registries: ${supportedRegistries.join(", ")} This tool fetches README content to help understand how to use a package. -For npm, PyPI, and Cargo, README is fetched directly from the registry API. -For Maven, Go, JSR, NuGet, Docker, Swift, and GitHub Actions, README is fetched from the package's GitHub repository. +For npm, PyPI, Cargo, RubyGems, Packagist, pub.dev, JSR, NuGet, and Docker, documentation is fetched directly from the registry API. +For Maven, Go, Swift, and GitHub Actions, README is fetched from the package's GitHub repository. Returns: - README content (when available)