monorepoly
Org-wide index of code and engineering activity for LLM agents.
Install
- Users of a hosted server:
uv tool install monorepoly(orpip install monorepoly), thenmonorepoly setup --server_url <url> --client_id <id>. Nothing else is installed. - Running the server yourself:
monorepoly[server]; running the indexer:monorepoly[indexer]. A command whose extra is missing says which one to install. - Images: the Dockerfile's
servertarget runsserve_http, itsindexertarget (the default) the indexing commands.
Setup
docker compose up -d(Atlas Local, for tests and local runs)- Set
GITLAB_GROUP_ID(a group path or numeric id),GITLAB_TOKEN,GITLAB_URL,GOOGLE_CLOUD_PROJECT,MONGODB_DB,MONGODB_URI,TYPESAFE_API_KEY gcloud auth application-default login
Use
-
Index every project in the group:
uv run monorepoly index_org(archived projects are skipped; add--include_archivedto index them too) -
Index one project's code, commits and branches:
uv run monorepoly index_project group/project -
Sync one project's issues, merge requests and discussions:
uv run monorepoly sync_activity group/project -
--debugskips every database write, but still makes the paid Vertex AI and Jev calls. -
Set up Claude Code:
monorepoly setupprompts for the settings, checks GitLab, MongoDB and Google credentials, installs the skill and registers the server (--debugchanges nothing and prints theclaude mcp addcommand with secrets masked) -
Or register by hand with your own GitLab token, passing every setting with
-e(see Security model):claude mcp add monorepoly -e GITLAB_GROUP_ID=... -e GITLAB_TOKEN=... -e GITLAB_URL=... \ -e GOOGLE_CLOUD_PROJECT=... -e MONGODB_DB=... -e MONGODB_URI=... -e TYPESAFE_API_KEY=... \ -- uv run --directory <this repo> monorepoly serve -
Skill:
monorepoly install-skillcopies it to~/.claude/skills/; rerun after upgrading -
Connect to a hosted server (see Self-host on Google Cloud):
monorepoly setup --server_url <url> --client_id <GitLab OAuth application id>; Claude Code opens GitLab in the browser on first use
Index from CI
The image runs the CLI; its entrypoint is the monorepoly command.
docker build --target indexer -t registry.gitlab.com/<group>/monorepoly:latest .
docker push registry.gitlab.com/<group>/monorepoly:latest
The job authenticates to Vertex AI with Workload Identity Federation: GitLab issues the job an OIDC token, and GCP exchanges it for a service account. No key is stored anywhere.
One-time GCP setup:
-
Create a workload identity pool and an OIDC provider for
https://gitlab.com, with the attribute mappinggoogle.subject=assertion.sub,attribute.namespace_path=assertion.namespace_pathand the attribute conditionassertion.namespace_path.startsWith('<group>'), so only your group's pipelines can use it. -
Grant a service account
roles/aiplatform.user, and grant the pool's principalsroles/iam.workloadIdentityUseron that service account. -
Generate the credential config (it holds no secret):
gcloud iam workload-identity-pools create-cred-config \ projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider> \ --service-account=<sa>@<project>.iam.gserviceaccount.com \ --credential-source-file=/tmp/gcp-id-token --output-file=gcp-credentials.jsonand store its content in the CI variable
GCP_CREDENTIAL_CONFIG.
CI variables (masked): GITLAB_GROUP_ID, GITLAB_TOKEN (a group access token with
read_api and read_repository), GITLAB_URL, GOOGLE_CLOUD_PROJECT, MONGODB_DB,
MONGODB_URI, TYPESAFE_API_KEY.
monorepoly:
image:
name: registry.gitlab.com/<group>/monorepoly:latest
entrypoint: [""]
id_tokens:
GCP_ID_TOKEN:
aud: https://iam.googleapis.com/projects/<number>/locations/global/workloadIdentityPools/<pool>/providers/<provider>
variables:
GOOGLE_APPLICATION_CREDENTIALS: /tmp/gcp-credentials.json
script:
- echo "$GCP_ID_TOKEN" > /tmp/gcp-id-token
- echo "$GCP_CREDENTIAL_CONFIG" > "$GOOGLE_APPLICATION_CREDENTIALS"
- monorepoly index_project "$CI_PROJECT_PATH"
- monorepoly sync_activity "$CI_PROJECT_PATH"
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
A CI runner keeps no clone cache, so every run clones the repository again; only commits new since the last run reach Jev and Vertex AI.
Self-host on Google Cloud
deploy/ is a Pulumi program that runs the MCP server as a Cloud Run service and index_org as
a Cloud Run job, with every credential in Secret Manager. Engineers connect over HTTP and log in
with GitLab; their laptops hold no MongoDB, Vertex AI or TypeSafe credentials.
You need: a GCP project, gcloud auth application-default login, Pulumi and an Atlas cluster.
The images come from ghcr.io, built by GitHub Actions on every push to master and tagged with
the commit; a deploy runs the images of the checkout's HEAD, so push and wait for the
images workflow before pulumi up.
-
In GitLab, create an OAuth application (group or instance level): scope
read_api, redirect URIhttp://localhost:41593/callback, Confidential unchecked (Claude Code uses PKCE, so there is no secret). Its application id is public; you hand it to users. -
In Atlas, create two database users for the index database: read-only for the server, read-write for the indexer.
-
Create a group access token with
read_apiandread_repositoryfor the indexer. -
Configure and deploy, keeping the state in your own project:
cd deploy pulumi login gs://<state bucket> pulumi stack init prod pulumi config set gcp:project <project> pulumi config set gcp:region <region> pulumi config set gitlabGroupId <group path or id> pulumi config set mongodbDb <database> pulumi config set --secret gitlabToken <group access token> pulumi config set --secret mongodbUriReadonly <read-only URI> pulumi config set --secret mongodbUriReadwrite <read-write URI> pulumi config set --secret typesafeApiKey <key> pulumi upOptional:
gitlabUrl(defaulthttps://gitlab.com),schedule(default hourly,0 * * * *),indexerTimeout(default3300s; keep it below the schedule's interval, so runs never overlap),indexerMemory(default4Gi; every run clones all repositories into memory),license(see License),egressSubnetwork(sends all traffic through that subnet, e.g. one whose Cloud NAT has a static IP on the Atlas IP access list),schedulerRegion(defaultgcp:region; Cloud Scheduler is missing from some regions, e.g.europe-north1),imageTag(default the checkout's commit),imageRepository(defaultkimvais/monorepoly). -
Run the first full index by hand; it can take hours:
gcloud run jobs execute monorepoly-index --region <region> --task-timeout=24h --wait -
Start the schedule:
pulumi config set scheduleEnabled true && pulumi up -
Give users the
setup_commandoutput; setoauthClientIdto your OAuth application id to have it filled in.
Every user sees only what their own GitLab account can see, as with a local server.
License
Free for public repositories and open-source projects; private repositories and commercial use need a commercial license (terms: LICENSE).
Public projects are indexed without a license. Private and internal projects need a license file covering their group; without one the indexer skips them and stores nothing about them, not even their names.
Give the indexer the file's content in MONOREPOLY_LICENSE. On Google Cloud:
pulumi config set --secret license -- "$(cat monorepoly.license)" && pulumi up.
A license file is TOML followed by an SSH signature over exactly the bytes above it. To issue
one, write the payload, with key set to the fingerprint ssh-keygen -l -f <key>.pub prints:
licensee = "Acme Oy"
gitlab_url = "https://gitlab.com"
groups = ["acme"]
expires = 2027-10-16
key = "SHA256:L2Z9F/e49TkoYH8+dWjsfpjJvuyJgDnkFLOOVlJDtpQ"
then sign it with a key whose .pub file is in monorepoly/trusted_keys/, and append the
signature:
ssh-keygen -Y sign -f ~/.ssh/id_ed25519 -n monorepoly-license acme.toml
cat acme.toml acme.toml.sig > monorepoly.license
The file must keep LF line endings: any changed byte breaks the signature. groups are full
paths and include their subgroups; expires is the last valid day.
Security model
- The server filters every query by what your GitLab token can see: projects, per-feature access
(repository, merge requests, issues) and confidential items. This protects against accidental
exposure, not against anyone holding
MONGODB_URI: the database holds the whole group's index, and the URI reads all of it directly. - Give the server a read-only Atlas database user; only the indexer needs write access.
- The hosted server is public at the network level on purpose: it checks every request's GitLab
token with GitLab, and accepts any of the user's tokens with
read_api, which already reads everything the index would show them. - Distribute the server settings only to people entitled to read the whole group.
- Register the settings with
claude mcp add -e KEY=VALUE ...rather than in your shell profile, so an agent's shell commands cannot read them.
Test
uv run pytest (needs Atlas Local). Live checks: uv run pytest -m live.
Metadata
Release files for monorepoly 0.2.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| monorepoly-0.2.0.tar.gz | 52.6 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| monorepoly-0.2.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 114.5 kB
Release files / monorepoly-0.2.0.tar.gz
| Download URL | monorepoly-0.2.0.tar.gz |
|---|---|
| Size | 52.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
f3c239424f9af284d343b5fe482878b090bf4a3455af83d61b495c727e69819a
|
|
BLAKE2b-256 checksum How to use checksums |
b6d62c6b543a6d1a18dda415536b8c563572ec2e6f5658cbff43e7b738a43f9f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / monorepoly-0.2.0-py3-none-any.whl
| Download URL | monorepoly-0.2.0-py3-none-any.whl |
|---|---|
| Size | 61.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
ec2fe368a371b135fa673ce8596a965b4286782b686bcc86de3fcc52fa8b157f
|
|
BLAKE2b-256 checksum How to use checksums |
0c8c30eccdf5926378f8504fe479ed980b7a1af797337111377c2b3a290c75c8
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|