Skip to content

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

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 (& → &amp;, < → &lt;, ' → &#x27;).
  • 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_engine rather 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_HOST path mapping: Windows paths must use forward slashes or properly escaped backslashes in the Docker -v flag arguments.
  • %USERPROFILE%\.fhir-frog: the default FROG_CONFIG_DIR uses %USERPROFILE% which expands correctly in PowerShell / CMD but may need to be set explicitly in the MCP client config JSON (use C:\Users\<name>\.fhir-frog).
  • Scoop shim resolution: the Scoop bin: fhir-frog-mcp.jar shim relies on java/temurin17-jre having added Java to PATH.
  • Chocolatey shim: Install-BinFile shimming of .cmd files 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 <noreply@gitlab.com> - 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_EPOCH is set from the git HEAD commit time (or --source-date-epoch).
  • All file timestamps are set to SOURCE_DATE_EPOCH with touch -d @epoch.
  • The changelog is compressed with gzip -9n (no embedded timestamp).
  • dpkg-deb --build --root-owner-group is used to strip host uid/gid.
  • Files are written in a fixed staging layout order.

The scripts/build-deb.sh script

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. If --jar is a URL, requires https:// scheme; downloads with curl -fsSL.
  4. Verifies the sha256 of the JAR before packaging.
  5. Rejects newlines in values (would corrupt Debian control fields).
  6. Performs substitution via Python (scripts/build-deb-sub.py) so special characters cannot inject control-file fields.
  7. Refuses to emit any output that still contains an @…@ placeholder.
  8. 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 template
  • scripts/gen-brew-formula.sh — the Homebrew generation script
  • packaging/debian/control.tmpl — the Debian control file template
  • scripts/build-deb.sh — the Debian package build script
  • packaging/scoop/fhir-frog-mcp.json.tmpl — the Scoop manifest template
  • scripts/gen-scoop-manifest.sh — the Scoop manifest generation script
  • packaging/chocolatey/fhir-frog-mcp.nuspec.tmpl — the Chocolatey nuspec template
  • scripts/gen-choco-package.sh — the Chocolatey package generation script