Collects Odoo module metadata from GitHub/GitLab organizations, visualizes it on a web dashboard, and serves it to LLM clients over MCP.
The project is a Rust workspace made of three services that share a single SQLite database:
| Service | Binary | Role |
|---|---|---|
| OGHCollector | oghcollector |
CLI that clones repositories, parses Odoo __manifest__.py files and writes the results to the database. It is the only component allowed to write. |
| OGHServer | oghserver |
actix-web dashboard that reads the database in read-only mode (module search, dependency graph, migration tracking, committer stats, etc). |
| OGHMcp | oghmcp |
Read-only MCP server exposing module search and code-analysis data as tools, over a Streamable HTTP endpoint (/mcp), so any LLM client can reach it by URL. |
All three ship in the same Docker image and the project is designed to be run with Docker Compose.
- Docker and the Docker Compose plugin.
- A GitHub and/or GitLab personal access token so the collector can query the API:
- Creating a GitHub personal access token
- A personal access token for your GitLab instance, if you also collect from GitLab.
git clone git@github.com:Tardo/OGHCollector.git
cd OGHCollector
# 1. Build the images
docker compose build
# 2. Populate the database at least once — this also applies any pending Diesel
# migrations automatically, see "Database & Migrations" below
docker compose run --rm -u appuser -T app oghcollector OCA 18.0
# 3. Start the dashboard (:8080) and the MCP endpoint (:8081)
docker compose upThe dashboard is now available at http://localhost:8080 and the MCP endpoint at http://localhost:8081/mcp.
oghserverandoghmcpopen the database read-only. If it doesn't exist yet, they will start anyway but won't serve any data untiloghcollectorhas run at least once.
The schema is managed with Diesel; migration files live in migrations/.
Migrations are compiled straight into the oghcollector binary via diesel_migrations::embed_migrations!
and are applied automatically, in order, every time the collector runs. There is nothing to run by hand:
running the collector once creates the database file, and every later run — including after you git pull
a version of this project that adds new migrations — brings the schema up to date before it does anything else.
docker compose run --rm -u appuser -T app oghcollector OCA 18.0If you're running the crates directly with cargo run instead of Docker, install the Diesel CLI and use
the DATABASE_URL already defined in .env:
cargo install diesel_cli --no-default-features --features sqlite-bundled
diesel migration run # apply pending migrations
diesel migration revert # undo the last migration
diesel migration list # show applied/pending migrations
crates/sqlitedb/src/schema.rsis manually maintained, not regenerated fromdiesel print-schema. Diesel infers SQLiteINTEGER PRIMARY KEYcolumns asNullable<Integer>, but every id in this project is a non-nulli64/BigInt. If you rundiesel print-schema, diff it by hand and port over only the newtable!block(s) — never overwriteschema.rswith the raw output.
See docs/development.md for the full non-Docker development setup.
Mount a volume to /app/server.yaml (JSON is also supported) to override the defaults:
| Name | Type | Description | Default |
|---|---|---|---|
bind_address |
string | Address to bind the server on | 0.0.0.0 |
port |
int | Port to bind the server on | 8080 |
workers |
int | Number of worker processes | 2 |
template_autoreload |
bool | Reload templates automatically when they change | false |
static_autoreload |
bool | Reload static files automatically when they change | false |
allowed_origins |
list of strings | Allowed CORS origins | [] |
timezone |
string | Timezone used for display | UTC |
cookie_key |
string | Key used to sign session cookies | |
upload_limit |
int | Maximum upload size, in bytes | 2097152 |
cache_ttl |
int | Seconds a cache entry stays valid | 3600 |
db_pool_max_size |
int | Maximum number of pooled DB connections | 15 |
mcp_info_enabled |
bool | Show the /mcp page explaining how to connect popular LLM clients to the MCP endpoint, and its nav link |
false |
mcp_url |
string | Public URL of the MCP endpoint, displayed on that page | http://localhost:8081/mcp |
# docker-compose.override.yaml
services:
app:
volumes:
- ./server.yaml:/app/server.yamldocker compose run --rm -u appuser -T app oghcollector <origin> <version> [git_type]<origin>:- The name of an organization — all its repositories are scanned.
- The name of a repository, optionally followed by
:and a comma-separated list of folders to scan.
<version>: Odoo version to collect (e.g.18.0).[git_type]: Optional git client to use,GH(GitHub, default) orGL:<api_url>(GitLab).
# Odoo core modules, 18.0 (GitHub)
docker compose run --rm -u appuser -T app oghcollector odoo/odoo:/addons,/odoo/addons 18.0
# OCA/web modules, 18.0 (GitHub)
docker compose run --rm -u appuser -T app oghcollector OCA/web 18.0
# All OCA modules, 18.0 (GitHub)
docker compose run --rm -u appuser -T app oghcollector OCA 18.0
# All MyGroup modules, 18.0 (self-hosted GitLab)
docker compose run --rm -u appuser -T app oghcollector MyGroup 18.0 GL:https://mygitlabinstance.com/api/v4/If you run this behind Traefik, you may need to add
-l traefik.enable=falseso the one-off container isn't picked up as a routable service.
The recommended way to provide API tokens is through Docker secrets, so they never end up in
docker-compose.yaml or shell history. The collector automatically reads /run/secrets/gh_token and
/run/secrets/gl_token if present:
# docker-compose.override.yaml
services:
app:
secrets:
- gh_token
- gl_token
secrets:
gh_token:
file: ./gh_token.txt
gl_token:
file: ./gl_token.txtMake sure each secret file contains a single line with no extra trailing newline added by your editor
(e.g. nano -L gh_token.txt).
Alternatively, without secrets, set OGHCOLLECTOR_TOKEN_GH / OGHCOLLECTOR_TOKEN_GL as environment
variables (used as a fallback when the corresponding secret file isn't found).
To refresh the database periodically, add a cron job on the host that invokes update_db.sh,
which loops over every supported Odoo/OpenERP version for odoo/odoo and OCA:
0 */6 * * * cd /path/to/OGHCollector && ./update_db.shStreamable HTTP MCP endpoint exposing search_modules and get_module tools over the same category of
data as oghserver's /api/v1/* routes.
Mount a volume to /app/mcp.yaml (JSON is also supported) to override the defaults. Every key can also be
set via an OGHCOLLECTOR_MCP_-prefixed environment variable (e.g. OGHCOLLECTOR_MCP_CACHE_TTL), which
takes precedence over the file:
| Name | Type | Description | Default |
|---|---|---|---|
cache_ttl |
int | Seconds the get_module result cache stays valid |
3600 |
# docker-compose.override.yaml
services:
mcp:
volumes:
- ./mcp.yaml:/app/mcp.yamlBy default the MCP server only accepts requests whose Host header is localhost, 127.0.0.1 or ::1
(DNS-rebinding protection). For any real deployment, set OGHCOLLECTOR_MCP_ALLOWED_HOSTS to the
hostname(s)/IP(s) your clients actually connect to:
# docker-compose.override.yaml
services:
mcp:
environment:
OGHCOLLECTOR_MCP_ALLOWED_HOSTS: mcp.example.com,203.0.113.10
oghmcphas no authentication of its own. Ifoghserversits behind auth or a private network, giveoghmcpthe same treatment.
| Variable | Used by | Purpose |
|---|---|---|
OGHCOLLECTOR_TOKEN_GH |
collector | GitHub API token (fallback if the gh_token Docker secret isn't set) |
OGHCOLLECTOR_TOKEN_GL |
collector | GitLab API token (fallback if the gl_token Docker secret isn't set) |
DATABASE_URL |
Diesel CLI | SQLite connection string (local, non-Docker development only) |
OGHCOLLECTOR_DB_PATH |
mcp | Path to the SQLite database (default data/data.db) |
OGHCOLLECTOR_MCP_BIND_ADDR |
mcp | HTTP bind address (default 0.0.0.0:8081) |
OGHCOLLECTOR_MCP_ALLOWED_HOSTS |
mcp | Comma-separated Host header allowlist (default localhost,127.0.0.1,::1) |
OGHCOLLECTOR_MCP_CACHE_TTL |
mcp | Overrides cache_ttl from mcp.yaml (default 3600) |
RUST_LOG |
all three binaries | Log level (default: info) |
docker-compose.yaml is meant to stay untouched; layer your own settings (volumes, env vars, ports) on
top of it with a docker-compose.override.yaml file, which Docker Compose picks up automatically.
See docs/development.md for running the crates and the frontend build directly
with Cargo/pnpm, outside of Docker.
Distributed under the terms of the GNU AGPLv3.