This repository builds and publishes the container images used by the Obot MCP
catalog. Image publication is coordinated by GitHub Actions and
scripts/image_plan.py.
Images are divided into three families:
- Repackages are configured in
repackaging/images.yaml. Node and Python packages are installed into an application base and then wrapped with MMMCP. Docker repackages use an upstream image directly. - Repository images are versioned images configured in
repository-images.yaml, such as GitHub and Tableau. - Utilities are unversioned images configured in
repository-images.yaml, such as the STDIO wrapper and HTTP webhook converter.
The OpenAPI MCP wrapper is a utility image that serves stored OpenAPI snapshots using FastMCP, with request-scoped credentials and direct tools for API operations.
Node repackages can specify an npm_overrides mapping of dependency names to
versions when upstream dependencies need security patches. Python repackages
use constraints and overrides lists. A repackage can also specify a
dockerfile for image-specific fixes; changes to that file select its image
for rebuilding.
Versioned images are published as {version}-obotN, where N is an
auto-incrementing revision. Before a build, the planner lists the repository's
existing tags, finds the highest exact revision for that version, and allocates
the next number. A new version starts at obot1.
For example, if 1.2.3-obot1 and 1.2.3-obot2 exist, the next build publishes
1.2.3-obot3. Existing revision tags are never intentionally overwritten, and
the workflow checks again that the selected tag is absent before publishing.
There is no moving 1.2.3 or 1.2.3-main alias; catalog updates point directly
to the immutable revision.
Revision allocation answers where to publish, not whether the image contents changed. Any triggered build of a versioned image receives the next revision.
On a push to main, the coordinator compares the previous and current Git
revisions. An image is selected when its manifest entry or an owned path changes.
Shared build inputs select their known dependents:
- Node base or Node repackage changes select Node repackages.
- Python base or Python repackage changes select Python repackages.
- MMMCP wrapper changes select Node and Python repackages.
- Docker repackage changes select Docker repackages.
- Common planner, publisher, or Docker context changes select all applicable images.
An unrelated change selects no images. Changing only one manifest entry, such as DuckDuckGo, selects only that image.
Pull requests use the same selector and write a read-only table of proposed immutable tags to the Actions job summary. The preview does not reserve those tags, so the final revision may change before merge.
All image builds use native runners: ubuntu-24.04 for AMD64 and
ubuntu-24.04-arm for ARM64. Each architecture pushes its image by digest;
after both builds succeed, the publisher combines them into a manifest and
applies the published tags. Signing, SBOM generation, scanning, and catalog
updates run against the combined image.
Repackages, repository images, and utilities are planned once before the architecture jobs start, so both builds share the same revision and resolved parent digests. Node and Python repackages build their application base and MMMCP wrapper on the same native runner, with the wrapper pinned to that architecture's application base digest. The mutable application base tag is also published as a combined manifest.
Builds resolve configurable parent images to digests before publication. This keeps each individual build on one exact parent even when its configured tag is mutable.
The shared Node and Python bases use cgr.dev/chainguard/wolfi-base:latest
directly. The root MMMCP_IMAGE file is the single source for the complete
MMMCP image reference used by CI. The planner passes that reference to every
MMMCP consumer without resolving it to a digest. Their Dockerfiles default to
mmmcp:latest for direct builds.
The root UV_IMAGE file works the same way for the shared Python base and STDIO
wrapper; both Dockerfiles default to uv:latest.
Repository-image parent versions are similarly defined in
repository-images.yaml; their Dockerfile arguments use moving defaults such
as latest or node:alpine and CI overrides them with digest-pinned manifest
references.
The OpenAPI wrapper uses Wolfi with Python 3.12 in matching build and runtime stages. Its locked application environment is copied into the runtime without the uv build tool. Shared MMMCP images also upgrade distribution packages when assembling the final application image.
GitHub and STDIO wrapper builds remove inherited exact OS-package version pins before upgrading packages, so the pinned MMMCP release does not block security updates.
Grafana upgrades the upstream Alpine runtime packages and keeps usage statistics disabled. Its v2 release removes the deprecated Sift tools and splits the alerting management tools into read and write tools. Request-scoped authentication headers require Streamable HTTP, which is already used by the catalog configuration.
The Publish MCP Images workflow can be dispatched from main in one of two
modes:
single-repackagerebuilds one named repackage.all-mmmcp-consumersrebuilds every Node and Python repackage and every repository or utility Dockerfile with anMMMCP_IMAGEargument.
A manual run intentionally rebuilds the selected images and allocates their next immutable revisions. Changing the configured MMMCP version automatically selects all consumers; the all-consumers mode can rebuild them without a change.
Manual runs do not compare parent digests with the previous revision and do not skip unchanged builds. Single-image targeting applies only to repackages; the MMMCP mode also includes matching repository images and utilities.
Utilities normally publish moving branch tags. A repository v* tag publishes
that exact immutable utility tag; a non-release-candidate tag also updates
latest. Retrying the same source revision reuses its existing release tag.
Published images are signed, receive an SBOM, and are scanned with Trivy. Changes to the scanning workflows or security policy trigger scan-only jobs. These jobs resolve the highest existing revision and do not build an image or allocate a new revision.
.github/workflows/build.ymltests the planner, validates Dockerfiles, and previews affected revisions on pull requests..github/workflows/publish-images.ymlselects affected images and coordinates base builds, publication, and rescans..github/workflows/publish-image.ymlplans, builds, signs, scans, and updates the catalog for selected images..github/workflows/base-images.ymlpublishes the shared Node and Python base images..github/workflows/scan-images.ymland.github/workflows/scan-base-images.ymlperform scan-only runs.