Semantic code search and analysis for Elixir projects via MCP (Model Context Protocol)
Codicil is an Elixir library that provides AI coding assistants with deep semantic understanding of your codebase. Ask questions in natural language, find functions by behavior, trace dependencies, and understand code relationships—all through the Model Context Protocol.
Imagine asking your AI coding assistant:
- "Find functions that validate user input"
- "Show me what calls this function"
- "What dependencies does this module have?"
Codicil makes this possible by:
- Understanding your code - Analyzes Elixir projects during compilation, extracting functions, modules, and their relationships
- Creating semantic search - Uses AI to understand what code does, not just what it's named
- Connecting to AI assistants - Provides tools via MCP that work with Claude, ChatGPT, and other AI coding tools
- Semantic Function Search - Find code by describing what it does in plain English
- Dependency Analysis - See function call graphs and module relationships
- Automatic Indexing - Hooks into compilation, no manual scanning needed
- Multi-LLM Support - Works with Anthropic Claude, OpenAI, Cohere, Google Gemini, and Grok
These steps apply to both Phoenix and non-Phoenix projects.
Add Codicil to your mix.exs:
def deps do
[
# ... your existing dependencies
{:codicil, "~> 0.4", only: [:dev, :test]}
]
endThen install:
mix deps.getCodicil uses SQLite to store indexed code. Initialize it:
mix codicil.setupCreate or edit your .env file (or set in your shell):
# Required: Choose your LLM provider
export CODICIL_LLM_PROVIDER=openai # or: anthropic, cohere, google, grok
# Required: Add your API key for the chosen provider
export OPENAI_API_KEY=your_key_here
# OR
export ANTHROPIC_API_KEY=your_key_here
# OR
export COHERE_API_KEY=your_key_here
# OR
export GOOGLE_API_KEY=your_key_here
# Optional: Embeddings provider (defaults to openai)
# If you want to use Voyage AI for embeddings:
# export CODICIL_EMBEDDING_PROVIDER=voyage
# export VOYAGE_API_KEY=your_voyage_key_hereLoad the environment variables:
source .envEdit your mix.exs to enable Codicil's tracer in development:
def project do
[
app: :my_app,
version: "0.1.0",
elixir: "~> 1.14",
# Enable Codicil tracer in dev/test
elixirc_options: elixirc_options(Mix.env()),
deps: deps()
]
end
defp elixirc_options(:prod), do: []
defp elixirc_options(_env), do: [tracers: [Codicil.Tracer]]This ensures the tracer only runs in development and test, not in production.
Continue with these steps if you're using Phoenix.
Add the Codicil MCP endpoint to your Phoenix router. Edit lib/my_app_web/router.ex:
defmodule MyAppWeb.Endpoint do
# ... your endpoint stuff
# Codicil MCP endpoint (only available when Codicil is loaded)
if Code.ensure_loaded?(Codicil) do
plug Codicil.Plug
end
endStart your Phoenix server:
mix phx.serverThe MCP server will be available at http://localhost:4000/codicil/mcp (or your configured Phoenix port).
Compile your project to trigger indexing:
# In another terminal
mix compile --forceWatch the Phoenix logs to see functions being indexed!
Continue with these steps if you're NOT using Phoenix.
Add Bandit to serve the MCP endpoint. Edit mix.exs:
def deps do
[
# ... your existing dependencies
{:codicil, "~> 0.4", only: [:dev, :test]},
{:bandit, "~> 1.6", only: :dev} # HTTP server for MCP
]
endInstall:
mix deps.getAdd a Mix alias to start the MCP server. Edit your mix.exs:
def project do
[
# ... other config
aliases: aliases()
]
end
defp aliases do
[
# ... your existing aliases (if any)
codicil: "run --no-halt -e 'Bandit.start_link(plug: Codicil.Plug, port: 4700)'"
]
endIf you want to run multiple MCP servers simultaneously (e.g., Codicil + Tidewave), you can combine them in a single Mix alias:
defp aliases do
[
# ... your existing aliases (if any)
mcp: "run --no-halt -e 'Agent.start(fn -> Bandit.start_link(plug: Codicil.Plug, port: 4700); Bandit.start_link(plug: Tidewave, port: 4000) end)'"
]
endThis will start both MCP servers in the same Agent:
- Codicil MCP server on
http://localhost:4700/codicil/mcp - Tidewave MCP server on
http://localhost:4700/tidewave/mcp
Note: Each MCP server needs its own port. Adjust port numbers as needed.
Start the MCP server:
mix codicilThe MCP server will start on http://localhost:4700/codicil/mcp.
In a second terminal, compile your project to trigger indexing:
mix compile --forceWatch the logs in Terminal 1 to see functions being indexed!
Once Codicil is running, configure your AI assistant (Claude Desktop, Cline, etc.) to connect to the MCP server:
Phoenix Projects:
- URL:
http://localhost:4000/codicil/mcp(use your Phoenix port) - Transport: HTTP with SSE
Non-Phoenix Projects:
- URL:
http://localhost:4700/codicil/mcp - Transport: HTTP with SSE
The following MCP tools are now available:
find_similar_functions- Semantic search by descriptionlist_function_callers- Find what calls a function (useful for debugging and refactoring)list_function_callees- Find what a function calls (useful for debugging and refactoring)list_module_dependencies- Analyze module dependenciesget_function_source_code- Get complete function source with context (use instead ofgrep)
Phoenix Projects:
# Check that the MCP endpoint is responding
curl http://localhost:4000/codicil/mcp
# Check indexed functions
sqlite3 deps/codicil/priv/codicil.db "SELECT module, name, arity FROM functions LIMIT 10;"Non-Phoenix Projects:
# Check that the MCP server is responding
curl http://localhost:4700/codicil/mcp
# Check indexed functions
sqlite3 deps/codicil/priv/codicil.db "SELECT module, name, arity FROM functions LIMIT 10;"During compilation, Codicil:
- Captures module/function definitions via
Codicil.Tracer - Extracts documentation and relationships from bytecode
- Generates semantic summaries using LLMs (rate-limited, async)
- Creates vector embeddings for semantic search
- Stores everything in a local SQLite database
- Watches for file changes and automatically recompiles
Then your AI assistant queries this data through MCP tools.
Compilation → Tracer → ModuleTracer GenServer → RateLimiter → LLM/Embeddings
↓
SQLite Database
(functions, modules,
call graph, vectors)
↓
MCP Tools
↓
AI Assistant
Tech Stack:
- SQLite with
sqlite-vecextension for vector search - Ecto for database access
- Compiler tracers for automatic code analysis
- Multiple LLM providers (Anthropic, OpenAI, Cohere, Google, Grok)
- MCP server via Bandit + Plug
- FileSystem watcher for automatic recompilation
Find functions by semantic description using vector similarity search:
{
"description": "functions that validate email addresses",
"limit": 10
}Find what calls a specific function (useful for debugging and impact analysis during refactoring):
{
"moduleName": "MyApp.User",
"functionName": "create",
"arity": 1
}Find what a function calls (useful for debugging execution paths and refactoring):
{
"moduleName": "MyApp.Orders",
"functionName": "process",
"arity": 1
}Analyze module dependencies (imports, aliases, uses, requires, and runtime calls):
{
"moduleName": "MyApp.Accounts"
}Get complete function source code with module directives and location. Use this instead of grep or file reading for complete context.
{
"moduleName": "MyApp.User",
"functionName": "create",
"arity": 1
}You can override the default MCP tool descriptions at compile time to optimize how your AI assistant uses these tools. Different models (Claude, GPT-4, Codex, etc.) may have different preferences for how tools are described, so customizing descriptions can improve tool selection accuracy and reduce unnecessary tool calls.
To see the current default descriptions, use IEx:
iex -S mix
iex> Codicil.MCP.Tool.get_description(Codicil.MCP.Tools.FindSimilarFunctions)To customize descriptions, add to your config/config.exs (or config/dev.exs):
config :codicil, Codicil.MCP.Tools.FindSimilarFunctions, """
Find functions by semantic description. Use this when searching for functionality
by behavior rather than by name. Returns ranked results with code snippets.
"""
# Other configurable tool modules:
# - Codicil.MCP.Tools.ListFunctionCallers
# - Codicil.MCP.Tools.ListFunctionCallees
# - Codicil.MCP.Tools.ListModuleDependencies
# - Codicil.MCP.Tools.GetFunctionSourceCodeAfter changing tool descriptions, recompile:
mix clean
mix compileOverride default models:
export CODICIL_LLM_MODEL=claude-3-5-sonnet-20241022
export CODICIL_EMBEDDING_MODEL=voyage-3Use a different provider for embeddings:
export CODICIL_LLM_PROVIDER=anthropic
export CODICIL_EMBEDDING_PROVIDER=openai
export ANTHROPIC_API_KEY=your_claude_key
export OPENAI_API_KEY=your_openai_keyexport CODICIL_LLM_PROVIDER=openai
export OPENAI_API_KEY=dummy
export OPENAI_BASE_URL=http://localhost:11434/v1 # e.g., Ollama
export CODICIL_LLM_MODEL=llama3Solution:
- Verify environment variables are set:
echo $CODICIL_LLM_PROVIDER - Check that the MCP server is running:
curl http://localhost:4000/codicil/mcp(Phoenix) orcurl http://localhost:4700/codicil/mcp(non-Phoenix) - Force recompilation:
mix compile --force - Check logs for tracer errors
Solution:
- Initialize the database:
mix codicil.setup
Solution:
- Check what's using the port:
lsof -i :4700 - Kill the process or change the port in your Mix alias
Solution: Make sure you added the forward "/codicil/mcp", Codicil.Plug route to your Phoenix router and restarted the server.
Solution: Verify the tracer is enabled in your mix.exs:
defp elixirc_options(_env), do: [tracers: [Codicil.Tracer]]Force a clean recompile:
mix clean
mix compileContributing to Codicil? Here's how to set up the development environment:
# Clone the repository
git clone https://github.com/yourusername/codicil.git
cd codicil
# Install dependencies
mix deps.get
# Set up database
mix codicil.setup
# Run tests
mix test
# Format code
mix formatCodicil uses SQLite with the following tables:
- functions - Function definitions with summaries and embeddings
- modules - Module definitions
- function_calls - Call graph edges
- module_dependencies - Import/alias/use/require relationships
Vector search is powered by the sqlite-vec extension.
DO NOT deploy Codicil to production. Codicil is a development tool that:
- Makes LLM API calls (costs money)
- Indexes code at runtime (performance overhead)
- Runs an HTTP server (security surface)
Always include Codicil as a :dev only dependency:
{:codicil, "~> 0.6", only: [:dev, :test]}The tracer configuration shown above (elixirc_options(:prod), do: []) ensures Codicil is disabled in production builds.
Full documentation is available at:
- HexDocs: https://hexdocs.pm/codicil
- MCP Protocol Spec: https://spec.modelcontextprotocol.io
Codicil is inspired by and based on design patterns from GraphSense, a TypeScript/Node.js semantic code search tool. GraphSense pioneered the approach of combining vector similarity search with LLM validation for accurate code discovery.
MIT License - see LICENSE file for details.