vhdl-rag-mcp
Provides coding agents with hybrid semantic and exact-identifier search across VHDL source, documentation, and general code, with cross-referencing and exact source attribution.
README
vhdl-rag-mcp
An MCP (Model Context Protocol) server that gives coding agents high-quality semantic search over an organization's HDL code (VHDL, Verilog, SystemVerilog), HDL-related documentation, and general source code (C/C++, Python, ...) — all cross-referenced, all with exact source attribution.
Runs as an MCP server over stdio (installed from this Git
repository with uvx, see Installation). No
external services required: Qdrant runs embedded and the embedding
models run locally (ONNX via FastEmbed).
Intended use
The main uses are RAG and cross-referencing code against documentation, for coding agents (Claude Code, Maki, or any MCP client) that implement or modify HDL (VHDL, Verilog, or SystemVerilog).
RAG (Retrieval-Augmented Generation). RAG is a technique for
keeping a language model grounded in your material instead of only
its training data: before (or while) the model generates an answer, it
first retrieves relevant chunks from a knowledge base and uses those
as context. For a coding agent, that means the context it needs
usually lives outside the file it is editing — the company's coding
standards, design guides, and reference IP from earlier projects.
vhdl-rag-mcp is that retrieval layer: it maintains an up-to-date,
semantically searchable index of your repositories and hands the
agent the verbatim text (with exact repository, file, line range, and
commit) of every match, so the agent grounds its work in what the
organization actually wrote instead of hallucinating a plausible
pattern.
Cross-referencing code against documentation. This is what makes
the search more than three separate indexes: every chunk stores the
identifiers it defines or references (symbols), so the agent can
bridge the domains — and the HDL languages: a constant shared by a
SystemVerilog package, a Verilog module, and a VHDL entity is found
once and resolves to all of them. A standard that says
"asynchronous resets are named rst_n" can be checked against the
VHDL that actually uses rst_n and the C testbench that drives it; a
signal renamed in the RTL can be found in every doc section and test
function that still references the old name. In practice that means:
- Docs → code. Follow a convention from the standard to every VHDL construct and test function that implements it.
- Code → docs. Find the design rationale behind an implementation: given a process or function, which documentation section explains its convention.
- Consistency. Trace one identifier (e.g.
wr_ptr) across standard, RTL, and testbench so a rename or protocol change doesn't leave the domains out of sync.
Both uses rely on the index staying current: repositories are Git synced (branch-tracked or pinned to a tag/SHA) automatically in the background, so the context an agent retrieves reflects the code as it is, not a stale snapshot.
Capabilities
- Three indexed domains, one server. HDL source (VHDL, Verilog,
and SystemVerilog in one
hdlcollection, each chunk tagged with its language), documentation (Markdown/reST/text), and general code (C/C++, Python, ...) live in three Qdrant collections, each with a dense (jina v2) and a sparse (BM25) vector per chunk. - Hybrid search. Every query runs Qdrant's native hybrid
(dense + sparse, RRF-fused) query: semantic similarity and exact
identifier matching in one call. Ask about
rst_nand you get it. - HDL-aware chunking. VHDL files are chunked per construct
(entity, architecture, process, package, function, component) using
the vhdl_ls language server
(
documentSymbolwith exact line ranges); Verilog and SystemVerilog are chunked by Veridian (module/program/interface, package, inner functions and tasks, normalized to the same cross-language model — module →design_unit,always_ff→process— with the server-native kind kept asnative_symbol_kind). Both have a structural line-scanner fallback for files with syntax errors, and a whole-file last resort so no HDL is ever lost. - Structure-aware chunking elsewhere. Documentation is chunked per heading section; general code is chunked per top-level function/class by tree-sitter (any language with a grammar), with file-scope gap chunks for uncovered top-level code.
- Cross-referencing. Every chunk payload stores the identifiers it
defines or references (
symbols). Search tools accept asymbolsfilter that matches chunks referencing the given identifiers — bridging docs ↔ HDL ↔ test code (e.g. find every construct that touchesfifo_write), and across HDL languages (e.g.FIFO_DEPTHin a VHDL generic, a Verilog localparam, and an SV package constant). - Optional HDL analyzers, graceful degradation.
vhdl_lsand Veridian are external binaries that are not bundled or installed by this server: each is located via its config path or onPATH, and when one is missing its files simply fall back to structural/generic parsing.repository_statusreports each analyzer's availability, version, and mode (lsporfallback). - Exact source attribution. Every result names repository, file,
line range, and commit;
get_sourcereturns the exact current file (or a line range) from the synced working tree. - Incremental, self-maintaining index. Repositories are synced
from Git (clone/fetch/diff): only changed files are re-chunked and
re-embedded. A background task syncs every
sync_intervalseconds; the tools can force a sync or a full reindex at any time. - Graceful degradation. Failures are contained per repository and recorded in state; a broken repository never blocks the others or the server. A missing language-server binary degrades that analyzer to structural parsing (see above) instead of failing.
- Stdout is protocol-clean. All logging goes to stderr and a rotating log file, so the server is safe to run from any MCP host.
Installation
Requirements:
- uv (for
uvx), Python ≥ 3.12 - Git (with your normal credentials/SSH setup for private repos)
- Supported platforms: Linux with glibc ≥ 2.34 (RHEL 9/10 and derivatives such as AlmaLinux 9.6+, Ubuntu 24.04, Debian 12; x86_64 and arm64), Windows, and macOS 14+ (Apple Silicon and Intel). CI verifies all three OS families, including arm64 Linux, on Python 3.12–3.14.
vhdl_ls(only needed for repositories that contain VHDL): install a release from https://vhdl-lang.org/ sovhdl_lsis on yourPATH, or pointvhdl_ls_pathat the binary. Thevhdl_librariesdirectory shipped next to the binary is auto-detected. Per repository,vhdl_ls_hookmay generate thevhdl_ls.tomlworkspace config (below); otherwise the server writes a built-in default.- Veridian (only needed for repositories that contain Verilog or
SystemVerilog): install it so
veridianis on yourPATH, or pointveridian_pathat the binary. Per repository,veridian_hookmay generate theveridian.yamlworkspace config (below); otherwise the server writes a built-in default that declares the repository root as the workdir and include/source roots, so`include/`defineresolve in-tree. - Both binaries are optional: without one, its files are indexed with a structural/generic fallback instead.
The package is installed from this Git repository (it is not on PyPI):
$ uvx --from git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git vhdl-rag-mcp
uvx supports branch/tag pins in the same syntax:
git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git@v1.0. The server
accepts --help:
$ uvx --from git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git vhdl-rag-mcp --help
On first start the server creates its data directory, downloads the
embedding models (jina v2 base-code + base-en, ~tens of MB each,
once), and performs an initial sync of all configured repositories.
Configuration
Config file: ~/.config/vhdl-rag/config.toml (created with a
commented template on first run if absent).
data_dir = "~/.local/share/vhdl-rag" # all state lives here
sync_interval = 300 # seconds between periodic syncs
vhdl_ls_path = "vhdl_ls" # binary on PATH or full path (VHDL)
veridian_path = "veridian" # binary on PATH or full path (Verilog/SV)
log_level = "INFO"
# [qdrant]
# mode = "local" # embedded (default) — or "server" with url
# url = "http://qdrant:6333"
[[repositories]]
name = "company-standards" # unique, [A-Za-z0-9._-]
url = "git@github.com:company/vhdl-standards.git"
ref = "main" # branch (tracked on every sync),
# tag, or commit SHA (pinned)
# domains = ["hdl", "docs", "code"] # which domains to index (default: all)
# exclude = ["sim", "build/*", "*.log"] # glob path excludes ('*' crosses '/');
# wildcard-free patterns exclude the subtree
# vhdl_ls_hook = "make vhdl-ls-config" # command run at the repo root to
# generate vhdl_ls.toml when missing
# veridian_hook = "make veridian-config" # command to generate veridian.yaml
# ... or index your own active checkout instead of a remote:
[[repositories]]
name = "current-project"
path = "~/work/current-project" # local working repository
Notes:
- Config file selection: the default location is
~/.config/vhdl-rag/config.toml(a commented template is written there on first run). Select another file with theVHDL_RAG_MCP_CONFIGenvironment variable or the--config PATHflag. The top-level scalar options also have command-line overrides (--data-dir,--sync-interval,--vhdl-ls-path,--veridian-path,--log-level); the command line wins. urlorpath(exactly one):urlis a remote Git repository, cloned and kept in sync by the server underdata_dir/repos.pathis a local working repository — your own checkout, indexed in place and never modified (no clone, fetch, or checkout by the server).ref: a branch name is fetched and tracked on every sync. A tag or commit SHA pins the repository (a full 40-hex SHA skips the network fetch entirely).refis ignored for local working repositories.vhdl_ls_hook: shell command run at the repository root that generatesvhdl_ls.tomlwhen the file is missing (before thevhdl_lssession for that repository). When no hook is set, the hook fails, or it leaves no file behind, the server writes a built-in default (adefaultlibglob for all.vhd/.vhdlfiles plus the standard libraries shipped withvhdl_ls) and removes it after the session; files a hook creates are owned by the hook and are never removed by the server. For local working repositories the hook runs inside your own checkout.- Local working repositories index the working tree: HEAD plus
uncommitted changes (staged and unstaged) and untracked files
(honoring
.gitignore); chunks are attributed to the current HEAD commit. Deleting an untracked file is not tracked between syncs —reindexrepairs it. - Per-repository domains/excludes: index only what a repository
should contribute — e.g.
domains = ["hdl"]for a pure IP repository ("vhdl"is accepted as a legacy alias for"hdl"),exclude = ["sim"]to skip simulation-only files. - Changing embedding models changes the dense vector dimension;
the server fails loudly with an actionable message instead of
corrupting the index (delete the collection or
data_dirand reindex).
Usage
Run the server
$ uvx --from git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git vhdl-rag-mcp
It serves MCP over stdio until the host closes the connection; a
background task syncs all repositories every sync_interval seconds.
A single-instance lock (data_dir/server.lock) prevents two servers
from sharing one data directory.
Register with an MCP client
Claude Code:
$ claude mcp add vhdl-rag-mcp -- uvx --from git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git vhdl-rag-mcp
Maki (TOML config — verify the exact table names against your Maki version's docs):
[mcp_servers.vhdl_rag_mcp]
command = "uvx"
args = ["--from", "git+ssh://git@github.com/ru551n/vhdl-rag-mcp.git", "vhdl-rag-mcp"]
Tools
| Tool | What it does |
|---|---|
search_hdl(query, limit, repository, symbols, language) |
Hybrid search over HDL source (VHDL, Verilog, SystemVerilog): design units (entities/modules), architectures, processes/always blocks, packages, functions, tasks. language filters by HDL language. |
search_vhdl(query, limit, repository, symbols) |
search_hdl restricted to VHDL (back-compat name). |
search_docs(...) |
Same over documentation sections. |
search_code(...) |
Same over general code units (functions/classes). |
search_knowledge(query, limit, ...) |
All three domains at once, RRF-fused. |
get_source(repository, file, start_line, end_line) |
Exact current file content (or a slice) with commit attribution. |
repository_status() |
Per repository: ref, domains, last indexed commit, last sync, last error — plus the HDL analyzer status (vhdl_ls, Veridian: available, version, lsp/fallback mode). |
sync_repositories(repositories?) |
Incremental sync (default: all). Failures contained per repository. |
reindex_repository(repository) |
Drop and rebuild one repository's index. |
All search tools take an optional repository (name) filter plus
symbols: list[str] — restrict results to chunks referencing any of
the given identifiers. search_hdl/search_knowledge additionally
accept language (e.g. "verilog") to restrict results by language.
Results are rendered as markdown with source attribution, score,
language, and referenced identifiers; HDL content is fenced by
language.
Example agent flow:
search_knowledge("asynchronous reset conventions")→ a docs section plus VHDL and Verilog constructs that implement resets.search_hdl("reset", symbols=["rst_n"])→ every HDL chunk touchingrst_n, in every HDL language.search_hdl("fifo", language="systemverilog")→ only SystemVerilog.get_source("company-standards", "rtl/reset_ctrl.vhd", 12, 40)→ the exact lines to copy.
Operations
- Data directory (
data_dir): Qdrant collections, the per-repo Git working trees (<name>/), sync state (state/repositories.json), the log file (logs/vhdl-rag-mcp.log), and the lock file. Deleting it resets the index. - State & retries: a repository's
indexed_commitadvances only after its index update fully succeeded; a failed sync keeps the previous commit and the next sync retries the same diff.last_sync_erroris visible viarepository_status. - Removing a repository from the config: on the next start the server detects it in the state file and automatically drops all of its chunks and state.
- Logs:
stderr+logs/vhdl-rag-mcp.log(rotating, 3×5 MB).log_level = "DEBUG"for LSP/git/embedding detail.
Development
$ uv sync
$ uv run ruff format -q . && uv run ruff check . # format + lint
$ uv run mypy src # strict types
$ uv run pytest -q # offline test suite
The test suite runs fully offline: local file:// git remotes, fake
LSP server scripts (vhdl_ls and Veridian), and fake embedding
providers (real-binary tests are gated on the VHDL_LS_TEST_BIN and
VERIDIAN_TEST_BIN environment variables).
CI (.github/workflows/ci.yml) runs on every push to main and on
pull requests: ruff format --check, ruff check, mypy (strict),
and the full test suite on a matrix of Python 3.12/3.13/3.14 across
Ubuntu, Windows, and macOS, plus RHEL 9 and RHEL 10 container jobs
(official UBI images; UBI 9's glibc 2.34 is the strictest floor in
the dependency wheel set), and linux/arm64 jobs that run the suite
in manylinux aarch64 containers (glibc 2.34 and 2.39 floors,
mirroring the RHEL jobs) under QEMU user-mode emulation (no hosted
arm64 runners; this verifies the aarch64 wheels and execution on the
architecture).
Layout:
src/vhdl_rag_mcp/
config.py typed config (pydantic) + default template
state.py atomic repository sync state (schema-versioned)
git_manager.py async clone/fetch/checkout + incremental SyncPlan
routing.py extension -> domain classification (+domains/excludes)
lsp/ LSP transport (server-agnostic) + vhdl_ls and Veridian
adapters + analyzer discovery/status
embeddings/ FastEmbed dense/sparse providers (per-collection + shared)
vector_store.py Qdrant wrapper: hybrid RRF query, payload filters
indexing/ vhdl (vhdl_ls), verilog (Veridian), docs (sections),
code (tree-sitter), pipeline (incremental sync driver)
retrieval.py search service: fusion, language filter, source access
server.py FastMCP tools + startup + periodic sync + lock
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.