What is a paso Declaration?

On this page

A paso declaration is a usepaso.yaml file. It describes your API and what agents are allowed to do with it. paso reads this file and generates a standards-compliant MCP server. No protocol code. No SDK wiring. One file.

The smallest working declaration

version: "1.0"

service:
  name: Inventory
  description: Product inventory management for e-commerce
  base_url: https://api.inventory.com/v1
  auth:
    type: bearer

capabilities:
  - name: list_products
    description: List all products, optionally filtered by category
    method: GET
    path: /products
    permission: read
    inputs:
      category:
        type: string
        description: Filter by product category
        in: query

That’s 18 lines. It produces one MCP tool that agents can call. Let’s break it down.

service: who you are

The service block tells agents what this API does and how to reach it.

service:
  name: Inventory
  description: Product inventory management for e-commerce
  base_url: https://api.inventory.com/v1
  auth:
    type: bearer
  • name: Shows up in logs and tool registration. Keep it short.
  • description: Agents read this to decide if the API is relevant. Write it for an LLM, not a human.
  • base_url: The root URL. All capability paths are appended to this.
  • auth.type: How the token is sent. Options: bearer, api_key, oauth2, none.

The actual token is always provided at runtime via USEPASO_AUTH_TOKEN. It never goes in the YAML file.

capabilities: what agents can do

Each capability maps to one API endpoint. It’s a single action an agent can take.

capabilities:
  - name: create_product
    description: Create a new product listing
    method: POST
    path: /products
    permission: write
    consent_required: true
    inputs:
      name:
        type: string
        required: true
        description: Product name
      price:
        type: integer
        required: true
        description: Price in cents (e.g., 1999 = $19.99)
      category:
        type: enum
        values: [electronics, clothing, food, other]
        description: Product category

The required fields: name, description, method, path, permission.

  • name: Must be snake_case. This becomes the MCP tool name.
  • description: Agents use this to decide when to call the tool. Be specific. “Create a new product listing” beats “Create product”.
  • method: GET, POST, PUT, PATCH, DELETE.
  • path: Relative to base_url. Use {param} for path variables and add a matching input with in: path.
  • permission: read, write, or admin. Controls the risk tier.

inputs

Each input describes a parameter the agent sends.

FieldRequiredPurpose
typeYesstring, integer, number, boolean, enum, array, object
requiredNoDefault: false
descriptionYesWhat this parameter does
inNoWhere it goes: path, query, body, header. Defaults to body for POST/PUT/PATCH, query for GET/DELETE
valuesFor enumAllowed values
defaultNoDefault value

When set to true, the agent shows a confirmation dialog before executing. Use it for anything that creates, modifies, or deletes data.

permissions: what’s allowed and what’s not

The permissions object groups capabilities by risk tier.

permissions:
  read:
    - list_products
    - get_product
  write:
    - create_product
    - update_product
  admin:
    - delete_product
  forbidden:
    - purge_all_products
  • read: Safe operations. Listing, searching, fetching details.
  • write: Modifications. Creating, updating. Should use consent_required: true.
  • admin: High-risk. Deleting, changing access controls. Always require consent.
  • forbidden: Explicitly blocked. Never exposed as MCP tools.

If you omit the permissions object, each capability’s individual permission field is used directly.

constraints: guardrails

Add business rules to limit what agents can do.

- name: create_product
  permission: write
  consent_required: true
  constraints:
    - max_per_hour: 100
      description: Product creation is rate-limited
    - max_value: 99999999
      description: Maximum price is $999,999.99

Available constraints: max_per_hour, max_per_request, max_value, allowed_values, requires_field.

A complete declaration

Putting it all together for a real API:

version: "1.0"

service:
  name: Inventory
  description: Product inventory management for e-commerce
  base_url: https://api.inventory.com/v1
  auth:
    type: bearer

capabilities:
  - name: list_products
    description: List all products, optionally filtered by category
    method: GET
    path: /products
    permission: read
    inputs:
      category:
        type: string
        description: Filter by product category
        in: query
      limit:
        type: integer
        description: Number of results (1-100)
        default: 20
        in: query

  - name: create_product
    description: Create a new product listing
    method: POST
    path: /products
    permission: write
    consent_required: true
    inputs:
      name:
        type: string
        required: true
        description: Product name
      price:
        type: integer
        required: true
        description: Price in cents
    constraints:
      - max_per_hour: 100
        description: Product creation is rate-limited

  - name: delete_product
    description: Permanently delete a product and its history
    method: DELETE
    path: /products/{product_id}
    permission: admin
    consent_required: true
    inputs:
      product_id:
        type: string
        required: true
        description: The product ID
        in: path

permissions:
  read:
    - list_products
  write:
    - create_product
  admin:
    - delete_product

30 lines of YAML for a three-tool MCP server with permission tiers, consent gates, and rate limits. The equivalent hand-written MCP server is 200+ lines of TypeScript.

Verify it

usepaso validate
valid (Inventory, 3 capabilities, 0 regrets)

Then serve it:

export USEPASO_AUTH_TOKEN="your-token"
usepaso serve

Your API is agent-ready.

Questions

Do I need a separate YAML file per API? Yes. One usepaso.yaml per API. If you have a Stripe API and a Sentry API, that’s two files, two servers.

What if my API uses an API key instead of a bearer token? Set auth.type: api_key and optionally auth.header: X-API-Key. paso sends the USEPASO_AUTH_TOKEN value in that header.

Can I generate this from an OpenAPI spec? Yes. usepaso init --from-openapi ./openapi.json converts an OpenAPI 3.x spec into a usepaso.yaml. You’ll want to review and customize the output.

Related:

Read the full declaration reference.