A pack is a versioned bundle with a b3pack.json manifest at its root. The manifest names the pack, declares the hosts it may reach and the credentials it may use, and describes each action and trigger.

A small pack

This pack has one action, which calls the public Open-Meteo geocoding API and needs no credential:

json
{ "pack": "io.b3os/open-meteo", "version": "1.0.0", "title": "Open-Meteo", "description": "Place search and current weather from the Open-Meteo API.", "publisher": { "id": "io.b3os", "name": "B3OS" }, "license": "MIT", "engine": "^1.0", "egress": [{ "host": "geocoding-api.open-meteo.com" }], "actions": [ { "key": "search-places", "title": "Search places", "description": "Find places by name. Returns coordinates and time zone for each match, best match first.", "inputSchema": { "type": "object", "additionalProperties": false, "required": ["name"], "properties": { "name": { "type": "string", "minLength": 2, "maxLength": 100 } } }, "outputSchema": { "type": "object", "additionalProperties": false, "required": ["places"], "properties": { "places": { "type": "array" } } }, "effects": { "class": "read" }, "impl": { "kind": "http", "request": { "method": "GET", "url": "https://geocoding-api.open-meteo.com/v1/search", "query": { "name": "${{ input.name }}", "format": "json" } }, "response": { "map": { "places": "${{ has(response.body.results) ? response.body.results : [] }}" } } } } ]}

Its action is called as io.b3os/open-meteo/search-places@1.0.0, and its MCP tool name is open-meteo__search-places.

Manifest fields

FieldMeaning
pack, version<namespace>/<name> and a semantic version without build metadata. The namespace is the publisher's reverse-DNS name.
title, description, license, publisherWhat the pack is and who publishes it. Verification status lives in the registry, never in the manifest.
engineThe pack runtime it needs, as a caret range; the v1 runtime is 1.0.0.
egressEvery host any action or trigger may reach. Actions may narrow it. A new host in a later version is a major change.
connectionsThe credentials the pack can use, by key, each with a type (api_key, bearer, basic, oauth2) and the hosts it is injected for
actionsUp to 256 actions: key, title, description, inputSchema, outputSchema, effects and impl
triggersUp to 64 webhook, poll or schedule triggers
signatureWritten by the publish tool: the content hash and the publisher's Ed25519 signature

How a declarative action runs

  1. The input is validated against inputSchema, and schema defaults fill in absent fields.
  2. The request template is evaluated. Expressions are CEL inside ${{ }}, with input and the connections' non-secret params in scope. Text spliced into the URL is percent-encoded, so an input can never add a path segment or a query.
  3. The final URL's host must match the action's egress list.
  4. The egress proxy adds the credential for each connection the action requires, only on requests to that connection's hosts: api_key where the pack's inject says, bearer and oauth2 as Authorization: Bearer, basic as Authorization: Basic. Templates never see secrets and cannot set the injected header.
  5. A response counts as success on a 2xx status unless response.success says otherwise. response.map builds the output, which must match outputSchema; the default map is the response body. On failure, the first matching errors[] entry names the pack's error, reported in the problem's pack_error.

Packs whose logic needs code are written in JavaScript and run in a separate runtime with no credentials, reaching the network only through the egress proxy. In the beta those are first-party packs only.

Signing and verification

Every pack version has a content hash over all of its files (the manifest serialized with RFC 8785, without its signature) and an Ed25519 signature by the publisher's registered key over b3pack-sig-v1, the pack, the version and the hash. Before loading a version, a node recomputes the hash, compares it with the registry's record, and checks the signature. A pack that fails is not loaded, and calls to it fail with pack_unavailable.

The registry

CallDoes
GET /v1/actions/packsPacks the node serves, with their latest version and action and trigger counts
GET /v1/actions/packs/{namespace}/{name}A pack version with its actions, triggers, connections and egress hosts
GET /v1/actions/packs/{namespace}/{name}/versions/{version}/manifestThe exact b3pack.json, as published and signed
GET /v1/actions/registry/packsThe registry at beta-api.b3os.org, with every published version
POST /v1/actions/registry/packsPublish a version as a gzip tarball (actions:write)

To see what a pack does before an agent calls it, read its manifest:

bash
curl -s https://beta-api.b3os.org/v1/actions/packs/io.b3os/open-meteo \ -H "Authorization: Bearer $B3OS_API_KEY"curl -s https://beta-api.b3os.org/v1/actions/packs/io.b3os/open-meteo/versions/1.0.0/manifest \ -H "Authorization: Bearer $B3OS_API_KEY"

Versions are immutable once published. In the beta only first-party B3OS packs (io.b3os) can be published; public publishing comes later, with signed provenance and a review of each version's new capabilities.

Ask a question... ⌘I