Semantic search over Modelica libraries via a local RAG (Retrieval-Augmented Generation) pipeline. Parses Modelica source files using OMParser.jl, embeds each class with a local embedding model, stores the index in SQLite, and exposes search through an MCP server that Claude Code can query.
- Parse —
OMParser.jlparses each.mofile into an Absyn AST. The walker extracts every non-package class (model, function, record, block, connector, type, ...) as a chunk with its fully qualified name (e.g.Modelica.Electrical.Analog.Basic.Resistor) and source lines. - Embed — chunks are sent to an embedding backend (GitHub Models, Ollama, or llama-server). Only files that changed since the last run are re-indexed.
- Store — embeddings are stored as binary blobs in SQLite alongside chunk metadata. Cosine similarity search is computed in Julia at query time.
- Serve — an MCP stdio server exposes three tools:
search_codebase,lookup_symbol, andrebuild_index.
- Julia 1.9+
- OMParser.jl (from the OM.jl monorepo)
- One of:
- A GitHub personal access token — uses the GitHub Models free embedding API (no local server required)
- Ollama with an embedding model (e.g.
nomic-embed-text,mxbai-embed-large) - llama.cpp
llama-serverwith a GGUF embedding model
import Pkg
# Develop the OM.jl local dependencies (adjust path as needed)
Pkg.develop(path="/path/to/OM.jl/OMParser.jl")
# Develop this package — runs setup.jl automatically via deps/build.jl
Pkg.develop(path="/path/to/ModelicaRag.jl")setup.jl detects Ollama and llama-server on your system and writes a config.toml. If the Modelica library path is not found automatically, edit config.toml and set [codebase] root.
You can also run setup manually at any time (it will not overwrite an existing config.toml):
julia setup.jl
config.toml is machine-specific and not tracked by git. Copy the provided example and edit the paths:
cp config.toml.example config.toml
Then open config.toml and fill in the paths for your system. The key fields:
Three backends are supported. Choose one in config.toml:
GitHub Models (recommended — no local server required):
[embeddings]
backend = "github_models"
model = "text-embedding-3-small" # or "text-embedding-3-large"
batch_size = 32Set your token in the environment before running. Do not put it in config.toml:
export GITHUB_TOKEN=github_pat_...
GitHub Models is free for all GitHub users: 150 embedding requests/day per token.
Ollama:
[embeddings]
backend = "ollama"
url = "http://localhost:11434"
model = "nomic-embed-text"
batch_size = 32Pull the model first: ollama pull nomic-embed-text
llama-server:
[embeddings]
backend = "llama"
url = "http://localhost:8080"
batch_size = 32
[server]
llama_server = "/path/to/llama.cpp/build/bin/llama-server"
model_path = "/path/to/models/some-embedding-model.gguf"Julia starts llama-server automatically when this backend is selected.
The remaining fields are the same for all backends:
[store]
path = "data/index.db"
[codebase]
root = "/path/to/Modelica/library"
extensions = [".mo"]The quickest way to see the tool in action is the included playground script. It indexes the Modelica Standard Library and runs a set of example searches, lookups, and fuzzy queries.
export GITHUB_TOKEN=github_pat_...
julia --project playground.jl
# or pass the library path explicitly:
julia --project playground.jl /usr/share/openmodelica/libraries/Modelica\ 4.0.0The playground writes its own index to data/playground.db (separate from the main index). Subsequent runs are incremental — only changed files are re-embedded, so re-runs are fast. The four semantic searches use four API requests from the free tier (150/day).
No local installation needed — try it in a Codespace:
Click "Code > Open with Codespaces" on the repository page. The devcontainer installs Julia, clones the Modelica Standard Library into data/msl/, and runs Pkg.instantiate() automatically. GITHUB_TOKEN is provided by Codespaces, so the playground works with no extra configuration:
julia --project playground.jlusing ModelicaRag
# Build or update the index (incremental by default)
ModelicaRag.main(["index"])
# Force a full rebuild
ModelicaRag.main(["index", "--force"])
# Search from the REPL
ModelicaRag.main(["search", "thermal resistor", "--top-k", "5"])
# Find symbols by partial name (no embedding cost)
ModelicaRag.main(["fuzzy", "HeatTransfer"])
ModelicaRag.main(["fuzzy", "sin", "--top-k", "20"])
# Start the MCP stdio server
ModelicaRag.main(["serve"])A custom config path can be passed with --config path/to/config.toml.
Add .mcp.json to your Claude Code MCP settings, or symlink the included .mcp.json into a project:
{
"mcpServers": {
"modelica-rag": {
"command": "julia",
"args": [
"--project=/path/to/ModelicaRag.jl",
"-e",
"using ModelicaRag; ModelicaRag.main([\"serve\"])"
]
}
}
}Once connected, four tools are available:
| Tool | Description |
|---|---|
search_codebase |
Semantic search over indexed Modelica classes |
lookup_symbol |
Exact lookup by qualified name (case-insensitive) |
fuzzy_lookup |
Substring match on symbol name — finds all classes whose name contains the pattern |
rebuild_index |
Incremental or full index rebuild |
ModelicaRag.jl/
├── Project.toml
├── config.toml.example # template — copy to config.toml and fill in paths
├── setup.jl # auto-detects backend and library, writes config.toml
├── deps/build.jl # local dev only: Pkg.develop local copies of OM packages
├── data/ # SQLite index (created on first run)
└── src/
├── ModelicaRag.jl # package entry point
├── Parser.jl # Absyn AST walker
├── Embedder.jl # GitHub Models, Ollama, and llama-server backends
├── Store.jl # SQLite storage and cosine similarity search
├── MCP.jl # MCP stdio server
└── CLI.jl # index / serve / search / fuzzy commands