Packaging¶
This page describes the packages produced for each platform, their current status, and how the Homebrew formula is generated from a release.
Distribution matrix¶
| Platform | Homebrew | .deb | JAR (direct) | Container image | Scoop | Chocolatey |
|---|---|---|---|---|---|---|
| macOS (aarch64) | beta* | — | ✓ v0.1.0 | ✓ v0.1.0 | — | — |
| macOS (x86_64) | beta* | — | ✓ v0.1.0 | ✓ v0.1.0 | — | — |
| Debian / Ubuntu | — | beta* (#7) | ✓ v0.1.0 | ✓ v0.1.0 | — | — |
| Windows | — | — | ✓ v0.1.0 | ✓ v0.1.0 | beta* (#5) | beta* (#5) |
Notes:
- *Homebrew status: the generated formula installs and passes brew test on Linuxbrew from the real v0.1.0 jar, and the installed fhir-frog-mcp command serves MCP calls over stdio. It has not yet been run on a Mac. The tap is public: brew tap fhirfrog/tap https://gitlab.com/fhirfrog/homebrew-tap (repository https://gitlab.com/fhirfrog/homebrew-tap).
- *Debian .deb status: the package is built and tested reproducibly on the host (dpkg-deb, control fields, file modes, byte-identical rebuild all pass). It has not yet been installed inside a Debian or Ubuntu container — that will be tested by running lintian in a container as part of issue #7. An apt repository is planned for phase 2 (needs a signing-key decision).
- *Scoop / Chocolatey status: the packages are generated from templates and structurally validated (manifest is valid JSON, nuspec is valid XML, nupkg contains expected entries). They have not yet been run on Windows. The Scoop bucket is public (https://gitlab.com/fhirfrog/scoop-bucket) and the Chocolatey package is published on the project's GitLab NuGet feed (https://gitlab.com/api/v4/projects/87883606/packages/nuget/index.json, anonymous read). See issue #5.
- Homebrew is macOS-first. Linuxbrew works (it is how the formula is tested).
- The JAR runs on any platform with Java 17+; it is the most portable option.
- The container image (registry.gitlab.com/fhirfrog/fhir-frog-mcp) is built
for linux/amd64 and linux/arm64.
- winget requires an installer type (.exe, .msi, .msix); a plain JAR is not
one. winget support waits for the planned jlink zip or MSI build.
Homebrew formula¶
The formula is generated from a template so it can be regenerated for each release without manually editing Ruby.
Template¶
packaging/homebrew/fhir-frog-mcp.rb.tmpl contains three placeholders:
| Placeholder | Substituted with |
|---|---|
@VERSION@ |
Release version, e.g. 0.1.0 |
@URL@ |
Download URL for the shaded JAR |
@SHA256@ |
SHA-256 hex digest of the JAR |
The URL pattern for the GitLab generic package registry is:
https://gitlab.com/api/v4/projects/fhirfrog%2Ffhir-frog-mcp/packages/generic/fhir-frog-mcp/<version>/fhir-frog-mcp-<version>-all.jar
Jar installation approach¶
Because the file downloaded by Homebrew is a .jar (not an archive),
using: :nounzip is specified in the url directive to prevent Homebrew
from attempting extraction. Inside def install, the jar is placed under
libexec/ using libexec.install, and a launcher script is written to
bin/ using bin.write_jar_script.
How to generate the formula for a release¶
After tagging the release and uploading the shaded JAR to the GitLab generic package registry, run:
# Compute the sha256 of the downloaded jar:
sha256sum fhir-frog-mcp-<version>-all.jar
# Generate the formula:
bash scripts/gen-brew-formula.sh \
--version <version> \
--sha256 <64-char hex from sha256sum> \
--out packaging/homebrew/fhir-frog-mcp.rb
The script:
1. Validates --version against ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.]+)?$.
2. Validates --sha256 against ^[0-9a-f]{64}$.
3. Performs substitution via Python (str.replace) so special characters in
the URL (e.g. &, |, %) cannot break a sed delimiter.
4. Refuses to emit any output that still contains an @…@ placeholder.
The generated .rb file is committed to the tap repository (separate from
this repository) and published via brew tap.
Formula structure summary¶
class FhirFrogMcp < Formula
desc "MCP server for FHIR Frog …"
homepage "https://gitlab.com/fhirfrog/fhir-frog-mcp"
url "<URL>", using: :nounzip
sha256 "<SHA256>"
version "<VERSION>"
license "Apache-2.0"
depends_on "openjdk@17"
def install
libexec.install "fhir-frog-mcp-<VERSION>-all.jar"
bin.write_jar_script libexec/"fhir-frog-mcp-<VERSION>-all.jar",
"fhir-frog-mcp", java_version: "17"
end
test do
output = shell_output("#{bin}/fhir-frog-mcp --version 2>&1")
assert_match "<VERSION>", output
end
end
Planned: jlink runtime image¶
A future release may bundle a trimmed-down Java runtime produced by jlink
so that users do not need to install a separate JDK. This is under
consideration; no timeline is set.
Planned: macOS notarisation¶
Distributing a signed binary through Homebrew or as a .pkg installer may
eventually require an Apple Developer ID to allow Gatekeeper to verify the
binary. This is currently undecided — the requirement is only triggered
if fhir-frog-mcp is distributed as a signed, self-contained binary (e.g. via
jlink) rather than as a plain JAR. The shaded JAR itself does not require
notarisation.
Windows packages (Scoop and Chocolatey)¶
Status¶
Generated and structurally validated; not yet run on Windows. The packages are built from templates, the manifests are valid JSON / XML, and the nupkg contains the expected entries. Runtime behaviour on Windows (Scoop shim, Chocolatey shim, Java path resolution) has not been verified.
See issue #5 for progress.
Scoop manifest¶
The Scoop manifest is generated from packaging/scoop/fhir-frog-mcp.json.tmpl.
| Placeholder | Substituted with |
|---|---|
@VERSION@ |
Release version, e.g. 0.1.0 |
@URL@ |
Download URL for the shaded JAR (with #/fhir-frog-mcp.jar fragment for Scoop rename) |
@SHA256@ |
SHA-256 hex digest of the JAR |
Manifest structure summary¶
{
"version": "<VERSION>",
"description": "MCP server for FHIR Frog ...",
"homepage": "https://gitlab.com/fhirfrog/fhir-frog-mcp",
"license": "Apache-2.0",
"depends": "java/temurin17-jre",
"url": "<URL>#/fhir-frog-mcp.jar",
"hash": "<SHA256>",
"bin": "fhir-frog-mcp.jar",
"checkver": { "url": "...", "jsonpath": "$[0].tag_name", "regex": "v([\\d.]+)" },
"autoupdate": { "url": "...<$version>..." }
}
bin: fhir-frog-mcp.jar causes Scoop to shim the JAR as the fhir-frog-mcp command using the Java installed by java/temurin17-jre.
The autoupdate section does not include hash because that requires a .sha256 sidecar file to be published alongside the JAR at a predictable URL. The release process must be updated to publish such a sidecar before autoupdate.hash can be enabled.
How to generate the Scoop manifest for a release¶
bash scripts/gen-scoop-manifest.sh \
--version <version> \
--sha256 <64-char hex from sha256sum> \
--out packaging/scoop/fhir-frog-mcp.json
The generated .json is committed to the Scoop bucket repository (separate from this repository): https://gitlab.com/fhirfrog/scoop-bucket.
Chocolatey package¶
The Chocolatey package is generated from three templates:
| Template | Purpose |
|---|---|
packaging/chocolatey/fhir-frog-mcp.nuspec.tmpl |
Package metadata (XML) |
packaging/chocolatey/tools/chocolateyinstall.ps1.tmpl |
Install script (PowerShell) |
packaging/chocolatey/tools/chocolateyuninstall.ps1.tmpl |
Uninstall script (PowerShell) |
The install script uses Get-ChocolateyWebFile to download the JAR, writes a fhir-frog-mcp.cmd launcher, and calls Install-BinFile to shim it onto PATH. Uninstall calls Uninstall-BinFile and removes the jar and cmd launcher.
Dependency: temurin17 (Chocolatey package for Eclipse Temurin JDK 17).
Escaping rules¶
- nuspec (.xml): values are XML-escaped via
html.escape()in Python (&→&,<→<,'→'). - PS1 files (PowerShell single-quoted strings): single quotes in URL or SHA256 are doubled (
'→''). This is the PowerShell rule for single-quoted strings.
nupkg structure¶
A .nupkg is a ZIP with forward-slash entry paths, no absolute paths:
[Content_Types].xml
_rels/.rels
package/services/metadata/core-properties/fhir-frog-mcp.psmdcp
fhir-frog-mcp.nuspec
tools/chocolateyinstall.ps1
tools/chocolateyuninstall.ps1
How to generate the Chocolatey package for a release¶
bash scripts/gen-choco-package.sh \
--version <version> \
--sha256 <64-char hex from sha256sum> \
--outdir /tmp/choco-out
The nupkg is written to <outdir>/fhir-frog-mcp.<version>.nupkg. Publish it to a NuGet feed (e.g. GitLab Package Registry) or submit to the Chocolatey Community Repository (requires moderation).
Chocolatey community-repository moderation¶
The Chocolatey Community Repository requires manual review for new packages. The first submission goes through a moderation queue before becoming publicly installable via choco install fhir-frog-mcp. This is tracked in issue #5.
Windows-specific items to verify on Windows¶
The following have not been verified on an actual Windows system:
- Docker Desktop named pipe vs socket: Docker Desktop on Windows exposes
//./pipe/docker_enginerather than a Unix socket at/var/run/docker.sock. The Docker-mode client config for Windows will need a different volume mount. The containerised mode may require Docker Desktop's "Expose daemon on tcp://localhost:2375 without TLS" option. FROG_WORKSPACE_HOSTpath mapping: Windows paths must use forward slashes or properly escaped backslashes in the Docker-vflag arguments.%USERPROFILE%\.fhir-frog: the defaultFROG_CONFIG_DIRuses%USERPROFILE%which expands correctly in PowerShell / CMD but may need to be set explicitly in the MCP client config JSON (useC:\Users\<name>\.fhir-frog).- Scoop shim resolution: the Scoop
bin: fhir-frog-mcp.jarshim relies onjava/temurin17-jrehaving added Java to PATH. - Chocolatey shim:
Install-BinFileshimming of.cmdfiles requires the Chocolatey shimgen tooling; behaviour with long paths has not been tested. - winget: winget requires an installer type (
.exe,.msi,.msix). A plain JAR is not accepted. winget support waits for the planned jlink zip or MSI; see issue #5.
Debian / Ubuntu package (.deb)¶
Design¶
The .deb is intentionally thin — no bundled Java runtime. This was a deliberate decision:
- There is no runtime to patch: the installed JRE is managed by the system package manager (apt), so security patches are applied system-wide without rebuilding the package.
- The shaded JAR is architecture-independent (
Architecture: all). - The total installed size is dominated by the JAR (~155 MB); bundling a trimmed runtime would not save significant space and would require per-architecture builds.
Status¶
Built and tested; not yet installed inside a Debian or Ubuntu container (that step requires Claude to run lintian in a container, tracked in issue #7).
An apt repository is planned for phase 2. It needs a signing-key decision before it can be published. For now, the .deb is a direct download and install.
Package structure¶
| File | Mode | Purpose |
|---|---|---|
/usr/bin/fhir-frog-mcp |
0755 | POSIX sh launcher: exec java -jar /usr/share/fhir-frog-mcp/fhir-frog-mcp.jar "$@" |
/usr/share/fhir-frog-mcp/fhir-frog-mcp.jar |
0644 | Shaded JAR |
/usr/share/doc/fhir-frog-mcp/copyright |
0644 | Debian copyright format 1.0, Apache-2.0 |
/usr/share/doc/fhir-frog-mcp/changelog.Debian.gz |
0644 | Minimal Debian changelog (gzip -9n) |
/usr/share/doc/fhir-frog-mcp/README.Debian |
0644 | FROG_WORKSPACE, Docker optional, install page link |
Control fields:
- Package: fhir-frog-mcp
- Architecture: all
- Section: devel, Priority: optional
- Maintainer: FHIR Frog contributors <[email protected]>
- Depends: openjdk-17-jre-headless | java17-runtime-headless | openjdk-21-jre-headless | java21-runtime-headless
- Homepage: https://gitlab.com/fhirfrog/fhir-frog-mcp
Templates¶
packaging/debian/ contains five files:
| File | Purpose |
|---|---|
control.tmpl |
Debian control file template (@VERSION@ placeholder) |
changelog.tmpl |
Debian changelog template (@VERSION@, @CHANGELOG_DATE@) |
copyright |
Static Debian copyright file (no placeholders) |
README.Debian |
Static README for Debian (no placeholders) |
fhir-frog-mcp.launcher |
POSIX sh launcher (no placeholders) |
How to build the .deb for a release¶
After uploading the shaded JAR to the GitLab generic package registry, run:
# Compute the sha256 of the downloaded jar:
sha256sum fhir-frog-mcp-0.1.0-all.jar
# Build the .deb:
bash scripts/build-deb.sh \
--version 0.1.0 \
--jar /path/to/fhir-frog-mcp-0.1.0-all.jar \
--sha256 <64-char hex from sha256sum> \
--outdir dist/
Or pass the JAR as a URL (downloaded with curl -fsSL, sha256 verified before packaging):
bash scripts/build-deb.sh \
--version 0.1.0 \
--jar https://gitlab.com/api/v4/projects/fhirfrog%2Ffhir-frog-mcp/packages/generic/fhir-frog-mcp/0.1.0/fhir-frog-mcp-0.1.0-all.jar \
--sha256 <64-char hex> \
--outdir dist/
Output: dist/fhir-frog-mcp_0.1.0-1_all.deb.
Reproducibility¶
build-deb.sh produces byte-identical output for the same inputs:
SOURCE_DATE_EPOCHis set from the git HEAD commit time (or--source-date-epoch).- All file timestamps are set to
SOURCE_DATE_EPOCHwithtouch -d @epoch. - The changelog is compressed with
gzip -9n(no embedded timestamp). dpkg-deb --build --root-owner-groupis used to strip host uid/gid.- Files are written in a fixed staging layout order.
The scripts/build-deb.sh script¶
The script:
- Validates
--versionagainst^[0-9]+\.[0-9]+\.[0-9]+([~+.][0-9A-Za-z.]+)?$. - Validates
--sha256against^[0-9a-f]{64}$. - If
--jaris a URL, requireshttps://scheme; downloads withcurl -fsSL. - Verifies the sha256 of the JAR before packaging.
- Rejects newlines in values (would corrupt Debian control fields).
- Performs substitution via Python (
scripts/build-deb-sub.py) so special characters cannot inject control-file fields. - Refuses to emit any output that still contains an
@…@placeholder. - Overrides template directory via
FROG_DEB_TEMPLATE_DIR(for tests only).
See also¶
- install.md — how to install and configure the MCP server
packaging/homebrew/fhir-frog-mcp.rb.tmpl— the Homebrew formula templatescripts/gen-brew-formula.sh— the Homebrew generation scriptpackaging/debian/control.tmpl— the Debian control file templatescripts/build-deb.sh— the Debian package build scriptpackaging/scoop/fhir-frog-mcp.json.tmpl— the Scoop manifest templatescripts/gen-scoop-manifest.sh— the Scoop manifest generation scriptpackaging/chocolatey/fhir-frog-mcp.nuspec.tmpl— the Chocolatey nuspec templatescripts/gen-choco-package.sh— the Chocolatey package generation script