Public API for querying hex.pm and hexdocs.pm.
All functions return {:ok, result} or {:error, reason} and are designed
to be used from iex, other Elixir code, or as the backend for MCP tools.
Quick Start
iex> HexpmMcp.search("json")
{:ok, [%{name: "jason", version: "1.4.4", downloads_all: 197_000_000, ...}, ...]}
iex> HexpmMcp.get_info("plug")
{:ok, %{name: "plug", description: "Composable modules for web applications",
downloads: %{all: 156_000_000, recent: 3_100_000, week: 250_000, day: 35_000},
licenses: ["Apache-2.0"], latest_stable_version: "1.19.1", ...}}
iex> HexpmMcp.health_check("req")
{:ok, %{name: "req",
maintenance: %{status: "Active", age: "4 years ago", days_since_release: 57, total_versions: 52},
popularity: %{all: 11_500_000, recent: 1_600_000, week: 141_000},
quality: %{has_docs: true, licenses: ["Apache-2.0"], required_deps: 3, optional_deps: 4},
risk: %{owner_count: 1, retired_count: 0},
links: %{...}}}Function Groups
Simple lookups
search/2, get_info/1, get_downloads/1, get_owners/1, get_versions/1
Version-resolving lookups
get_release/2, get_dependencies/2, get_features/2 -- pass nil for version
to automatically resolve to the latest stable version.
Composite analysis
compare_packages/1, health_check/1, audit_dependencies/2,
find_alternatives/1, dependency_tree/3 -- these make multiple API calls
in parallel and return aggregated results.
HexDocs browsing
get_readme/2, get_docs/2, get_doc_item/3, search_docs/3
Error Values
All functions return {:error, reason} on failure. Common reasons:
:not_found-- package or version does not exist on hex.pm:rate_limited-- hex.pm API rate limit exceeded:too_few_packages/:too_many_packages-- invalid input tocompare_packages/1{:api_error, status, body}-- unexpected HTTP status from hex.pm{:request_failed, reason}-- network error
Summary
Functions
Audit a package's dependencies for risks.
Audit a list of mix.exs dependencies for risks.
Compare 2-5 hex.pm packages side by side.
Get the full transitive dependency tree for a package (BFS, max depth 5).
Find and compare alternative packages for a given hex.pm package.
Get the dependencies of a package version.
Get full documentation for a specific module or function.
Get the module listing for a package's documentation.
Get download statistics for a hex.pm package.
Get optional features/extras for a package release.
Get detailed information about a hex.pm package.
Get the owners/maintainers of a hex.pm package.
Get the README content for a package as markdown.
Get detailed information about a specific release.
List all versions of a hex.pm package with retirement status.
Comprehensive health check for a hex.pm package.
Search for packages on hex.pm by name/keywords.
Search within a package's documentation by name.
Check which mix.exs dependencies have newer versions available.
Types
Functions
Audit a package's dependencies for risks.
Checks each dependency in parallel for:
- Retired versions
- Stale packages (no release in 2+ years)
- Single-maintainer packages (bus factor risk)
- Known vulnerabilities via OSV.dev
Examples
iex> HexpmMcp.audit_dependencies("phoenix")
{:ok, %{
name: "phoenix", version: "1.8.5",
total_checked: 10, total_warnings: 8, deps_with_warnings: 7,
results: [
%{name: "jason", issues: ["single maintainer"]},
%{name: "plug", issues: ["2 retired version(s)", "3 known vulnerability(ies)"]},
%{name: "telemetry", issues: []},
...
]
}}
iex> HexpmMcp.audit_dependencies("phoenix", "1.7.0")
{:ok, %{name: "phoenix", version: "1.7.0", ...}}
Audit a list of mix.exs dependencies for risks.
Accepts a deps string (as it appears in mix.exs) and runs a comprehensive audit on each package: staleness, retirement, bus factor, and CVEs via OSV.dev.
Examples
iex> deps = """
...> {:phoenix, "~> 1.7"},
...> {:ecto, "~> 3.10"},
...> {:jason, "~> 1.0"}
...> """
iex> HexpmMcp.audit_mix_deps(deps)
{:ok, %{
total_checked: 3,
total_warnings: 2,
deps_with_warnings: 2,
results: [
%{name: "ecto", pinned_version: "~> 3.10", issues: []},
%{name: "jason", pinned_version: "~> 1.0", issues: ["single maintainer"]},
%{name: "phoenix", pinned_version: "~> 1.7", issues: ["3 retired version(s)"]},
...
]
}}
@spec compare_packages([String.t()]) :: {:ok, [map()]} | {:error, :too_few_packages | :too_many_packages}
Compare 2-5 hex.pm packages side by side.
Fetches package info and dependency counts in parallel for each package.
Examples
iex> HexpmMcp.compare_packages(["plug", "bandit", "cowboy"])
{:ok, [
%{name: "plug", downloads_all: 156_000_000, downloads_recent: 3_100_000,
latest_version: "1.19.1", updated_at: "2025-12-09T...",
licenses: "Apache-2.0", dep_count: 3},
%{name: "bandit", downloads_all: 9_900_000, ...},
%{name: "cowboy", downloads_all: 78_000_000, ...}
]}
iex> HexpmMcp.compare_packages(["only_one"])
{:error, :too_few_packages}
Get the full transitive dependency tree for a package (BFS, max depth 5).
Traverses dependencies breadth-first, resolving each to its latest version. Deduplicates by package name (each package appears once in the tree).
Options
:max_depth-- maximum depth to traverse (default 5, capped at 5)
Examples
iex> HexpmMcp.dependency_tree("req", nil, max_depth: 2)
{:ok, %{
name: "req", version: "0.5.17", total_unique_deps: 8,
tree: [
%{name: "req", version: "0.5.17", depth: 0, deps: [
%{name: "finch", requirement: "~> 0.17", optional: false, depth: 1},
%{name: "mime", requirement: "~> 2.0 or ~> 1.0", optional: false, depth: 1},
...
]},
%{name: "finch", version: "0.21.0", depth: 1, deps: [...]},
...
]
}}
Find and compare alternative packages for a given hex.pm package.
Extracts keywords from the package description, searches for similar packages, deduplicates, and returns the top 10 sorted by recent downloads.
Examples
iex> HexpmMcp.find_alternatives("httpoison")
{:ok, %{
package: %{name: "httpoison", description: "HTTP client for Elixir",
downloads_all: 42_000_000, downloads_recent: 500_000},
alternatives: [
%{name: "req", version: "0.5.17", downloads_all: 11_500_000,
downloads_recent: 1_600_000, updated_at: "2026-02-07T...",
status: "Active", description: "Req is a batteries-included HTTP client...",
licenses: "Apache-2.0"},
%{name: "finch", ...},
...
]
}}
Get the dependencies of a package version.
If version is nil, resolves to the latest stable version.
Examples
iex> HexpmMcp.get_dependencies("req")
{:ok, %{name: "req", version: "0.5.17", dependencies: [
%{name: "finch", requirement: "~> 0.17", optional: false},
%{name: "jason", requirement: "~> 1.0", optional: true},
%{name: "mime", requirement: "~> 2.0 or ~> 1.0", optional: false},
...
]}}
iex> HexpmMcp.get_dependencies("plug", "1.19.1")
{:ok, %{name: "plug", version: "1.19.1", dependencies: [...]}}
@spec get_doc_item(String.t(), String.t(), String.t() | nil) :: {:ok, String.t()} | {:error, error()}
Get full documentation for a specific module or function.
Fetches the module's hexdocs.pm page and converts it to markdown.
Examples
iex> HexpmMcp.get_doc_item("plug", "Plug.Conn")
{:ok, "# Plug.Conn\n\nThe Plug connection.\n\n..."}
iex> HexpmMcp.get_doc_item("plug", "Plug.Conn", "1.19.1")
{:ok, "# Plug.Conn\n\n..."}
Get the module listing for a package's documentation.
Parses the hexdocs.pm sidebar data to extract all modules, behaviours, and protocols with their function/type counts.
Examples
iex> HexpmMcp.get_docs("plug")
{:ok, [
%{name: "Plug", type: "module", doc: "Types: 1, Callbacks: 2, Functions: 2"},
%{name: "Plug.Conn", type: "module", doc: "Types: 22, Functions: 52"},
%{name: "Plug.Router", type: "module", doc: "Functions: 12"},
...
]}
Get download statistics for a hex.pm package.
Examples
iex> HexpmMcp.get_downloads("phoenix")
{:ok, %{name: "phoenix", all: 148_000_000, recent: 2_600_000, week: 223_000, day: 13_000}}
Get optional features/extras for a package release.
If version is nil, resolves to the latest stable version.
Examples
iex> HexpmMcp.get_features("req")
{:ok, %{
name: "req", version: "0.5.17",
optional_deps: [
%{name: "brotli", requirement: "~> 0.3.1"},
%{name: "jason", requirement: "~> 1.0"},
...
],
extra_metadata: %{}
}}
Get detailed information about a hex.pm package.
Returns metadata, download stats, licenses, links, and version info.
Examples
iex> HexpmMcp.get_info("plug")
{:ok, %{
name: "plug",
description: "Composable modules for web applications",
latest_stable_version: "1.19.1",
latest_version: "1.19.1",
downloads: %{all: 156_000_000, recent: 3_100_000, week: 250_000, day: 35_000},
licenses: ["Apache-2.0"],
build_tools: ["mix"],
elixir_requirement: "~> 1.14",
inserted_at: "2013-12-31T...",
updated_at: "2025-12-09T...",
docs_url: "https://hexdocs.pm/plug/",
hex_url: "https://hex.pm/packages/plug",
links: %{"GitHub" => "https://github.com/elixir-plug/plug"}
}}
iex> HexpmMcp.get_info("nonexistent")
{:error, :not_found}
Get the owners/maintainers of a hex.pm package.
Examples
iex> HexpmMcp.get_owners("phoenix")
{:ok, [%{username: "josevalim", email: "jose@example.com"}, ...]}
Get the README content for a package as markdown.
Fetches the README from hexdocs.pm and converts HTML to markdown.
Examples
iex> HexpmMcp.get_readme("req")
{:ok, "# Req\n\nReq is a batteries-included HTTP client for Elixir.\n\n..."}
iex> HexpmMcp.get_readme("req", "0.5.0")
{:ok, "# Req\n\n..."}
Get detailed information about a specific release.
If version is nil, resolves to the latest stable version.
Examples
iex> HexpmMcp.get_release("plug", "1.19.1")
{:ok, %{
name: "plug", version: "1.19.1", publisher: "josevalim",
inserted_at: "2025-12-09T...", updated_at: "2025-12-09T...",
downloads: 500_000, has_docs: true,
build_tools: ["mix"], elixir_requirement: "~> 1.14",
dependencies: [
%{name: "mime", requirement: "~> 2.0", optional: false},
%{name: "plug_crypto", requirement: "~> 2.1", optional: false}
],
retired: nil
}}
# Resolves to latest stable when version is nil
iex> HexpmMcp.get_release("plug")
{:ok, %{name: "plug", version: "1.19.1", ...}}
List all versions of a hex.pm package with retirement status.
Examples
iex> HexpmMcp.get_versions("plug")
{:ok, %{name: "plug", versions: [
%{version: "1.19.1", inserted_at: "2025-12-09T...", has_docs: true, retired: nil},
%{version: "1.14.0", inserted_at: "2023-03-15T...", has_docs: true,
retired: %{reason: "security", message: "CVE-2024-..."}},
...
]}}
Comprehensive health check for a hex.pm package.
Fetches package info, owners, and latest release in parallel, then computes maintenance status, popularity metrics, quality indicators, and risk factors.
Examples
iex> HexpmMcp.health_check("req")
{:ok, %{
name: "req",
maintenance: %{
age: "4 years ago",
total_versions: 52,
status: "Active", # Active (<90d), Recent (<1y), Aging (<2y), Stale (2y+)
days_since_release: 57
},
popularity: %{all: 11_500_000, recent: 1_600_000, week: 141_000},
quality: %{
has_docs: true,
licenses: ["Apache-2.0"],
build_tools: ["mix"],
elixir_requirement: "~> 1.14",
required_deps: 3,
optional_deps: 4
},
risk: %{
owner_count: 1, # 1 = "single maintainer" warning
retired_count: 0
},
links: %{
hex_url: "https://hex.pm/packages/req",
docs_url: "https://hexdocs.pm/req/",
"GitHub" => "https://github.com/wojtekmach/req"
}
}}
Search for packages on hex.pm by name/keywords.
Options
:sort-- sort order:"name","recent_downloads","total_downloads","inserted_at","updated_at":page-- page number (1-indexed, 100 results per page)
Examples
iex> HexpmMcp.search("json")
{:ok, [%{name: "jason", version: "1.4.4", description: "A blazing fast JSON parser...",
downloads_all: 197_000_000, downloads_recent: 4_000_000,
url: "https://hex.pm/packages/jason"}, ...]}
iex> HexpmMcp.search("http client", sort: "recent_downloads", page: 1)
{:ok, [%{name: "req", ...}, %{name: "finch", ...}, ...]}
Search within a package's documentation by name.
Searches module names and doc snippets in the hexdocs.pm sidebar data. Returns up to 20 matching items.
Examples
iex> HexpmMcp.search_docs("phoenix", "Router")
{:ok, [
%{"title" => "Phoenix.Router", "type" => "module", "doc" => "Reflection: 3, Functions: 21"},
%{"title" => "Phoenix.Router.NoRouteError", "type" => "module", "doc" => ""},
...
]}
Check which mix.exs dependencies have newer versions available.
Accepts a deps string and checks each against the latest version on hex.pm. Flags major version bumps as potentially breaking.
Examples
iex> deps = """
...> {:phoenix, "~> 1.7"},
...> {:jason, "~> 1.0"}
...> """
iex> HexpmMcp.upgrade_check(deps)
{:ok, %{
total_checked: 2,
upgrades_available: 1,
results: [
%{name: "jason", pinned_version: "~> 1.0", latest_version: "1.4.4",
status: :up_to_date},
%{name: "phoenix", pinned_version: "~> 1.7", latest_version: "1.8.5",
status: :minor_upgrade, retired: false}
]
}}