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 point | Destination |
|---|---|
| Amodal with a GitHub source | A commit on the selected branch. Add connection defaults to the production branch. |
| Amodal with an Amodal source | A source snapshot stored by Amodal. No GitHub repository is involved. |
CLI connections add or remove | Files 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 rulesA REST connection uses either native OpenAPI tools or the generic request tool:
spec.jsonconfigures the API, credentials and optionalopenapisource.openapi.jsonsupplies operation contracts when selected byopenapi.source.surface.mddocuments connections served byrequest. Native connections derive their tools from the OpenAPI document and ignore this endpoint listing.policy.jsondefines 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.mdandrules.mdsupply 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
| Field | Description |
|---|---|
protocol | "rest" (default) or "mcp" |
baseUrl | API base URL (required for REST) |
specUrl | Documentation URL; does not activate native tools |
skipRequestTool | Withhold 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. |
testPath | Relative 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.provider | Managed 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 |
filter | Native selection: tags and include_paths intersect; exclude_paths applies last |
transport | "stdio", "sse", or "http" (MCP only) |
command | Command to spawn (MCP stdio only) |
args | Command arguments (MCP stdio only) |
env | Environment variables (MCP stdio only) |
url | Server URL (MCP sse/http only) |
headers | HTTP headers, e.g. for auth (REST and MCP sse/http) |
contextInjection | Inject scope context values into API requests (see below) |
openapi.source | Required 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.exposure | auto (default), direct, or discovery; operation visibility is bounded to 40 across native connections. |
openapi.sessionHeader | Optional 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.
| Field | Description |
|---|---|
in | Where to inject: "header", "query", "path", or "body" |
field | The header name, query param name, path placeholder, or body field name |
required | If true, requests fail with an error when the key is missing from scope context. Defaults to false (silently skipped). |
- header: adds
field: valueto request headers (e.g.,X-Tenant-Id: abc123) - query: appends
field=valueto the URL query string - path: replaces
{field}in the endpoint URL (e.g.,/tenants/{tenant_id}/data) - body: adds
field: valueto 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.
querysearches operation identifiers, tool names, paths, tags and summaries. Queries have a 256-character limit.tagselects a tag. Pages contain at most ten operations;cursorretrieves the next page.- Returned operations become typed tools on the next model reply. Calling a hidden operation in the same reply is refused.
refresh: truereloads 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
| Tier | Behavior |
|---|---|
| omitted | Allow without confirmation |
true | Ask 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.
| Policy | Effect |
|---|---|
never_retrieve | Matching field removed from the JSON response |
retrieve_but_redact | Matching value kept in model data and recorded for redaction from user-facing output |
role_gated | Always 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.