Codicil
View SourceSemantic 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.
What does it do?
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
Key Features
- 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
Installation & Setup
Common Steps (All Projects)
These steps apply to both Phoenix and non-Phoenix projects.
Step 1: Add Dependencies
Add Codicil to your mix.exs:
def deps do
[
# ... your existing dependencies
{:codicil, "~> 0.4", only: [:dev, :test]}
]
endThen install:
mix deps.get
Step 2: Initialize the Database
Codicil uses SQLite to store indexed code. Initialize it:
mix codicil.setup
Step 3: Configure Environment Variables
Create 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_here
Load the environment variables:
source .env
Step 4: Enable the Compiler Tracer
Edit 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.
Phoenix Project Setup
Continue with these steps if you're using Phoenix.
Step 5 (Phoenix): Add MCP Route to Router
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
endStep 6 (Phoenix): Start Phoenix and Compile
Start your Phoenix server:
mix phx.server
The 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 --force
Watch the Phoenix logs to see functions being indexed!
Non-Phoenix Project Setup
Continue with these steps if you're NOT using Phoenix.
Step 5 (Non-Phoenix): Add Bandit Dependency
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.get
Step 6 (Non-Phoenix): Add Mix Alias for MCP Server
Add 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)'"
]
endStep 6a (Non-Phoenix, Optional): Combining Multiple MCP Servers
If 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.
Step 7 (Non-Phoenix): Start MCP Server and Compile
Start the MCP server:
mix codicil
The MCP server will start on http://localhost:4700/codicil/mcp.
In a second terminal, compile your project to trigger indexing:
mix compile --force
Watch the logs in Terminal 1 to see functions being indexed!
Configure Your AI Assistant
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)
Verify It's Working
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;"
How It Works
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.
Architecture
Compilation → Tracer → ModuleTracer GenServer → RateLimiter → LLM/Embeddings
↓
SQLite Database
(functions, modules,
call graph, vectors)
↓
MCP Tools
↓
AI AssistantTech 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
MCP Tool Reference
find_similar_functions
Find functions by semantic description using vector similarity search:
{
"description": "functions that validate email addresses",
"limit": 10
}list_function_callers
Find what calls a specific function (useful for debugging and impact analysis during refactoring):
{
"moduleName": "MyApp.User",
"functionName": "create",
"arity": 1
}list_function_callees
Find what a function calls (useful for debugging execution paths and refactoring):
{
"moduleName": "MyApp.Orders",
"functionName": "process",
"arity": 1
}list_module_dependencies
Analyze module dependencies (imports, aliases, uses, requires, and runtime calls):
{
"moduleName": "MyApp.Accounts"
}get_function_source_code
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
}Advanced Configuration
Customizing Tool Descriptions
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 compile
Custom Models
Override default models:
export CODICIL_LLM_MODEL=claude-3-5-sonnet-20241022
export CODICIL_EMBEDDING_MODEL=voyage-3
Separate Embedding Provider
Use 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_key
Local LLM Support (OpenAI-compatible)
export CODICIL_LLM_PROVIDER=openai
export OPENAI_API_KEY=dummy
export OPENAI_BASE_URL=http://localhost:11434/v1 # e.g., Ollama
export CODICIL_LLM_MODEL=llama3
Troubleshooting
"Functions not being indexed"
Solution:
- 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
"Database not found"
Solution:
- Initialize the database:
mix codicil.setup
"Port 4700 already in use" (Non-Phoenix)
Solution:
- Check what's using the port:
lsof -i :4700 - Kill the process or change the port in your Mix alias
"Route not found" (Phoenix)
Solution: Make sure you added the forward "/codicil/mcp", Codicil.Plug route to your Phoenix router and restarted the server.
Tracer Not Running
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 compile
Development
Contributing 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 format
Database Schema
Codicil 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.
Production Warning
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.7", only: [:dev, :test]}The tracer configuration shown above (elixirc_options(:prod), do: []) ensures Codicil is disabled in production builds.
Documentation
Full documentation is available at:
- HexDocs: https://hexdocs.pm/codicil
- MCP Protocol Spec: https://spec.modelcontextprotocol.io
Acknowledgments
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.
License
MIT License - see LICENSE file for details.