Skip to Content
McpInstall a Signed Bundle (Air-Gap)

Install a Signed Bundle (Air-Gap)

A .fai-mcp bundle is a single, cryptographically-signed archive that packs a set of MCP attach specs plus the npm/PyPI packages they run β€” so the kernel can attach those areas without reaching the public registries. This is the install path for enterprise networks, regulated environments, and fully air-gapped hosts.

Every bundle is a gzip-tar with this layout:

manifest.json ← signed metadata (per-file SHA256 + signature block) signature.sig ← Cosign signature over manifest.json specs/<slug>.json ← the attach specs snapshots/<slug>.json ← tool snapshots packages/<pkg>.tgz ← the bundled npm/uvx packages (the air-gap payload) sbom/bundle.cdx.json ← CycloneDX SBOM of the bundled packages

Doctrine #4 β€” NEVER BUNDLE WITHOUT SIGNATURE. Only a bundle whose signature.sig verifies against the FrootAI public key attaches silently. See Trust Tiers for the trust model.

1. Download

Grab a published bundle from the downloads page (or your internal mirror):

curl -L -o fai-mcp-tier1-v1.fai-mcp \ https://frootai.dev/downloads/fai-mcp-tier1-v1.fai-mcp

The flagship Tier-1 core bundle packs azure, playwright, github, markitdown, context7, and ms-learn.

2. Verify the signature

Always verify before you deploy. Verification runs a 6-step check: the signature must verify over manifest.json, every referenced file must re-hash to its recorded SHA256, and the spec count must be consistent.

frootai mcp bundle verify ./fai-mcp-tier1-v1.fai-mcp

You’ll get one of three verdicts:

VerdictMeaningWhat happens
βœ… validsignature verifies, all digests matchattaches silently
⚠️ unsignedno signature presentattaches only with --allow-unsigned
❌ invalidsignature or a file digest failedblocked β€” do NOT deploy

An invalid verdict means the bundle was tampered with or corrupted in transit. Re-download from a trusted source; never deploy it.

3. Deploy

Deploying extracts the bundle into your local deploy store (~/.frootai/bundles/<name>/), registers each spec, and test-attaches every area so you know the bundled packages run before you depend on them:

frootai mcp bundle deploy ./fai-mcp-tier1-v1.fai-mcp

After deploy, attaching any registered area runs the bundled package β€” npx/uvx never touch the network.

4. Confirm what’s installed

frootai mcp bundle list

This shows every deployed bundle, its spec count, and a live signature verdict β€” so a bundle that was tampered with after deploy shows up as invalid here.

Air-gap workflow

On a fully offline host, the flow is the same minus the registries:

  1. On a connected host, download + verify the bundle.
  2. Copy the .fai-mcp file across your air-gap boundary (USB, internal mirror).
  3. On the offline host, verify again, then deploy.
  4. Attach areas as usual β€” every subprocess runs the bundled .tgz/wheel.

Because the packages travel inside the signed bundle, no step reaches npm or PyPI. The signature is the trust root; the SBOM (sbom/bundle.cdx.json) gives your security team the full package inventory.

Unsigned bundles

While the signing key ceremony is pending, internal builds may be unsigned. An unsigned bundle is refused by default. To deploy one anyway:

frootai mcp bundle deploy ./internal-build.fai-mcp --allow-unsigned

This prints a warning and falls back to the manifest’s declared trust tier (the most conservative tier across its specs, unverified). Only do this for bundles you built yourself.

Revocation

The kernel checks a revocation list at attach time (https://frootai.dev/security/revoked-bundles.json). If a bundle (or a specific version/build) is later found to be compromised, it is added to that list and the kernel blocks it on the next attach β€” even if its signature still verifies. Keep the revocation list reachable from your network, or mirror it internally.

Troubleshooting

SymptomCauseFix
verdict: invalidtamper / corruptionre-download from a trusted source
deploy refused: unsignedno signaturepass --allow-unsigned (internal only)
already existsbundle already deployedpass --force to redeploy
bundle revokedlisted in revoked-bundles.jsondo not use β€” get a fresh bundle

See also

Last updated on