Packages

HexDocs MCP is a project that provides semantic search capabilities for Hex package documentation, designed specifically for AI applications. It downloads, processes, and generates embeddings from Hex package documentation and provides a Model Context Protocol (MCP) server for searching.

Current section

Files

Jump to
hexdocs_mcp lib mix tasks hex.docs.mcp.ex
Raw

lib/mix/tasks/hex.docs.mcp.ex

defmodule Mix.Tasks.Hex.Docs.Mcp do
use Mix.Task
@shortdoc "Downloads Hex docs, converts to markdown chunks, and generates embeddings"
@requirements ["app.config", "app.start"]
@moduledoc """
Fetches Hex docs for a package, converts HTML to markdown, creates semantic chunks, and generates embeddings by default.
## Usage
$ mix hex.docs.mcp COMMAND [options] PACKAGE [VERSION]
* `COMMAND` - Either `fetch` or `search` (required)
* `PACKAGE` - Hex package name to work with (required for fetch/search)
* `VERSION` - Package version to work with (optional, defaults to latest)
## Options
--model MODEL - Ollama model to use for embeddings (default: nomic-embed-text)
--query QUERY - Query string for search command
## Examples
$ mix hex.docs.mcp fetch phoenix # Download, chunk docs and generate embeddings
$ mix hex.docs.mcp fetch --model all-minilm phoenix # Use custom model for embeddings
$ mix hex.docs.mcp search --query "channels" phoenix # Search in existing embeddings
The fetch command:
1. Downloads docs using mix hex.docs
2. Converts HTML to a single markdown file
3. Chunks the markdown text for embedding in vector databases
4. Generates embeddings using Ollama
5. Automatically creates the database if it doesn't exist
The search command:
1. Looks up existing embeddings for the specified package
2. Performs a similarity search using the query
3. Returns the most relevant results
"""
def run(["fetch" | args]) do
Application.ensure_all_started(:hexdocs_mcp)
%{package: package, version: version, model: model} = parse_args!(args)
ensure_database_initialized()
HexdocsMcp.CLI.process_docs(package, version, model)
end
def run(["search" | args]) do
Application.ensure_all_started(:hexdocs_mcp)
%{package: package, version: version, model: model, search: search} = parse_args!(args)
ensure_database_initialized()
HexdocsMcp.CLI.search(search, package, version, model)
end
def run([]) do
Mix.raise("Package name is required. Usage: mix hex.docs.mcp CMD [options] PACKAGE [VERSION]")
end
def run(args) do
Application.ensure_all_started(:hexdocs_mcp)
%{package: package, version: version, model: model, search: search} = parse_args!(args)
ensure_database_initialized()
if search do
HexdocsMcp.CLI.search(search, package, version, model)
else
HexdocsMcp.CLI.process_docs(package, version, model)
end
end
# Check if database is initialized and initialize if not
defp ensure_database_initialized do
# Ensure data directory exists
data_path = HexdocsMcp.Config.data_path()
File.mkdir_p(data_path)
try do
# Try executing a simple query against the embeddings table
HexdocsMcp.Repo.query!("SELECT 1 FROM embeddings LIMIT 1")
rescue
# If the table doesn't exist, initialize the database
error in Exqlite.Error ->
if error.message =~ "no such table" do
Mix.shell().info("Database not initialized. Running initialization...")
HexdocsMcp.CLI.init_database()
else
# Re-raise if it's a different error
reraise error, __STACKTRACE__
end
# Handle connection errors separately
_ in DBConnection.ConnectionError ->
Mix.shell().info("Database connection issue. Running initialization...")
HexdocsMcp.CLI.init_database()
end
end
defp parse_args!([]) do
Mix.raise(
"Package name is required. Usage: mix hex.docs.mcp fetch [options] PACKAGE [VERSION]"
)
end
defp parse_args!(args) do
{opts, args} =
OptionParser.parse!(args,
aliases: [
m: :model,
q: :query
],
strict: [
model: :string,
query: :string
]
)
[package | args] = args
%{
package: package,
version: List.first(args) || "latest",
model: opts[:model] || HexdocsMcp.Config.default_embedding_model(),
search: opts[:query]
}
end
end