Connections
A connection tells the runtime how to call an external API and tells the model which calls exist. Define local connections in amodal/connections/<name>/ or the top-level connections/<name>/. The loader reads amodal/connections/ when it contains entries, otherwise connections/. amodal connections add <name> follows the same rule; see Use the CLI.
Where changes are saved
| Entry point | Destination |
|---|---|
| Amodal with a GitHub source | A commit on the selected branch. A branch that does not exist is created from the production branch. |
| Amodal with an Amodal source | A source snapshot stored by Amodal. |
CLI connections add or remove | Files in the working tree at --path. Commit and push them yourself. |
Source holds only connection configuration. Secret values, OAuth client secrets, tokens, and account bindings stay in credential storage. Deploy the saved source to make a connection available to a hosted runtime.
Directory Structure
amodal/connections/my-api/
├── spec.json ← protocol, base URL, auth, OpenAPI source
├── 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 is served either by native OpenAPI operation tools or by the generic request tool. surface.md documents endpoints for request; native connections derive their tools from the OpenAPI document and ignore it. policy.json is required for a local REST connection, and a REST connection without one is skipped with a warning. The minimal policy is {"endpoints": {}}. MCP connections need no policy.
Credentials
Credential values live outside the repository. auth.token, auth.privateKey, and any headers or env entry named like a credential (Authorization, Proxy-Authorization, X-Api-Key, Cookie, or a name ending in token, key, secret, or password) must hold env:NAME, scope:KEY, or a {{NAME}} template. A literal value fails validation with CONNECTION_SECRET_IN_SOURCE. Other headers and environment entries 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, and a managed provider is selected with auth.provider. An MCP server that publishes OAuth metadata needs neither. See OAuth connections.
spec.json
Built-in protocols are rest (the default) and mcp. Installed connection drivers can define other protocols.
REST Connection
A native OpenAPI connection:
{
"baseUrl": "env:SHELTER_API_URL",
"openapi": {
"source": "./openapi.json",
"exposure": "auto",
"sessionHeader": "x-agent-session-id"
},
"auth": { "type": "bearer", "token": "env:SHELTER_API_TOKEN" },
"filter": { "exclude_paths": ["/health"] }
}The openapi block activates native tools. The runtime prepares the document when it builds a session, registers one tool per selected operation, and withholds the connection from request. A missing or invalid document fails preparation; the runtime does not fall back to request. A document file, format, or specUrl without the openapi block does not activate native tools.
A minimal request connection is a baseUrl plus an empty policy:
// spec.json
{ "baseUrl": "https://api.adviceslip.com" }// policy.json
{ "endpoints": {} }A named agent sees a connection only when its agent.json connections list includes it. Omit auth for a public API; there is no "none" auth type.
REST calls through native tools and request refuse redirects, cap responses at 5 MiB, and time out after 30 seconds. Set baseUrl to the final API URL.
MCP Connection
{
"protocol": "mcp",
"transport": "stdio",
"command": "uvx",
"args": ["mcp-server-github"],
"env": { "GITHUB_TOKEN": "env:GITHUB_TOKEN" }
}See MCP Servers for transports and options.
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 request, for example when custom tools own API execution. Does not disable native tools. |
test | Request for Test connection: path relative to baseUrl, method (GET default, POST, or HEAD), query, body, headers, and expectStatuses (default [200]). |
testPath | Path for a HEAD test request when test is absent, for example "/me". |
format | "openapi", "graphql", "grpc", "rest", or "aws-api". REST only. |
auth.type | "bearer", "api_key", "basic", "header", "body", "google_service_account", or "oauth2". MCP supports bearer, and oauth2 over HTTP or SSE. oauth2 takes no token. |
auth.token | Credential reference for bearer, api_key, basic (base64 user:pass), and header. |
auth.header | REST only. Header for bearer, api_key, and header. Defaults to Authorization, or X-API-Key for api_key. |
auth.prefix | REST bearer prefix (default Bearer). MCP bearer always sends Authorization: Bearer <token>. |
auth.fields | For body: credential fields merged into the JSON body of POST, PUT, and PATCH requests from the request tool, and of every non-GET, non-HEAD request from native operation tools. |
auth.clientEmail | For google_service_account, with auth.privateKey, auth.scope (default https://www.googleapis.com/auth/cloud-platform), and auth.tokenUri (default https://oauth2.googleapis.com/token). |
auth.provider | Managed OAuth application ID (oauth2 only). |
filter | Native operation selection: tags and include_paths intersect; exclude_paths applies last. |
transport | "stdio", "sse", or "http". MCP only. |
command, args | Command and arguments to spawn. MCP stdio only. |
env | Environment variables. MCP stdio only. |
url | Server URL. MCP SSE and HTTP only. |
headers | HTTP headers. REST, and MCP SSE and HTTP. |
contextInjection | Forward request context values into API requests. See Context Injection. |
openapi.source | ./file.json, ./file.yaml, ./file.yml, an HTTP(S) URL, or env:VAR resolving to a URL. Files must stay inside the connection directory after symlink resolution. |
openapi.exposure | auto (default), direct, or discovery. See Discovery. |
openapi.sessionHeader | Header that carries the runtime session ID on operation requests. Credential header names are reserved. Document fetches do not receive it. Operations of this connection run one at a time. |
Context Injection
contextInjection forwards values from the chat request's context into calls to this connection, such as a tenant or workspace ID:
{
"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 names a key in the request context:
{
"message": "What are my recent orders?",
"scope_id": "user-123",
"context": { "tenant_id": "tenant-abc", "org_id": "org-456" }
}| Field | Description |
|---|---|
in | "header", "query", "path", or "body". |
field | Header name, query parameter, {placeholder} in the endpoint path, or body field. |
required | When true, a request fails if the context lacks the key. Default false, which skips the key. |
queryis not appended when the request already sets that parameter.bodyadds the field to the JSON body, which is sent only for POST, PUT, and PATCH. Therequesttool rejects a call whosedatais not an object when body injection is configured.- Native OpenAPI connections support header, query, and body injection. Path injection fails preparation. Native query injection supplies defaults that model arguments can override. Model values for injected body fields are rejected.
Context can contain client-supplied values and is not authenticated identity. The downstream API must validate tenant access. See Scope.
Native OpenAPI connections
Tools and arguments
Operation tools are named <connection>__<operationId>. Characters other than letters, digits, underscores, and hyphens become underscores. Names longer than 64 characters are shortened with a hash suffix. A missing operationId is derived from the method and path. Duplicate identifiers or tool names fail preparation.
Arguments contain only the declared parameter groups:
{
"path": { "residentId": "r-2" },
"query": { "notify": false },
"body": { "adopterName": "Dana Kim" }
}The model cannot select a method, URL, connection, header, or operation through arguments. Arguments that violate the schema fail with Invalid parameters: before hooks, policy, confirmation, and HTTP.
Supported contracts:
- OpenAPI 3.0.x and 3.1.x, with GET, HEAD, POST, PUT, PATCH, and DELETE.
- Path parameters with primitive values and
simpleserialization. - Query parameters with primitives or primitive arrays and
formserialization. - JSON bodies, including
application/*+jsonmedia types and DELETE bodies. - Internal JSON Pointer references. External or cyclic references fail.
- OpenAPI 3.1 tuples with
prefixItemsanditems: false. Draft-styleitems: [...]tuples are rejected. - Schemas without a
__proto__property.
Limits are 16 MiB per document, 2,000 operations, reference depth 64, and 1,024 characters per regex pattern. An unsupported selected operation fails preparation; exclude it with filter.
Document servers, security, and vendor extensions do not route requests, supply credentials, or grant permissions. Configure credentials in spec.json and permissions in policy.json.
Discovery
Every native connection exposes <connection>__discover:
- Without arguments, it returns the tag index.
querysearches operation IDs, tool names, paths, tags, and summaries, up to 256 characters.tagselects a tag. Pages hold at most ten operations;cursorfetches the next page.- Returned operations become typed tools on the next model reply. Calling them in the same reply is refused.
refresh: truereloads the source. It is refused while native calls are pending.
At most 40 native operation tools are visible per model round across native connections. direct connections that exceed the budget fail session preparation. auto connections use direct mode when they fit beside the direct connections, and discovery otherwise. Discovery evicts the least recently used operations.
Model provider compatibility
Operation schemas are sent to the model as formal tool definitions. The runtime validates arguments against the original schema regardless of how the provider adapter translates it. See Providers.
Policy and confirmation
Native operations differ from request in these ways:
- A write without a matching policy entry is denied.
- GET and HEAD are reads. A matching entry with
"intent": "read"makes another method a read. - All matching keys apply, including wildcards, and the strictest tier wins. Keys match the template and the encoded concrete path.
reviewandneveralso deny reads.bodyin an entry lists allowed top-level body keys. Lists from several matching entries intersect, and an empty list permits no body. Other keys are rejected before confirmation.
{
"endpoints": {
"POST /residents/*": { "returns": [], "confirm": true },
"POST /residents/{residentId}/adopt": { "returns": [], "body": ["adopterName"] }
}
}Approval is bound to the call and its effective arguments. A hook rewrite after approval asks again.
Freshness and results
File sources are read on every session preparation. URL sources are cached for 60 seconds per URL and credential set, and failures are not cached. A session keeps its catalog until refresh. Document fetches time out after 15 seconds and receive connection credentials only on the baseUrl origin.
Results over 16 KiB are kept in session memory and returned as a digest with a handle. read_spilled_result reads a nested dot path and pages arrays with offset and limit. The store keeps at most 16 results or 32 MiB and does not survive a process restart.
Custom tools
Custom handlers can call any connection with ctx.request(). These calls apply connection authentication and context injection, but not endpoint policy, field restrictions, schema validation, or confirmation. See Tools.
policy.json
{
"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"
}
],
"alternativeLookups": [
{
"restrictedField": "Customer.payment_token",
"alternativeEndpoint": "GET /customers/{id}/billing-status",
"description": "Use billing status instead of raw payment credentials"
}
],
"delegations": { "escalateConfirm": true }
}| Field | Description |
|---|---|
endpoints | Required. Keys are METHOD /path, with {param} and * patterns. |
endpoints.*.returns | Required. Entity names the response contains, used by field restrictions. Use [] when none apply. |
endpoints.*.confirm | Omitted, true, "review", or "never". See Action Tiers. false is invalid. |
endpoints.*.reason | Text shown in a confirmation card or denial. |
endpoints.*.thresholds | Parameter rules that raise the tier. See Threshold Escalation. |
endpoints.*.intent | "read" lets a mutating method run as a read. |
endpoints.*.body | Allowed top-level body keys. Native OpenAPI only. |
fieldRestrictions | entity, field, policy, sensitivity, and optional reason. See Field Restriction Policies. |
alternativeLookups | Prompt guidance of the form "Instead of restrictedField, use alternativeEndpoint". |
delegations.escalateConfirm | When true, confirm becomes review for calls made in a delegated session (a subagent or an autonomous run). |
Action Tiers
confirm | Behavior |
|---|---|
| omitted | Run without approval. |
true | Pause for user approval, then run. |
"review" | Refuse with a reason stating human review is required. The runtime does not queue the call. |
"never" | Refuse. |
For the generic request tool:
- Tiers apply only to writes (POST, PUT, PATCH, DELETE). Reads run without a policy check.
- A write that matches no entry runs without approval. Add a
"*"or wildcard entry to set a minimum tier. - An exact key wins. Otherwise the strictest matching pattern applies.
Confirmation Model describes the approval flow and how custom-tool tiers map to these values.
Field Restriction Policies
| Policy | Effect |
|---|---|
never_retrieve | Field removed from the JSON response. |
retrieve_but_redact | Value masked: ****6789 for a value of 8 or more characters ending in 4 digits, **** otherwise. |
role_gated | Field always removed. allowedRoles is accepted but not applied. |
Restrictions also add "Do not request" and "Will be redacted" lines to the system prompt.
For request calls, the scrubber checks the top-level object, or each item of a top-level array, against every entity in the endpoint's returns. A key named after an entity, such as customer or tickets, limits everything under it to that entity's restrictions. Other keys, such as data or items, keep the combined set. Entity names match regardless of case.
Native OpenAPI connections identify entities from the response schema: data at a position whose schema references #/components/schemas/Taxpayer is a Taxpayer, whatever its key. Restrictions apply to every oneOf, anyOf, and allOf branch, even without a returns entry. Key-name matching applies where the schema names no restricted component.
MCP tools, connection-driver tools, and ctx.request() results are not scrubbed. Verify the response shape in an eval before relying on removal or redaction.
Threshold Escalation
{ "field": "body.amount", "above": 10000, "escalate": "review" }Add this object to an endpoint's thresholds array. When body.amount exceeds 10000, the tier escalates to review and the call is denied. escalate accepts "review" or "never". A missing or non-numeric value escalates as if the threshold were exceeded, and numeric strings compare as numbers. field is a dot path into the grouped parameters: body and query, plus path for native tools. Native thresholds see values after context injection.
Packaged Connections
Packages listed in amodal.json#packages can ship connections. See amodal.json. A local connection with its own spec.json takes precedence over a packaged one of the same name. To customize a packaged connection while keeping its spec.json, add surface.md, policy.json, oauth.json, entities.md, or rules.md under the top-level connections/<name>/, not under amodal/. A local policy.json merges with the package policy.