Elizon Docs
Features & SpecsPortable definitions

Tools

Portable MCP and OpenAPI Tool definitions.

A Tool is either an MCP server or an OpenAPI (Swagger) tool. Its address and behaviour are part of the Package. Whatever authenticates to it is not: that's a Slot the installer fills with their own account.

For a complete, valid project with one Tool of each kind, run npx @elizon/cli example tool. It prints an MCP Tool connected with OAuth, an MCP Tool that takes an API key as a header, an OpenAPI Tool, the credential Slot behind each one, and an Agent that binds their operations. It needs no account and no network.

MCP

tools/calendar.yaml
toolType: mcp
name: Practice Calendar
identifier: practice-calendar
url: https://calendar.example.com/mcp
authType: oauth_manual
credentialSlot: calendar-access
headers:
  - name: X-Elizon-Package
    valueType: fixed
    value: triage
requestTimeoutMs: 30000
sseReadTimeoutMs: 60000
FieldMeaning
nameDisplay name.
identifierThe Tool's identifier, unique within the installing project. A clash is offered as reuse-or-rename.
urlThe MCP server's address, part of what the Package is.
authTypedcr, oauth_manual or none.
credentialSlotThe credential Slot that authenticates it for oauth_manual, otherwise null.
headers[]Extra headers: valueType: fixed with a value, or valueType: credential with a slot.
requestTimeoutMs, sseReadTimeoutMsTimeouts in milliseconds.

An API key sent as a header

An API key isn't an OAuth flow, so use authType: none and send the key as a credential header. Its value is resolved from the installer's Secrets vault at call time:

tools/ledger.yaml
toolType: mcp
name: Spend Ledger
identifier: spend-ledger
url: https://ledger.example.com/mcp
authType: none
credentialSlot: null
headers:
  - name: X-Ledger-Key
    valueType: credential
    slot: ledger-access
requestTimeoutMs: 15000
sseReadTimeoutMs: 30000

A valueType: fixed header is carried into the .pkg exactly as written, so validate and build warn with TOOL_FIXED_HEADER_SECRET when its value looks like a secret: it starts with an auth scheme (Bearer …, Basic …), contains a known API-key prefix (sk-…, ghp_…, AKIA…), or is set under a credential-bearing header name such as Authorization, Cookie or X-Api-Key. The Package still builds; move the key to a credential header like the one above. A vault: or {{secret.…}} value is refused instead, because a Package never carries a secret pointer.

OpenAPI (Swagger)

toolType: swagger
name: Orders API
spec: { … the full OpenAPI 3.x document … }
authType: header # none, header or query_param
credentialSlot: orders-api-key
FieldMeaning
specThe complete OpenAPI 3.x document.
actionsOptional — leave it out. build derives the operations Agents can call from spec.
authTypenone, header or query_param.
credentialSlotThe credential Slot holding the key, or null.

Binding an Agent to an operation

Name the operation by its operationId from the spec:

agents/order-desk.yaml
tools:
  - tool: tools.orders
    toolType: swagger
    actionId: getOrder # the spec's operationId
    enabled: true
    config: {}

npx @elizon/cli build derives each operation's action exactly as Studio does when you import the spec, and writes the action's actionId into the artifact. That actionId is the method in lower case, _, then the path with every character other than a letter or a digit replaced by _ (the path keeps its case): getOrder on GET /orders/{id} becomes get__orders__id_, and on GET /orders/{orderId} it becomes get__orders__orderId_. Binding by that actionId directly works too, and is the only way to name an operation that has no operationId.

build refuses a binding that names neither an operationId nor an actionId of the Tool, and lists the Tool's actionIds.

References in the spec

build resolves the spec's own references ($ref: '#/components/…') and carries the resolved spec in the artifact, as Studio stores it. It never fetches anything, so a reference to another file or URL is refused: bundle the spec into one self-contained document first.

Email addresses in the spec

Most third-party OpenAPI documents carry a contact address (info.contact.email). Inside the embedded spec, validate and build report an address-shaped match as a PORTABLE_MACHINE_CONTENT_EMAIL warning at its path, such as tools.orders.spec.info.contact.email, instead of refusing it. A vendor's contact, or a match that is not an address such as logo@2x.png, may stay; a person's address must go. A vault: / {{secret.…}} reference in the spec is still refused. Everywhere else in the Tool file an address is refused (see PORTABLE_PERSONAL_DATA_LEAK).

Listing actions yourself

You rarely need to. If you do, each action must be an operation of spec, in the shape Studio derives:

actions:
  - actionId: get__orders__id_ # GET /orders/{id}
    name: getOrder
    description: Fetch one order.
    method: GET
    path: /orders/{id}
    parameters:
      - { name: id, in: path, required: true, schema: { type: string } }
    requestBody: null
FieldMeaning
actionIdWhat an Agent's tools[].actionId names. Unique within the Tool.
nameWhat the model sees the action called.
methodAn HTTP method. With path, it must be an operation that spec actually has.
pathThe spec's path, starting with /.
parametersOptional. The operation's parameters (name, in, required), used to build the request.
requestBodyOptional. { required, contentType } when the operation takes a body, otherwise null.

A listed actions is carried as written: build checks it but does not derive anything.

Credentials

Declare the credential as a Slot and point the Tool at it:

elizon.yaml
slots:
  - id: ledger-access
    type: credential
    scheme: api_key
    required: true
    boundTo: [tools.ledger]
    description: An API key for your ledger with read access.

At install the operator binds it to a Secret, and the platform writes the reference into the Tool. A Tool whose credential was deferred is finished later with packages bind or Bind in Studio. OAuth Tools (dcr, oauth_manual) are connected in the Studio wizard, which also runs a connectivity test for each Tool.

What validate and build refuse

validate checks the manifest, the references between objects and each Tool's credential settings. build runs validate first and also parses every Tool file, so the rest of what is wrong inside a Tool is reported by build. Run both after each change.

Each refusal below comes with the smallest change that triggers it, written against the project npx @elizon/cli example tool prints. The title of each snippet names the file to change. Only the keys shown change; the rest of the file stays as the example has it.

UNRESOLVED_LOCAL_REFERENCE

Refused by validate and build. An Agent names a Tool the manifest does not declare. The hint lists the Tools that are declared.

agents/order-desk.yaml
tools:
  - tool: tools.order
    toolType: swagger
    actionId: getOrder
    enabled: true
    config: {}

PORTABLE_UNDECLARED_REFERENCE

Refused by validate and build. A Tool's credential is always a credential Slot that the installer binds, at install or later. A Package never carries a key or a secret name. The diagnostic's path names the Tool and the field, such as tools.orders.credentialSlot or tools.ledger.headers[0].slot, and the message contains the one export gives for the same mistake.

A credentialSlot that names no declared Slot:

tools/orders.yaml
credentialSlot: no-such-slot

A credentialSlot that names a Slot of another type (here the llm Slot):

tools/orders.yaml
credentialSlot: reasoning

A credential header whose slot is not a declared credential Slot:

tools/ledger.yaml
headers:
  - name: X-Ledger-Key
    valueType: credential
    slot: nope

credentialSlot: null on a Tool whose authentication needs a credential: an oauth_manual MCP Tool, or a header / query_param OpenAPI Tool. Without a Slot it would install with nothing for the operator to supply:

tools/calendar.yaml
credentialSlot: null
tools/orders.yaml
credentialSlot: null
tools/orders.yaml
authType: query_param
credentialSlot: null

PORTABLE_DEFINITION_INVALID for a secret reference

Refused by validate and build. A value in a Tool file is a vault reference: it starts with vault:, or contains {{secret.…}}. A reference resolves against whichever Organization reads it, so the install refuses the Package. Use a credential header and a credential Slot instead. The path names the field, such as tools.ledger.headers[0].value, and every reference in the file is reported:

tools/ledger.yaml
headers:
  - name: X-Ledger-Key
    valueType: fixed
    value: vault:ledger_key
tools/ledger.yaml
headers:
  - name: X-Ledger-Key
    valueType: fixed
    value: '{{secret.LEDGER}}'

PORTABLE_PERSONAL_DATA_LEAK

Refused by validate and build. A string in the Tool file holds an email address, such as a fixed header's value or an action's description. The Tool file travels with the Package, so remove the address (or, if it isn't one, such as logo@2x.png, rename it). The path names the field, such as tools.ledger.headers[1].value, and every address in the file is reported. See Carried text.

An address-shaped match inside the embedded spec is a warning, not a refusal; see Email addresses in the spec.

tools/ledger.yaml
headers:
  - name: X-Ledger-Key
    valueType: credential
    slot: ledger-access
  - name: X-Ledger-Contact
    valueType: fixed
    value: ops@acme.com

PORTABLE_DEFINITION_INVALID

Refused by build. The Tool file is not a valid portable Tool. The diagnostic's path names the field, such as tools.ledger.authType.

A value the field does not take. An MCP authType is dcr, oauth_manual or none:

tools/ledger.yaml
authType: api_key

A field a Tool does not have. There is nowhere in a Tool file to put a credential, so a credential written into one is refused rather than carried:

tools/ledger.yaml
authConfig:
  token: replace-me

An identifier-shaped value (a UUID). An id belongs to one Organization and means nothing in another:

tools/ledger.yaml
headers:
  - name: X-Ledger-Account
    valueType: fixed
    value: 3f2b8c1e-4d5a-4b6c-8d7e-9f0a1b2c3d4e

A header in the format Studio stores (expression). In a Package a header is fixed or credential:

tools/ledger.yaml
headers:
  - name: X-Ledger-Key
    valueType: expression
    value: ledger-key

A spec that is not OpenAPI 3.x, when actions is left out (build can only derive actions from 3.x):

tools/orders.yaml
spec:
  swagger: '2.0'
  info:
    title: Orders API
    version: 1.0.0
  paths: {}

A spec with a $ref to another file or a URL. build never fetches anything:

tools/orders.yaml
spec:
  openapi: 3.0.3
  info:
    title: Orders API
    version: 1.0.0
  paths:
    /orders:
      get:
        operationId: listOrders
        responses:
          '200':
            $ref: ./responses.yaml#/Orders

A listed action that is not an operation of the spec:

tools/orders.yaml
actions:
  - actionId: delete__orders__orderId_
    name: deleteOrder
    method: DELETE
    path: /orders/{orderId}

PORTABLE_UNRESOLVED_BINDING

Refused by build. An Agent binds an OpenAPI Tool by a name that is neither an operationId in the spec nor one of the Tool's actionIds. The message lists the Tool's actionIds.

agents/order-desk.yaml
tools:
  - tool: tools.orders
    toolType: swagger
    actionId: cancelOrder
    enabled: true
    config: {}

An MCP binding's actionId is the name of a tool the server lists, and build cannot check it: it never connects to the server.

Refused when a Tool is exported from Studio or installed

These come from the same Tool code, at the two ends where a Tool meets a real Organization: exporting a Tool from Studio into a Package, and installing a Package. Where a Tool file can make the same mistake, validate and build refuse it first, as the sections above show.

CodeWhereSmallest input
PORTABLE_UNDECLARED_REFERENCEExportAn oauth_manual MCP Tool, or a header / query_param OpenAPI Tool, with no credential Slot mapped to its authentication.
PORTABLE_UNDECLARED_REFERENCEExportAn Expression header (vault:ledger-key) with no credential Slot mapped to its secret.
PORTABLE_UNDECLARED_REFERENCEExportA Slot mapped to the Tool that the Package does not declare, or declares with a type other than credential (an llm Slot, say).
PORTABLE_UNDECLARED_REFERENCEExportA dcr MCP Tool with a Slot mapped to it. A dcr client registers itself when it connects, so it takes no credential from the operator.
PORTABLE_ORG_IDENTIFIER_LEAKExportA fixed header whose value is a vault reference (vault:ledger-key) or an identifier-shaped value (a UUID).
PORTABLE_UNRESOLVED_BINDINGInstallA credential Slot the Tool uses (its credentialSlot or a header's slot) that the operator neither binds to a secret nor defers.
PORTABLE_DEFINITION_INVALIDInstallA Tool file with a vault reference: a value that starts with vault:, or a {{secret.…}} anywhere, such as the value of a fixed header.

build does not yet check two things, so check them yourself before you build:

  • credentialSlot is null for dcr and none. build accepts a Slot there, and the installer is then asked for a credential nothing reads. validate does not check it because it is outside the scope of the credential checks above, and export already refuses it;
  • vault: later in a value, such as a URL's query (?k=vault:x). No stage refuses it: not validate, build, export or install. Each treats only a value that starts with vault: as a vault reference, so never put a secret name anywhere in a Tool file.