Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Connections

Each connection gives the runtime enough structure to make API calls and gives the model enough context to choose the right calls. Define local connections in amodal/connections/ or the top-level connections/ directory, then commit and deploy the repo. amodal connections add <name> writes spec.json, adds policy.json for REST, and copies oauth.json when supplied; see Use the CLI. The command uses amodal/connections/ when it contains entries, otherwise connections/.

Where changes are saved

Entry pointDestination
Amodal with a GitHub sourceA commit on the selected branch. Add connection defaults to the production branch.
Amodal with an Amodal sourceA source snapshot stored by Amodal. No GitHub repository is involved.
CLI connections add or removeFiles in the working tree at --path. Commit and push them yourself.

Only connection configuration goes into source. Secret values, OAuth client secrets, access tokens, refresh tokens, and account bindings stay in credential storage. Deploy the saved source to make its connection definitions available to a hosted runtime.

Directory Structure

amodal/connections/my-api/
├── spec.json       ← protocol, base URL, auth, health check
├── policy.json     ← endpoint permissions, confirmations, field restrictions
├── oauth.json      ← (custom OAuth) application settings, never the client secret
├── openapi.json    ← API contract when openapi.source selects this file
├── surface.md      ← endpoint documentation for the request tool
├── entities.md     ← (optional) entity definitions
└── rules.md        ← (optional) business rules

A REST connection uses either native OpenAPI tools or the generic request tool:

  • spec.json configures the API, credentials and optional openapi source.
  • openapi.json supplies operation contracts when selected by openapi.source.
  • surface.md documents connections served by request. Native connections derive their tools from the OpenAPI document and ignore this endpoint listing.
  • policy.json defines permissions. It is required for local REST connections. The minimal form is {"endpoints": {}}; native writes require matching entries. Packaged connections without a policy load with an empty policy.
  • entities.md and rules.md supply optional domain context.

Installed packages can provide all of these files. A local connections/<name>/surface.md, policy.json, oauth.json, entities.md, or rules.md (top level, not under amodal/) can customize a packaged connection while continuing to use the package's spec.json.

Credentials

Files declare how a connection authenticates; credential values live outside the repository. auth.token, auth.privateKey, and any headers or env entry named like a credential (Authorization, X-Api-Key, Cookie, or a name ending in token, key, secret, or password) must reference a secret as env:NAME or scope:KEY, or hold a {{NAME}} template. A literal value fails validation with CONNECTION_SECRET_IN_SOURCE. Other headers and environment entries, such as Accept or a log level, accept literals.

OAuth 2.0

Declare auth: {"type": "oauth2"} in a REST or HTTP MCP connection to authorize an account and refresh its tokens automatically. A custom application is described in oauth.json; a managed provider is selected with auth.provider; an MCP server that publishes OAuth metadata needs neither. See OAuth connections for Amodal, CLI, local development, hosted execution, and provider recipes.

Installed Packages

Runtime content packages are declared in amodal.json#packages:

{
  "packages": ["@amodalai/connection-typefully", "@amodalai/connection-devto"]
}

The packages array is the source of truth for package content. Do not use dependencies in amodal.json to load agent packages.

spec.json

Built-in connection protocols are REST (the default) for HTTP APIs and MCP for Model Context Protocol tool servers. Installed connection drivers can define additional protocols.

REST Connection

{
  "baseUrl": "env:SHELTER_API_URL",
  "openapi": {
    "source": "./openapi.json",
    "exposure": "auto",
    "sessionHeader": "x-agent-session-id"
  },
  "filter": { "exclude_paths": ["/health"] }
}

To follow an independently deployed API, select its document URL:

{
  "baseUrl": "env:SPECTRUMOS_API_URL",
  "openapi": {
    "source": "env:SPECTRUMOS_OPENAPI_URL",
    "exposure": "discovery",
    "sessionHeader": "x-agent-session-id"
  },
  "auth": { "type": "bearer", "token": "env:SPECTRUMOS_API_TOKEN" }
}

The explicit openapi block activates native tools. The runtime prepares the document when building a session, registers an operation tool for each selected operation, and withholds this connection from request. Without the block, the connection uses request and surface.md, unless skipRequestTool: true withholds the generic tool. An incidental document file, format, or specUrl does not activate native tools.

Include the connection in the active agent's agent.json connections list. Native preparation loads only connections available to that agent. A missing or invalid selected document fails preparation; the runtime does not fall back to a generic request tool.

The minimal request connection is a baseUrl in spec.json plus an empty policy:

// spec.json
{
  "baseUrl": "https://api.adviceslip.com",
  "format": "rest"
}
// policy.json
{
  "endpoints": {}
}

Omit auth for a public API; there is no "none" auth type. Bearer authentication defaults to the Authorization header and Bearer prefix. Custom handlers retain ctx.request() for both connection modes.

REST requests through native tools and the generic request tool refuse redirects, cap responses at 5 MiB, and combine caller cancellation with a 30-second timeout. Set baseUrl to the final API URL. These limits apply whether or not the connection declares an openapi block.

MCP Connection

{
  "protocol": "mcp",
  "transport": "stdio",
  "command": "uvx",
  "args": ["mcp-server-github"],
  "env": { "GITHUB_TOKEN": "env:GITHUB_TOKEN" }
}

MCP connections do not require policy.json, baseUrl, or format. See MCP Servers for full details on transports and configuration.

Fields

FieldDescription
protocol"rest" (default) or "mcp"
baseUrlAPI base URL (required for REST)
specUrlDocumentation URL; does not activate native tools
skipRequestToolWithhold a REST connection from the generic request tool. Use it when custom tools own API execution. It does not disable native tools selected by openapi.
testPathRelative path appended to baseUrl for validate health checks (optional, e.g. "/me")
format"openapi", "graphql", "grpc", "rest", or "aws-api" (REST only)
auth.type"bearer", "api_key", "basic", "header", "body", "google_service_account" (REST only), or "oauth2" (REST and HTTP or SSE MCP). OAuth takes no token; the account is authorized in Amodal or the CLI
auth.providerManaged OAuth application id on the platform ("oauth2" only). Without it, a custom application comes from oauth.json, or from MCP discovery for an MCP server
filterNative selection: tags and include_paths intersect; exclude_paths applies last
transport"stdio", "sse", or "http" (MCP only)
commandCommand to spawn (MCP stdio only)
argsCommand arguments (MCP stdio only)
envEnvironment variables (MCP stdio only)
urlServer URL (MCP sse/http only)
headersHTTP headers, e.g. for auth (REST and MCP sse/http)
contextInjectionInject scope context values into API requests (see below)
openapi.sourceRequired native source: ./file.json, ./file.yaml, ./file.yml, an HTTP(S) URL, or env:VAR resolving to a URL. Files must remain inside the connection directory after symlink resolution.
openapi.exposureauto (default), direct, or discovery; operation visibility is bounded to 40 across native connections.
openapi.sessionHeaderOptional header carrying the runtime session ID on operation requests. Auth header names are reserved. Document fetches do not receive it.

Context Injection

When embedding an agent in a multi-tenant app, you need API calls to include tenant identifiers, such as a user, organization, or workspace ID. contextInjection forwards values from request context into calls to this connection. Context can contain client-supplied values; the downstream API must validate tenant access independently.

{
  "baseUrl": "https://api.example.com",
  "auth": {
    "type": "bearer",
    "token": "env:API_TOKEN"
  },
  "contextInjection": {
    "tenant_id": {
      "in": "header",
      "field": "X-Tenant-Id",
      "required": true
    },
    "org_id": {
      "in": "query",
      "field": "org_id"
    }
  }
}

Each key in contextInjection maps to a key in the scope context object. When the agent makes a request to this connection, the runtime looks up that key in the context and injects the value.

FieldDescription
inWhere to inject: "header", "query", "path", or "body"
fieldThe header name, query param name, path placeholder, or body field name
requiredIf true, requests fail with an error when the key is missing from scope context. Defaults to false (silently skipped).
Injection targets:
  • header: adds field: value to request headers (e.g., X-Tenant-Id: abc123)
  • query: appends field=value to the URL query string
  • path: replaces {field} in the endpoint URL (e.g., /tenants/{tenant_id}/data)
  • body: adds field: value to the JSON request body (POST/PUT/PATCH only)

Native OpenAPI connections support header, query and body injection, including declared DELETE bodies. They reject path injection during preparation. Query injection supplies defaults that model arguments can override; do not use those defaults as an authorization boundary. See native policy and confirmation for runtime-owned fields and validation.

How context is passed: The embedding app includes a context object in the chat request body alongside scope_id:

{
  "message": "What are my recent orders?",
  "scope_id": "user-123",
  "context": {
    "tenant_id": "tenant-abc",
    "org_id": "org-456"
  }
}

See Scope for full details on multi-tenant configuration.

Native OpenAPI connections

Tools and arguments

Operation tools use <connection>__<operationId>. Characters other than letters, digits, underscores and hyphens become underscores. Names have a 64-character limit and a deterministic hash suffix for longer names. Missing identifiers derive from the method and path. Duplicate identifiers or tool names fail preparation.

Arguments contain only declared parameter groups:

{
  "path": { "residentId": "r-2" },
  "query": { "notify": false },
  "body": { "adopterName": "Dana Kim" }
}

Required path or query members make their group required, except query members supplied by context injection. The runtime validates required query values after applying those defaults. The body is required when the document declares it required. The model cannot select a method, URL, connection, header or operation identifier through arguments.

OpenAPI 3.0.x and 3.1.x support GET, HEAD, POST, PUT, PATCH and DELETE. Path parameters use primitive values with simple serialization. Query parameters use primitives or primitive arrays with form serialization, including both explode settings. Bodies use JSON, including a declared application/*+json media type and DELETE bodies. Configured headers and cookies can bind required non-model parameters. Internal JSON Pointer references are supported; external or cyclic references fail compilation.

The runtime enforces nested objects, arrays, required keys, additional properties, enums, bounds, lengths, patterns and composition. OpenAPI 3.1 tuples use prefixItems and items: false. Draft-style items: [...] tuples are rejected with a diagnostic pointer. Limits are 5 MiB per document, 2,000 operations, reference depth 64 and 1,024 characters per regex pattern. Selected unsupported inputs fail preparation; exclude those operations with filter.

Operation descriptions include the summary and method/path template. Deprecated operations remain selected and are marked. Document servers, security and vendor extensions do not route requests, acquire credentials or grant permissions. Configure credentials in spec.json and permissions in policy.json.

Unmet document authentication requirements produce diagnostics. Schemas declaring a __proto__ property are rejected because the validator cannot enforce that property.

Model arguments that violate the tool schema fail before hooks, policy, confirmation and HTTP with Invalid parameters: and the affected field. Hook argument rewrites are validated again. Query values are also validated after context injection, before policy, confirmation and HTTP. Native tools execute through the agent loop; custom handlers can use ctx.request() for authored HTTP calls.

Discovery

Every native connection exposes <connection>__discover:

  • No arguments returns the tag index.
  • query searches operation identifiers, tool names, paths, tags and summaries. Queries have a 256-character limit.
  • tag selects a tag. Pages contain at most ten operations; cursor retrieves the next page.
  • Returned operations become typed tools on the next model reply. Calling a hidden operation in the same reply is refused.
  • refresh: true reloads the source at a safe boundary. A refresh with queued or pending native calls is refused.

At most 40 native operation tools are visible per model round. Explicit direct tools remain visible. If explicitly direct connections exceed that combined limit, session preparation fails; use discovery or a filter. Automatic connections use direct mode when their combined operations and explicit direct operations fit the budget; otherwise automatic connections use discovery. Discovery evicts least recently used operations that are not pending. Child agents prepare their own declared connections and visible sets.

Model provider compatibility

The runtime sends generated operation schemas as formal tool definitions through the selected provider's SDK adapter. The adapter translates the model request and response. The native executor separately validates arguments and calls the application API.

For Google, middleware converts closed tuples into an item schema in the model-facing copy before SDK translation. For example, an exact two-number coordinate becomes an array of numbers in that copy. Runtime validation retains the original tuple and rejects extra, missing or incorrectly typed coordinates. Other providers use their adapter's schema handling without this Google conversion.

Provider acceptance of a tool declaration does not replace runtime validation. Run your agent's evals with its selected model and real contract before deployment. See Providers for the tested provider paths and their limits.

Policy and confirmation

A native write without a matching policy entry is denied. GET and HEAD default to reads; intent: "read" in a policy entry can classify a POST as a read. Model-supplied intent grants nothing. Deny rules also apply to reads.

Policy keys match the operation template and encoded concrete path. All matching rules apply, including wildcards; the strictest tier wins. A write entry without confirm allows the write, confirm: true asks for approval, and review or never denies it.

{
  "endpoints": {
    "POST /residents/*": { "returns": [], "confirm": true },
    "POST /residents/{residentId}/adopt": { "returns": [], "body": ["adopterName"] }
  }
}

body lists allowed top-level body keys. Matching lists intersect; an empty list permits no body fields. Extra keys are rejected before confirmation. A body-key policy requires an object body. Threshold fields address effective groups, such as body.amount or query.limit, after context injection. Approval is bound to the call and effective request; changed arguments require approval again.

Query context injection supplies defaults that caller values can override. Body and header injection are runtime-owned; model values for those body fields are rejected. Path injection is unsupported. $NAME in a model value remains literal. The session header uses the runtime session ID, not caller-provided scope context. Scope context is not authenticated identity.

Freshness, results and diagnostics

File sources are read on every fresh session preparation. URL sources are cached for 60 seconds per resolved URL and credential fingerprint. Concurrent loads share a fetch; failures are not cached. Each session retains its catalog until explicit refresh. A failed refresh keeps the current catalog. API 404 or 410 responses do not trigger retries or silently change the contract.

Operation requests preserve the configured base path and use the REST request limits described above. Document fetches have a 15-second deadline. Connection credentials accompany document requests only on the API base URL's origin.

Results over 16 KiB are stored in session memory and returned as a bounded digest with a handle. read_spilled_result selects a nested dot path and pages arrays with offset and limit; each page stays within 16 KiB. The store retains at most 16 results or 32 MiB and is not persisted across process restarts.

The connection inspector reports the redacted source, catalog digest, OpenAPI version, operation counts, exposure mode and preparation status. Native tool traces carry connection, operation identifier, method, template, catalog digest, policy outcome and duration.

Custom tools and SDK usage

Native tools execute operations described by the OpenAPI contract. Custom tools implement domain logic and can call the API through ctx.request(). A custom discovery and execution interface can use skipRequestTool: true to withhold the generic request tool. Authored ctx.request() calls apply connection authentication and context injection. They do not enforce endpoint permissions or response field restrictions. They also omit native schema validation, confirmation, discovery, and result paging. Enforce required permissions and approval in the authored workflow or the downstream API.

Programmatic SDK callers await agent.createSession(), which returns Promise<AgentSession> regardless of connection mode. Connection preparation can reject that promise. Each session has its own tool visibility and result handles:

const agent = await createAgent({ bundle, provider });
const session = await agent.createSession();

policy.json

Controls what the agent can see and do:

{
  "endpoints": {
    "GET /tickets/{id}": {
      "returns": ["Ticket"]
    },
    "POST /tickets/{id}/comments": {
      "returns": ["Ticket"],
      "confirm": true,
      "reason": "Adds a visible support-ticket comment"
    },
    "DELETE /tickets/{id}": {
      "returns": ["Ticket"],
      "confirm": "never",
      "reason": "Ticket deletion is not allowed through the agent"
    }
  },
  "fieldRestrictions": [
    {
      "entity": "Customer",
      "field": "payment_token",
      "policy": "never_retrieve",
      "sensitivity": "secret",
      "reason": "Payment tokens are never exposed to the agent"
    },
    {
      "entity": "Customer",
      "field": "email",
      "policy": "retrieve_but_redact",
      "sensitivity": "pii_name"
    },
    {
      "entity": "Ticket",
      "field": "internal_notes",
      "policy": "role_gated",
      "sensitivity": "internal",
      "allowedRoles": ["supervisor"]
    }
  ],
  "rowScoping": {
    "Ticket": {
      "tenant_id": {
        "type": "field_match",
        "userContextField": "tenantId",
        "label": "your tenant's tickets"
      }
    }
  },
  "delegations": {
    "enabled": true,
    "maxDurationDays": 7,
    "escalateConfirm": true
  },
  "alternativeLookups": [
    {
      "restrictedField": "Customer.payment_token",
      "alternativeEndpoint": "GET /customers/{id}/billing-status",
      "description": "Use billing status instead of raw payment credentials"
    }
  ]
}

Omit confirm for read endpoints. For connection policies, confirm accepts only true, "review", or "never"; false is not valid.

Action Tiers

TierBehavior
omittedAllow without confirmation
trueAsk user for approval before executing
"review"Blocked for the agent: refused with a reason stating human review is required. The runtime does not queue the call; escalation is up to the host app
"never"Block the operation entirely

These tiers are one surface of the platform-wide confirmation model; Confirmation Model maps them to custom-tool exposure kinds and the request tool's intent handshake.

Field Restriction Policies

Restrictions apply only when the scrubber identifies both the endpoint's declared returns entity and a matching nested entity key in the JSON response. A flat object is not inferred to be that entity. Generic REST calls require an exact policy endpoint key; native OpenAPI calls match operation templates and concrete paths. Verify the actual response shape in an eval before relying on removal or redaction.

PolicyEffect
never_retrieveMatching field removed from the JSON response
retrieve_but_redactMatching value kept in model data and recorded for redaction from user-facing output
role_gatedAlways removed. allowedRoles is not applied by the field scrubber.

Threshold Escalation

Endpoints can escalate their confirmation tier based on request parameters:

{ "field": "body.amount", "above": 10000, "escalate": "review" }

Add this object to an endpoint's thresholds array. If body.amount > 10000, its tier escalates to review and execution is denied.

Packaged Connections

Connection packages can provide reusable specs, policies, and model-facing docs. Declare packaged content in amodal.json#packages, then override specific docs locally when your agent needs project-specific guidance.