Connections
Each connection gives the runtime enough structure to make API calls and gives the model enough context to choose the right calls. You can define a connection locally in connections/, or install a package and list it in amodal.json#packages.
amodal pkg connect slack # install a plugin
amodal pkg sync --from <url> # sync from OpenAPI/GraphQLDirectory Structure
connections/my-api/
├── spec.json ← protocol, base URL, auth, health check
├── access.json ← endpoint permissions, confirmations, field restrictions
├── surface.md ← endpoint capabilities and model-facing usage notes
├── entities.md ← (optional) entity definitions
└── rules.md ← (optional) business rulesFor a local REST connection, spec.json and surface.md are the important files:
spec.jsonis the machine contract. The runtime uses it to buildctx.request()and therequesttool.surface.mdis the model contract. It tells the LLM which endpoints exist and when to use them.access.jsonis optional for simple APIs. When present, it tightens permissions and confirmation behavior.entities.mdandrules.mdare optional context for complex APIs.
Installed packages can provide all of these files. A local connections/<name>/surface.md, access.json, entities.md, or rules.md can customize a packaged connection while continuing to use the package's spec.json.
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
Connections support two protocols: REST (the default) for HTTP APIs, and MCP for Model Context Protocol tool servers.
REST Connection
{
"baseUrl": "https://api.example.com",
"specUrl": "https://api.example.com/openapi.json",
"format": "openapi",
"auth": {
"type": "bearer",
"token": "env:MY_API_TOKEN",
"header": "Authorization",
"prefix": "Bearer "
},
"sync": {
"auto": true,
"frequency": "on_push",
"notify_drift": true
},
"filter": {
"tags": ["public"],
"include_paths": ["/api/v2/**"],
"exclude_paths": ["/api/v2/internal/**"]
}
}MCP Connection
{
"protocol": "mcp",
"transport": "stdio",
"command": "uvx",
"args": ["mcp-server-github"],
"env": { "GITHUB_TOKEN": "env:GITHUB_TOKEN" }
}MCP connections do not require access.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 | URL to the API spec document (optional) |
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", "oauth2", "basic", "header" (REST only) |
sync | Auto-sync settings and drift notification (REST only) |
filter | Include/exclude endpoints by tag or path glob (REST only) |
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 (MCP sse/http only) |
trust | Trust unsigned responses (MCP http only) |
contextInjection | Inject scope context values into API requests (see below) |
Context Injection
When embedding an agent in a multi-tenant app, you need API calls to include tenant-specific identifiers (user ID, org ID, household ID, etc.) so the downstream API returns only that tenant's data. contextInjection automates this — the runtime reads values from the scope context (passed with the chat request) and injects them into every API call to this connection.
{
"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)
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.
access.json
Controls what the agent can see and do:
{
"endpoints": {
"GET /api/deals/{id}": {
"returns": ["Deal"],
"confirm": false
},
"POST /api/deals": {
"returns": ["Deal"],
"confirm": true,
"reason": "Creates a new deal",
"thresholds": [
{ "field": "body.amount", "above": 10000, "escalate": "review" }
]
},
"DELETE /api/deals/{id}": {
"returns": ["Deal"],
"confirm": "never",
"reason": "Deletion not allowed via agent"
}
},
"fieldRestrictions": [
{
"entity": "Contact",
"field": "ssn",
"policy": "never_retrieve",
"sensitivity": "pii_identifier",
"reason": "PII — never exposed"
},
{
"entity": "Contact",
"field": "email",
"policy": "retrieve_but_redact",
"sensitivity": "pii_name"
},
{
"entity": "Deal",
"field": "internal_notes",
"policy": "role_gated",
"sensitivity": "internal",
"allowedRoles": ["supervisor"]
}
],
"rowScoping": {
"Deal": {
"owner_id": {
"type": "field_match",
"userContextField": "userId",
"label": "your deals"
}
}
},
"delegations": {
"enabled": true,
"maxDurationDays": 7,
"escalateConfirm": true
},
"alternativeLookups": [
{
"restrictedField": "Contact.ssn",
"alternativeEndpoint": "GET /api/contacts/{id}/verification-status",
"description": "Use verification status instead of raw SSN"
}
]
}Action Tiers
| Tier | Behavior |
|---|---|
false / omitted | Allow without confirmation |
true / "confirm" | Ask user for approval before executing |
"review" | Show the full plan before executing |
"never" | Block the operation entirely |
Field Restriction Policies
| Policy | Effect |
|---|---|
never_retrieve | Field completely removed from API responses |
retrieve_but_redact | Kept in data, replaced with [REDACTED] in output |
role_gated | Removed if user lacks allowedRoles, else redactable |
Threshold Escalation
Endpoints can escalate their confirmation tier based on request parameters:
{ "field": "body.amount", "above": 10000, "escalate": "review" }If body.amount > 10000, the tier escalates from confirm to review.
Drift Detection
amodal pkg sync --check # report drift without updating (CI-friendly)
amodal pkg sync # update local specs from remotePre-built Plugins
50+ pre-built connection plugins are available for popular APIs like Slack, GitHub, Stripe, Datadog, Jira, and more. Browse and install them from the Amodal Marketplace, or install directly from the CLI:
amodal pkg connect slack
amodal pkg connect stripe
amodal pkg connect datadogFor APIs not in the marketplace, create custom connections using the directory structure above with your own OpenAPI or GraphQL spec.