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 packagesDoctrine #4 β NEVER BUNDLE WITHOUT SIGNATURE. Only a bundle whose
signature.sigverifies 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-mcpThe 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-mcpYouβll get one of three verdicts:
| Verdict | Meaning | What happens |
|---|---|---|
β
valid | signature verifies, all digests match | attaches silently |
β οΈ unsigned | no signature present | attaches only with --allow-unsigned |
β invalid | signature or a file digest failed | blocked β 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-mcpAfter deploy, attaching any registered area runs the bundled package β
npx/uvx never touch the network.
4. Confirm whatβs installed
frootai mcp bundle listThis 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:
- On a connected host, download +
verifythe bundle. - Copy the
.fai-mcpfile across your air-gap boundary (USB, internal mirror). - On the offline host,
verifyagain, thendeploy. - 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-unsignedThis 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
| Symptom | Cause | Fix |
|---|---|---|
verdict: invalid | tamper / corruption | re-download from a trusted source |
deploy refused: unsigned | no signature | pass --allow-unsigned (internal only) |
already exists | bundle already deployed | pass --force to redeploy |
bundle revoked | listed in revoked-bundles.json | do not use β get a fresh bundle |